Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
71 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Contents
 Page 1
 Page 2

 Printable version
 Discuss this article
 in the forums


Ok so let's assume a connection is requested. For this we'll need to code the ConnectionRequest event. The most interesting and difficult thing to get right when coding any kind of network application is the handshake. Just as you'd shake someone's hand when you meet them in the street, our client must shake hands with the server to introduce itself. When our client says hello, we'll say hello back and ask the user to tell us what their name or "handle" is. We'll also do the following:

a) create an additional winsock control in the server
b) assign it a random port (since it can't share the same port as the server) and
c) accept the new connection on that control.
d) We'll also assign a "handle" to that user, add them to the listed connections on the server.

We do this by putting the following code in the connection request event.

Private Sub sktConnection_ConnectionRequest(Index As Integer, ByVal requestID As Long)
  ' Now accept the new connection
  'A connection was requested from the server.
  Dim i As Integer
  Dim iConnection As Integer
  'Make sure this is control 0 in the array.
  'This is the only one that can accept connections.
  If Index = 0 Then

    'Search for available Winsock control.
    For i = 1 To gNumConnections
      If sktConnection(i).State = sckClosed Then
        iConnection = i
        Exit For
      End If
    Next i

    'If none was found, create a new one.
    If iConnection = 0 Then

      ' Tell the world there's a new connection
      gNumConnections = gNumConnections + 1

      'Load a new Winsock control for this connection.
      Load sktConnection(gNumConnections)

      ' This connection needs a handle
      ReDim Preserve gHandles(gNumConnections) As String
      ReDim Preserve gSentYN(gNumConnections) As Boolean
      ReDim Preserve gMessages(gNumConnections) As String

      ' Catch this user up on the previous conversation
      ' This way they don't get resent the chat session to date
      gMessages(gNumConnections) = gTotalincoming

      ' set their handle
      gHandles(gNumConnections) = "unknown"

      ' Add to the servers connections
      lblActiveConnections.Caption = gNumConnections & " Active Connections"
      iConnection = gNumConnections
    End If

    'Set port for this control to 0. (Randomly assigns an available port.)
    sktConnection(iConnection).LocalPort = 0

    'Have this control accept the connection.
    sktConnection(iConnection).Accept requestID

    ' Send the welcome message
    sktConnection(iConnection).SendData "Welcome to the CornflakeZone, enter your handle"
  End If

End Sub

Ok so now the users' received a request to input their username/handle. Let's imagine that the client did that. We'd get a DataArrival event triggered on that client's server side winsock control. VB tells us which control this is by filling in the "index" parameter for us. To complete the "handshake", I've added some logic to state that if the user's handle is unknown then the data arriving must be their handle so assign it.

If the handle is assigned then the data arriving must be a part of the conversation so we add it to the global record of the conversation. I've coded some simple functions to do these tasks for me. Here's the DataArrival code.

Private Sub sktConnection_DataArrival(Index As Integer, ByVal bytesTotal As Long)
  'Data has arrived at the server from an open connection.
  Dim newdata As String

  'Get the data.
    sktConnection(Index).GetData newdata If gHandles(Index) = "unknown" Then ' Store it internally gHandles(Index) = newdata ' Announce the new arrival AddToTotalIncoming (newdata & " just joined") Else 'Pass the index of the connection from which the data came. AddToTotalIncoming (gHandles(Index) & ":: " & newdata) End If End Sub

Ok, it's time to complete the server side code. Let's review. We have a way for the users to connect, we have a way for the server to accept data. We don't have a way to transmit the conversation to the users. That's what we'll code next. You saw in the form screen shots that we added a timer to the server. Double click on that control to create it's interval event. The server's responsibility is to transmit the conversation to the clients.

You might have noticed me referring to the "global record of the conversation". What I mean by this is that the server maintains the conversation to date. It would be inefficient of us to send the entire conversation to EVERY client EVERY time there's an update. That's just silly. What makes more sense is that if we just send the differences in the conversation to the client. We maintain a copy of the conversation that's been sent to each client so we can determine what the differences are for that client. I do this with the "Left()" function.

We'll send the conversation updates one at a time to each client. Every time the timer fires, we send one of the clients' the latest conversation updates. Let's maintain an array of "sent YN" flags to help us figure out which clients need an update. The timer function will fire each 100 or so milliseconds so a 10 user session will experience 1 second total lag time. Not bad for a homegrown solution. You can play around with the 100 milliseconds to find the value that works best for you.

Take a look at the code for the timer function.

Private Sub tmServerTimer_Timer()
  txtTotalIncoming.Text = gTotalincoming
  'Send the new data to the group
  Call BroadcastMessage
End Sub
Private Sub BroadcastMessage()
  Dim i
  Dim myMessage As String
  Dim conn_index As Integer

  ' Set the default connection index
  conn_index = -1

  ' Loop through all connections excluding the servers
  ' Hence we start at 1
  For i = 1 To gNumConnections
    ' if this connection has not had an update then
    If gSentYN(i) = False And sktConnection(i).State = sckConnected Then
      ' Get the message we need to deliver
      myMessage = Left(gTotalincoming, Len(gTotalincoming) - Len(gMessages(i)))
      ' update the message store for this user
      gMessages(i) = gTotalincoming
      ' Get the index of this connection
      conn_index = i
      Exit For
    End If
  Next i

  ' This is so we know to
  ' send the data to everyone
  If conn_index = -1 Then
    For i = 1 To gNumConnections
      gSentYN(i) = False
    Next
  End If

  If conn_index > -1 Then
    ' check the connection's open
    If sktConnection(conn_index).State = sckConnected Then
      ' send the data
      sktConnection(conn_index).SendData myMessage
      'signal that we've sent to this user
      gSentYN(conn_index) = True
    End If
  End If

End Sub

Ok, let's turn our attention to the client. This a much simpler little app. It handles the other side of the handshake and also accepts data and displays the chat session to the user. I've added a little button to re-connect if we'd like.

Private Sub sktClient_DataArrival(ByVal bytesTotal As Long)
  Dim newdata As String
  ' Get the arriving data and print it out.
  sktClient.GetData newdata

  ' add the data to the output
  txtOutput.Text = newdata & txtOutput.Text
End Sub

Private Sub sktClient_Error(ByVal Number As Integer, Description As String,
                            ByVal Scode As Long, ByVal Source As String,
                            ByVal HelpFile As String, ByVal HelpContext As Long,
                            CancelDisplay As Boolean)
  ' This should handle any winsock errors.
  Call AddToOutput("Error: " & Description)
End Sub

Ok, that's it. If you'd like to test the application just download it and give it a shot. What's that you say? Only have one computer? That's ok, just type in localhost or 127.0.0.1. That'll connect the client to the server with both running on the same machine.

I hope you liked this tut and maybe learned a thing or two. I know I did. I didn't implement very much error checking in this app but you can easily add this and I felt it would get in the way of the code.

We implemented this in VB to keep things simple. Now that you understand how the multi-user thing works you're ready to move onto more challenging applications. Try a multi-user drawing session with a picture control. This could esaily be achieved by sending the users' mouse coordinates instead of the text messages. You could then have the clients' draw what the other users are drawing! Now THAT's communicating.

If you'd like a REAL challenge try creating a COM object to wrap up the winsock object in VC++ (see last tutorial). I've already done this in VC++ and will post it in a couple of days. I do most of my coding in C++ and just found it more useful to implement the C++ object. As always, send me feedback on this article.