Multiple Sockets

kurt69

Well-known member
Joined
Jan 17, 2006
Messages
78
Location
Australia
Programming Experience
Beginner
Hey howsit going?

Umm I'm trying to write some networking code, my application will have up to 8 concurrent connections, so it needs 8 sockets. I would rather not have to declare and write code for 8 sockets so I figured I'd try using some arrays, anyway here is what I've got. I don't have the chance to test it, so I would like to know if this would work before I do.

VB.NET:
    Public Sub lobbyHost()
        Dim h As Integer
        Dim tcpLstnr(8)
        For h = 0 To UBound(tcpLstnr)
            tcpLstnr(h) = New Sockets.TcpListener(IPAddress.Parse("127.0.0.1"), (11001 + h))
            Do While hosted = True
                tcpLstnr(h).Start()
                If tcpLstnr(h).Pending = True Then
                    Try
                        Dim tcpCntrl As TcpClient = tcpLstnr(h).AcceptTcpClient()
                        tcpCntrl.NoDelay = True
                        tcpCntrl.LingerState.Enabled = False
                        Dim tcpStrm As NetworkStream = tcpCntrl.GetStream
                        Dim bytes(1024) As [Byte]
                        Dim rcvData As [String]
                        rcvData = Nothing
                        Dim i As Int32
                        While (i <> 0)
                            rcvData = System.Text.Encoding.ASCII.GetString(bytes, 0, i)
                            i = tcpStrm.Read(bytes, 0, bytes.Length)
                        End While
                    Catch ex As Exception
                        MsgBox(ex.ToString)
                    End Try
                End If
            Loop
        Next
    End Sub
 
I tried your code and I got a port error on all the ports indicated in your code (110010-110017). If you were able to fix this, it just might work. I am not sure, but I think your looping method might cause another problem. If it is not being called on a separate thread, all other operations might freeze.

Check out this UserConnection class. I do not know what you are creating; therefore, I am not sure how helpful it might be to you. The basic Idea is that you can create a class that accepts all incoming connections and add them to an array list so that you can call on each one when needed (send data, text or even retrieve information). I used this class on my multi-chat program and it worked perfectly.

VB.NET:
[SIZE=2][COLOR=#0000ff]
Imports[/COLOR][/SIZE][SIZE=2] System.Text
[/SIZE][SIZE=2][COLOR=#0000ff]Imports[/COLOR][/SIZE][SIZE=2] System.Net.Sockets[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#008000]' The UserConnection class encapsulates the functionality of a TcpClient connection
' with streaming for a single user.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Public[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Class[/COLOR][/SIZE][SIZE=2] UserConnection[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2] Client [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] TcpClient[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]Const[/COLOR][/SIZE][SIZE=2] READ_BUFFER_SIZE [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Integer[/COLOR][/SIZE][SIZE=2] = 255
[/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2] ReadBuffer(READ_BUFFER_SIZE) [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Byte[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2] strName [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2] strHostName [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2] strIPAddress [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]'Overload the new operator to set up a read thread.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Public[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]New[/COLOR][/SIZE][SIZE=2]([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Client [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] TcpClient)
[/SIZE][SIZE=2][COLOR=#0000ff]Me[/COLOR][/SIZE][SIZE=2].Client = Client
[/SIZE][SIZE=2][COLOR=#008000]' This starts the asynchronous read thread. The data will be saved into
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' readBuffer.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Me[/COLOR][/SIZE][SIZE=2].Client.GetStream.BeginRead(ReadBuffer, 0, READ_BUFFER_SIZE, _
[/SIZE][SIZE=2][COLOR=#0000ff]AddressOf[/COLOR][/SIZE][SIZE=2] StreamReceiver, [/SIZE][SIZE=2][COLOR=#0000ff]Nothing[/COLOR][/SIZE][SIZE=2])[/SIZE]
[SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' The Name property uniquely identifies the user connection.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Public[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Property[/COLOR][/SIZE][SIZE=2] Name() [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Get
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Return[/COLOR][/SIZE][SIZE=2] strName
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Get
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Set[/COLOR][/SIZE][SIZE=2]([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Value [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2])
strName = Value
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Set
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Property[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Public[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Property[/COLOR][/SIZE][SIZE=2] IPAddress() [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Get
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Return[/COLOR][/SIZE][SIZE=2] strIPAddress
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Get
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Set[/COLOR][/SIZE][SIZE=2]([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Value [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2])
strIPAddress = Value
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Set
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Property[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Public[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Property[/COLOR][/SIZE][SIZE=2] HostName() [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Get
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Return[/COLOR][/SIZE][SIZE=2] strHostName
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Get
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Set[/COLOR][/SIZE][SIZE=2]([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Value [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2])
strHostName = Value
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Set
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Property[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Public[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Event[/COLOR][/SIZE][SIZE=2] LineReceived([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection, [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Data [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2])[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#008000]' This subroutine uses a StreamWriter to send a message to the user.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Public[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] SendData([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Data [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2])
[/SIZE][SIZE=2][COLOR=#008000]' Synclock ensure that no other threads try to use the stream at the same time.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]SyncLock[/COLOR][/SIZE][SIZE=2] Client.GetStream
[/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] writer [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]New[/COLOR][/SIZE][SIZE=2] IO.StreamWriter(Client.GetStream)
writer.Write(Data & Chr(13) & Chr(10))
[/SIZE][SIZE=2][COLOR=#008000]' Make sure all data is sent now.
[/COLOR][/SIZE][SIZE=2]writer.Flush()
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]SyncLock
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' This is the callback function for TcpClient.GetStream.Begin. It begins an 
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' asynchronous read from a stream.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] StreamReceiver([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] ar [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] IAsyncResult)[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] BytesRead [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Integer
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] strMessage [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Try[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' Ensure that no other threads try to use the stream at the same time.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]SyncLock[/COLOR][/SIZE][SIZE=2] Client.GetStream
[/SIZE][SIZE=2][COLOR=#008000]' Finish asynchronous read into readBuffer and get number of bytes read.
[/COLOR][/SIZE][SIZE=2]BytesRead = Client.GetStream.EndRead(ar)
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]SyncLock[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' Convert the byte array the message was saved into, minus one for the
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' Chr(13).
[/COLOR][/SIZE][SIZE=2]strMessage = Encoding.ASCII.GetString(ReadBuffer, 0, BytesRead - 1)[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]RaiseEvent[/COLOR][/SIZE][SIZE=2] LineReceived([/SIZE][SIZE=2][COLOR=#0000ff]Me[/COLOR][/SIZE][SIZE=2], strMessage)
[/SIZE][SIZE=2][COLOR=#008000]' Ensure that no other threads try to use the stream at the same time.[/COLOR][/SIZE]
[SIZE=2][COLOR=#008000]
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]SyncLock[/COLOR][/SIZE][SIZE=2] Client.GetStream
[/SIZE][SIZE=2][COLOR=#008000]' Start a new asynchronous read into readBuffer.
[/COLOR][/SIZE][SIZE=2]Client.GetStream.BeginRead(ReadBuffer, 0, READ_BUFFER_SIZE, _
[/SIZE][SIZE=2][COLOR=#0000ff]AddressOf[/COLOR][/SIZE][SIZE=2] StreamReceiver, [/SIZE][SIZE=2][COLOR=#0000ff]Nothing[/COLOR][/SIZE][SIZE=2])
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]SyncLock[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Catch[/COLOR][/SIZE][SIZE=2] ex [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] Exception
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Try[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub
[/COLOR][/SIZE]

This is the server's code.

VB.NET:
[SIZE=2][COLOR=#0000ff]
Imports[/COLOR][/SIZE][SIZE=2] System.Net.Sockets[/SIZE]
 
[SIZE=2][SIZE=2][COLOR=#0000ff]Const[/COLOR][/SIZE][SIZE=2] PORT_NUM [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Integer[/COLOR][/SIZE][SIZE=2] = 10000
[/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2] Clients [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]New[/COLOR][/SIZE][SIZE=2] Hashtable
[/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2] Listener [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] TcpListener
[/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2] ListenerThread [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] Threading.Thread[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#008000]' Start the background listener thread.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] MZW3_Server_Load([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] System.Object, _
[/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] e [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] System.EventArgs) [/SIZE][SIZE=2][COLOR=#0000ff]Handles[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]MyBase[/COLOR][/SIZE][SIZE=2].Load
ListenerThread = [/SIZE][SIZE=2][COLOR=#0000ff]New[/COLOR][/SIZE][SIZE=2] Threading.Thread([/SIZE][SIZE=2][COLOR=#0000ff]AddressOf[/COLOR][/SIZE][SIZE=2] DoListen)
ListenerThread.Start()
UpdateStatus([/SIZE][SIZE=2][COLOR=#800000]"Listener started"[/COLOR][/SIZE][SIZE=2])
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' This subroutine sends a message to all attached clients
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] Broadcast([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] strMessage [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2])[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] Client [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection
[/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] Entry [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] DictionaryEntry[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#008000]' All entries in the clients Hashtable are UserConnection so it is possible
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' to assign it safely.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]For[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Each[/COLOR][/SIZE][SIZE=2] Entry [/SIZE][SIZE=2][COLOR=#0000ff]In[/COLOR][/SIZE][SIZE=2] Clients
Client = [/SIZE][SIZE=2][COLOR=#0000ff]CType[/COLOR][/SIZE][SIZE=2](Entry.Value, UserConnection)
Client.SendData(strMessage)
[/SIZE][SIZE=2][COLOR=#0000ff]Next
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' This subroutine sends the contents of the Broadcast textbox to all clients, if
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' it is not empty, and clears the textbox
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] btnBroadcast_Click([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] System.Object, _
[/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] e [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] System.EventArgs) [/SIZE][SIZE=2][COLOR=#0000ff]Handles[/COLOR][/SIZE][SIZE=2] btnBroadcast.Click
[/SIZE][SIZE=2][COLOR=#0000ff]If[/COLOR][/SIZE][SIZE=2] txtBroadcast.Text <> [/SIZE][SIZE=2][COLOR=#800000]""[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Then
[/COLOR][/SIZE][SIZE=2]UpdateStatus([/SIZE][SIZE=2][COLOR=#800000]"Broadcasting: "[/COLOR][/SIZE][SIZE=2] & txtBroadcast.Text)
Broadcast([/SIZE][SIZE=2][COLOR=#800000]"BROAD|"[/COLOR][/SIZE][SIZE=2] & txtBroadcast.Text)
txtBroadcast.Text = [/SIZE][SIZE=2][COLOR=#800000]""
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]If
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' This subroutine checks to see if username already exists in the clients 
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' Hashtable. If it does, send a REFUSE message, otherwise confirm with a JOIN.[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] ConnectUser([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] UserName [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2], [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] HostName [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2], _
[/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] IPAddress [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2], [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection)[/SIZE]
[SIZE=2][COLOR=#0000ff]If[/COLOR][/SIZE][SIZE=2] Clients.Contains(UserName) [/SIZE][SIZE=2][COLOR=#0000ff]Then
[/COLOR][/SIZE][SIZE=2]ReplyToSender([/SIZE][SIZE=2][COLOR=#800000]"REFUSE"[/COLOR][/SIZE][SIZE=2], Sender)
[/SIZE][SIZE=2][COLOR=#0000ff]Else
[/COLOR][/SIZE][SIZE=2]Sender.Name = UserName
Sender.HostName = HostName
Sender.IPAddress = IPAddress
UpdateStatus(UserName & [/SIZE][SIZE=2][COLOR=#800000]" has joined the chat."[/COLOR][/SIZE][SIZE=2])
Clients.Add(UserName, Sender)
 
[/SIZE][SIZE=2][COLOR=#008000]' Send a JOIN to sender, and notify all other clients that sender joined
[/COLOR][/SIZE][SIZE=2]ReplyToSender([/SIZE][SIZE=2][COLOR=#800000]"JOIN"[/COLOR][/SIZE][SIZE=2], Sender)
SendToClients([/SIZE][SIZE=2][COLOR=#800000]"CHAT|"[/COLOR][/SIZE][SIZE=2] & Sender.Name & [/SIZE][SIZE=2][COLOR=#800000]" has joined the chat."[/COLOR][/SIZE][SIZE=2], Sender)
ListUser(Sender)
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]If
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' This subroutine notifies other clients that sender left the chat, and removes
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' the name from the clients Hashtable
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] DisconnectUser([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection)
UpdateStatus(Sender.Name & [/SIZE][SIZE=2][COLOR=#800000]" has left the chat."[/COLOR][/SIZE][SIZE=2])[/SIZE]
[SIZE=2]SendToClients([/SIZE][SIZE=2][COLOR=#800000]"CHAT|"[/COLOR][/SIZE][SIZE=2] & Sender.Name & [/SIZE][SIZE=2][COLOR=#800000]" has left the chat."[/COLOR][/SIZE][SIZE=2], Sender)[/SIZE]
[SIZE=2]Clients.Remove(Sender.Name)
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' This subroutine is used as a background listener thread to allow reading incoming
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' messages without lagging the user interface.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] DoListen()[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]Try[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' Listen for new connections.
[/COLOR][/SIZE][SIZE=2]Listener = [/SIZE][SIZE=2][COLOR=#0000ff]New[/COLOR][/SIZE][SIZE=2] TcpListener(System.Net.IPAddress.Any, PORT_NUM)
Listener.Start()
 
[/SIZE][SIZE=2][COLOR=#0000ff]Do[/COLOR][/SIZE]
[SIZE=2][COLOR=#008000]' Create a new user connection using TcpClient returned by
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' TcpListener.AcceptTcpClient()[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] Client [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]New[/COLOR][/SIZE][SIZE=2] UserConnection(Listener.AcceptTcpClient)[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#008000]' Create an event handler to allow the UserConnection to communicate
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' with the window.[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]AddHandler[/COLOR][/SIZE][SIZE=2] Client.LineReceived, [/SIZE][SIZE=2][COLOR=#0000ff]AddressOf[/COLOR][/SIZE][SIZE=2] OnLineReceived[/SIZE]
[SIZE=2]
UpdateStatus([/SIZE][SIZE=2][COLOR=#800000]"New connection found: waiting for log-in"[/COLOR][/SIZE][SIZE=2])
[/SIZE][SIZE=2][COLOR=#0000ff]Loop[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Until[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]False
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Catch
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Try
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' Concatenate all the client names and send them to the user who requested user list
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] ListUser([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection)[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] Client [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection
[/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] Entry [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] DictionaryEntry
[/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] strUserList [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2]UpdateStatus([/SIZE][SIZE=2][COLOR=#800000]"Sending "[/COLOR][/SIZE][SIZE=2] & Sender.Name & [/SIZE][SIZE=2][COLOR=#800000]" and all connected clients"[/COLOR][/SIZE][SIZE=2] & _
[/SIZE][SIZE=2][COLOR=#800000]" a list of users online."[/COLOR][/SIZE][SIZE=2])
strUserList = [/SIZE][SIZE=2][COLOR=#800000]"LISTUSERS"[/COLOR][/SIZE]
[SIZE=2][COLOR=#800000]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' All entries in the clients Hashtable are UserConnection so it is possible
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' to assign it safely.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]For[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Each[/COLOR][/SIZE][SIZE=2] Entry [/SIZE][SIZE=2][COLOR=#0000ff]In[/COLOR][/SIZE][SIZE=2] Clients
Client = [/SIZE][SIZE=2][COLOR=#0000ff]CType[/COLOR][/SIZE][SIZE=2](Entry.Value, UserConnection)
strUserList = strUserList & [/SIZE][SIZE=2][COLOR=#800000]"|"[/COLOR][/SIZE][SIZE=2] & Client.Name & [/SIZE][SIZE=2][COLOR=#800000]"|"[/COLOR][/SIZE][SIZE=2] & Client.HostName _
& [/SIZE][SIZE=2][COLOR=#800000]"|"[/COLOR][/SIZE][SIZE=2] & Client.IPAddress
[/SIZE][SIZE=2][COLOR=#0000ff]Next[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]For[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Each[/COLOR][/SIZE][SIZE=2] Entry [/SIZE][SIZE=2][COLOR=#0000ff]In[/COLOR][/SIZE][SIZE=2] Clients
Client = [/SIZE][SIZE=2][COLOR=#0000ff]CType[/COLOR][/SIZE][SIZE=2](Entry.Value, UserConnection)
Client.SendData(strUserList)
[/SIZE][SIZE=2][COLOR=#0000ff]Next
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' Send the list to the sender.
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]'ReplyToSender(strUserList, Sender)
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' This is the event handler for the UserConnection when it receives a full line.
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' Parse the cammand and parameters and take appropriate action.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] OnLineReceived([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection, [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Data [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2])[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] DataArray() [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' Message parts are divided by "|" Break the string into an array accordingly.[/COLOR][/SIZE]
[SIZE=2]DataArray = Data.Split(Chr(124))[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#008000]' dataArray(0) is the command.[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]Select[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Case[/COLOR][/SIZE][SIZE=2] DataArray(0)
[/SIZE][SIZE=2][COLOR=#0000ff]Case[/COLOR][/SIZE][SIZE=2][COLOR=#800000]"CONNECT"
[/COLOR][/SIZE][SIZE=2]ConnectUser(DataArray(1), DataArray(2), DataArray(3), Sender)
[/SIZE][SIZE=2][COLOR=#0000ff]Case[/COLOR][/SIZE][SIZE=2][COLOR=#800000]"CHAT"
[/COLOR][/SIZE][SIZE=2]SendChat(DataArray(1), Sender)
[/SIZE][SIZE=2][COLOR=#0000ff]Case[/COLOR][/SIZE][SIZE=2][COLOR=#800000]"FILEINFO"
[/COLOR][/SIZE][SIZE=2]SendFileInfo(DataArray(1), DataArray(2), DataArray(3), Sender)
[/SIZE][SIZE=2][COLOR=#0000ff]Case[/COLOR][/SIZE][SIZE=2][COLOR=#800000]"REPLY"
[/COLOR][/SIZE][SIZE=2]SendFileInfoReply(DataArray(1), DataArray(2), Sender)
[/SIZE][SIZE=2][COLOR=#0000ff]Case[/COLOR][/SIZE][SIZE=2][COLOR=#800000]"PRV"
[/COLOR][/SIZE][SIZE=2]SendPrivateChat(DataArray(1), DataArray(2), Sender)
[/SIZE][SIZE=2][COLOR=#0000ff]Case[/COLOR][/SIZE][SIZE=2][COLOR=#800000]"DISCONNECT"
[/COLOR][/SIZE][SIZE=2]DisconnectUser(Sender)
ListUser(Sender)
[/SIZE][SIZE=2][COLOR=#0000ff]Case[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Else
[/COLOR][/SIZE][SIZE=2]UpdateStatus([/SIZE][SIZE=2][COLOR=#800000]"unknown message:"[/COLOR][/SIZE][SIZE=2] & Data)
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Select
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' This subroutine sends a response to the sender.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] ReplyToSender([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] strMessage [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2], [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection)
Sender.SendData(strMessage)
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' Send a chat message to all clients except sender.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] SendChat([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Message [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2], [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection)
UpdateStatus(Sender.Name & [/SIZE][SIZE=2][COLOR=#800000]": "[/COLOR][/SIZE][SIZE=2] & Message)
SendToClients([/SIZE][SIZE=2][COLOR=#800000]"CHAT|"[/COLOR][/SIZE][SIZE=2] & Sender.Name & [/SIZE][SIZE=2][COLOR=#800000]": "[/COLOR][/SIZE][SIZE=2] & Message, Sender)
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] SendPrivateChat([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Message [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2], [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] strClient [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2], _
[/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection)[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] Client [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection
[/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] Entry [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] DictionaryEntry[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]For[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Each[/COLOR][/SIZE][SIZE=2] Entry [/SIZE][SIZE=2][COLOR=#0000ff]In[/COLOR][/SIZE][SIZE=2] Clients
Client = [/SIZE][SIZE=2][COLOR=#0000ff]CType[/COLOR][/SIZE][SIZE=2](Entry.Value, UserConnection)
[/SIZE][SIZE=2][COLOR=#0000ff]If[/COLOR][/SIZE][SIZE=2] strClient = Client.Name [/SIZE][SIZE=2][COLOR=#0000ff]Then
[/COLOR][/SIZE][SIZE=2]Client.SendData([/SIZE][SIZE=2][COLOR=#800000]"PRV|"[/COLOR][/SIZE][SIZE=2] & Message & [/SIZE][SIZE=2][COLOR=#800000]"|"[/COLOR][/SIZE][SIZE=2] & Sender.Name)
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]If
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Next
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] SendFileInfo([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] strClient [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2], [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] FileName [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2], _
[/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] FileSize [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2], [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection)[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] Client [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection
[/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] Entry [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] DictionaryEntry[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]Try[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' All entries in the clients Hashtable are UserConnection so it is possible
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' to assign it safely.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]For[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Each[/COLOR][/SIZE][SIZE=2] Entry [/SIZE][SIZE=2][COLOR=#0000ff]In[/COLOR][/SIZE][SIZE=2] Clients
Client = [/SIZE][SIZE=2][COLOR=#0000ff]CType[/COLOR][/SIZE][SIZE=2](Entry.Value, UserConnection)
[/SIZE][SIZE=2][COLOR=#0000ff]If[/COLOR][/SIZE][SIZE=2] strClient = Client.IPAddress [/SIZE][SIZE=2][COLOR=#0000ff]Then
[/COLOR][/SIZE][SIZE=2]Client.SendData([/SIZE][SIZE=2][COLOR=#800000]"FILEINFO|"[/COLOR][/SIZE][SIZE=2] & Sender.Name & [/SIZE][SIZE=2][COLOR=#800000]"|"[/COLOR][/SIZE][SIZE=2] _
& FileName & [/SIZE][SIZE=2][COLOR=#800000]"|"[/COLOR][/SIZE][SIZE=2] & FileSize)
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]If
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Next[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Catch[/COLOR][/SIZE][SIZE=2] ex [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] Exception
ReplyToSender(ex.ToString, Sender)
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Try
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] SendFileInfoReply([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] strClient [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2], [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Answer [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2], _
[/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection)[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] Client [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection
[/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] Entry [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] DictionaryEntry[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]For[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Each[/COLOR][/SIZE][SIZE=2] Entry [/SIZE][SIZE=2][COLOR=#0000ff]In[/COLOR][/SIZE][SIZE=2] Clients
Client = [/SIZE][SIZE=2][COLOR=#0000ff]CType[/COLOR][/SIZE][SIZE=2](Entry.Value, UserConnection)
[/SIZE][SIZE=2][COLOR=#0000ff]If[/COLOR][/SIZE][SIZE=2] strClient = Client.Name [/SIZE][SIZE=2][COLOR=#0000ff]Then
[/COLOR][/SIZE][SIZE=2]Client.SendData([/SIZE][SIZE=2][COLOR=#800000]"REPLY|"[/COLOR][/SIZE][SIZE=2] & Answer & [/SIZE][SIZE=2][COLOR=#800000]"|"[/COLOR][/SIZE][SIZE=2] & Sender.Name)
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]If
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Next
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' This subroutine sends a message to all attached clients except the sender.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] SendToClients([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] strMessage [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2], [/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] Sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection)[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] Client [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] UserConnection
[/SIZE][SIZE=2][COLOR=#0000ff]Dim[/COLOR][/SIZE][SIZE=2] Entry [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] DictionaryEntry[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#008000]' All entries in the clients Hashtable are UserConnection so it is possible
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' to assign it safely.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]For[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Each[/COLOR][/SIZE][SIZE=2] Entry [/SIZE][SIZE=2][COLOR=#0000ff]In[/COLOR][/SIZE][SIZE=2] Clients
Client = [/SIZE][SIZE=2][COLOR=#0000ff]CType[/COLOR][/SIZE][SIZE=2](Entry.Value, UserConnection)
[/SIZE][SIZE=2][COLOR=#008000]' Exclude the sender.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]If[/COLOR][/SIZE][SIZE=2] Client.Name <> Sender.Name [/SIZE][SIZE=2][COLOR=#0000ff]Then
[/COLOR][/SIZE][SIZE=2]Client.SendData(strMessage)
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]If
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Next
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' This subroutine adds line to the Status listbox
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] UpdateStatus([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] StatusMessage [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]String[/COLOR][/SIZE][SIZE=2])
lstStatus.Items.Add(StatusMessage)
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]
[/COLOR][/SIZE][SIZE=2][COLOR=#008000]' When the window closes, stop the listener.
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Private[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub[/COLOR][/SIZE][SIZE=2] MZW3_Server_Closing([/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] sender [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Object[/COLOR][/SIZE][SIZE=2], _
[/SIZE][SIZE=2][COLOR=#0000ff]ByVal[/COLOR][/SIZE][SIZE=2] e [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2] System.ComponentModel.CancelEventArgs) [/SIZE][SIZE=2][COLOR=#0000ff]Handles[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]MyBase[/COLOR][/SIZE][SIZE=2].Closing[/SIZE]
[SIZE=2]
Listener.Stop()
[/SIZE][SIZE=2][COLOR=#0000ff]Me[/COLOR][/SIZE][SIZE=2].Dispose()[/SIZE]
[SIZE=2]
[/SIZE][SIZE=2][COLOR=#0000ff]End[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Sub
[/COLOR][/SIZE][/SIZE]

The original code can be found in Microsoft’s 101 VB.NET examples. I modified it of course to fit my program.
 
kurt69, it would not work as intended, because if you look beyond the stupid 'dim i as integer (=0!!!), while i<>0 .... helloooo' there is no asynchronous methods used, in which case there can only be 1 concurrent connection. Also AcceptTcpClient is a blocking call which waits until a connection will accept on your first port 11001, so the loop will not continue before 11001 is connected finished and disconnected. etc.

You can put the method in a class and spawn a number of threads for maximum concurrents. Attached example.

For multiple clients I would still say it is better to implement as port manager on the server and use only one service port for clients to connect and get assigned the port for their communication use. This will allow any number of clients and still only one thread/port needed for listening.
 

Attachments

  • vbnet20-multiconnect.zip
    32.6 KB · Views: 167
Thanks for that Master Zero, I'll probably be using both yours and JohnH's examples. And JohnH, I get what you mean, thanks heaps. I actually already have a port manager, the user connects to this it sends back either a no ports available message or the first available port which they then connect to. As you can see I just had to make these ports which I obviously didn't know how too.

Thanks again.
 
Yeah I just went to open your example project JohnH but it's for .net2.0 and i've only got vs.net 2003.

So I was just wandering if i could do this: I create a class which has my networking code in it, then make an array of that class, have each member on a separate thread?
 
Yes, that's what I did, too. You could also get started with the free Visual Basic 2005 Express http://msdn.microsoft.com/vstudio/express/vb/ You might as well give it a quick spin before the Orcas and .Net 3.0 is unleashed..

Also the code in my example is very small and should be compatible with .Net 1, so you could just grab it outta the vb files.
 
As much as I'd love to get started with express, I've heard the migration wizard sucks and I don't want to create more work for myself, so I'll just stick to 2003 pro for now.

I'll most likely come back to this thread later I'm too busy to work on the project at all atm, Ill make sure I have a look at your project in notepad or something though. Thanks.
 
This is a lot of help, I'm trying to start a Client Server Application myself.
I'll look into the code and start implementing it.
 
AHH!!!! Excellent. This is actually the first.....

Hey John
You are the first person to actually give an EASY explenation on how to handle client/server techniques using threads. I have scanned forums and forums, the MSDN etc. etc. witout finding anything that could explain exactly how this works. Your code is so small that its self explaining.

ITS ABOUT TIME!!!

Anyway - I have one Q for you though:
To me it looks like your using different ports for every connection. This is of course an easy way out of the mess, but it is a pain in the ass when it comes to firewall and routing. Just imagine the messy Firewall configuration to make sure this will work. And we all know that using the same port is working perfectly, just like any WEB servers do.
So - is there an easy way to add support for this in your example code?

Regards
Ole Morten Heien
HD Software
Norway
 
Welcome to forums Norwegian! :) The example is actually quite bad, but serves as some basic view of connecting and communication.

Easy way? No and yes. No because the server listener must allow incomming call, firewall must be open at the listening port. Yes, well, it's the trick of any messaging service - how do they do it? Maybe the site linked here have some answers? http://www.hypothetic.org/docs/msn/index.php A shorter overview here: http://www.msblog.org/2006/12/27/who-wants-to-know-how-windows-live-messenger-actually-works/

The essential is that multiple clients can connect and stay connected to same server port simultaneously as long as server handles everything asynchronous and manages the collection of client sockets. If you use some port monitor software and MSN Messenger you will see that you are connected to some server at port 1863 at all times, this is the designated MSN port which all communication is routed through. Behind that scene are servers that handles connection (may very well only reply telling the client to connect to another server), authentication, notifications and message forwarding.

Extending the example in post 3 (and limiting the server now to one port) is here attached another simple version that allows and handle multiple client connections at same port. Compile both projects, start up the server, start up 2 or more client instances (go to client debug folder and run another .exe) and connect them and send some text to see.
 

Attachments

  • vbnet20-multiconnect2.zip
    33.4 KB · Views: 83
excellent. Exactly what I want

I ended up with an exception here because I tryed to reconnect a client twice. At least I suspect thats the reason, but that situation wil never hapend in my application. Would you say that this sollution is usable in production? Because to me it looks ok

Also I can see some other differences between this code and the example presented on MSDN:

In your code you declare the Client class and the tcp is declared as tcpClient. MSDN declare it as Socket and assigns this using some really complex referential assignments. Is your code implicit the same as MSDN or do I have to take some actions around this?

Allso, it looks to me as your example uses Asynchronous socket connetions since your using BeginRead etc. Is that a correct observation?

Thanx alot for this help. You really made my day :-D

Ole
 
Client class is just my business object used to store whatever I needed. System.Net.Sockets.TcpClient and TcpListener classes are the common usable wrapper in framework for a client-server Socket. All Begin../End.. method pairs are asynchronous.

The architecture is usable in production if I get what you mean, the code needs some refinement for error situations, and of course you have to define the protocol used for communication between server-client so they can send different messages and be handled appropriately at both ends.
 
Excelent. What about reply?

The Server part are to be part of my server software. I'm creating a software that communicate with a hardware. I will accept connections from clients on the WAN/LAN. WAN side clients will connect through VPN. Sometime the server must send information back to the client. In the MSDN example they shows how the server side can reply directly to the client by simply using the Handler.BeginSend method. This is fine, but I a not sure how to respond to the client. My guess is of course using a SendCallback as described in the MSDN, but this must be reaced outside the class because the method is shared and some how outside the scope (i'm not sure I'm making myself clear here ;-) )

Ole
 
Once the server TcpListener has accepted the connection a TcpClient instance is returned. This is the same object as the clients connecting TcpClient. Now you see both ends is a TcpClient. What way data is sent doesn't matter. Client TcpClient can read/write and server TcpClient can read/write. What the client writes the server must read, what the server writes the client must read. This is what a "protocol" is all about, defining how the communication will take place.
 
yeah but...

I can se that I had a hard time explaining myself here :)

I'l give it a new try
As far as I understand, the "client" instance is added to the list of clients inside the Listens method. This list on the other hand, you have declared as Private. In the MSDN example its pubic. Allso, the instantiating of the main object is done in the form code itselfe like this:

dim l as new lobbyHost(port, me)

Because of this, the clients is unreachable from the application it selfe. Therfore, would it be correct to change the Clients list from private to Public? Would that make it accessable from the application? Allso, I see that you raise an event when the data is received from the client. Can I send messages from the application to the clients using the same approach? Or would it be easyer to just create a method for this?

Regards
Ole
 
Back
Top