Jump to content

A simple TCP server for multiple connections


Zellpop
 Share

Recommended Posts

I've searched the great Internet for a TCP server that handles multiple connections. And everyone uses a separate thread for each connection. After some trial and errors I think I've found a solution that uses two threads, where the second handles the communication.

 

Some information before I start. Have been using VB.Net since its my «native» language smile.png For C# user it should be fairly easy to follow. When it comes to other languages I hope it will nudge you in the right direction. The .Net version is 4.5, but I think its more or less the same down to 2.0

Since VB.Net doesn't use a C language style/naming, I will try to translate some of the words to the C style syntax.

 

Module = Static Class

Sub = A routine/method that doesn't return a value

Function = A routine/method that return a value

Dim = A word used to declare a variable (Dim MyString as String)

ReDim = resize an array

New = The constructor key word (Dim MyCar as New Car)

Dictionary(of Key, Value) = Strongly typed HashTable

Queue(of Value) = Strongly typed Queue

Imports = Using/Include...

Namespace = I think that is the same all over the place

CInt = Convert to Int32

CUInt = Convert to UInt32

Don't be alarmed about _Class naming. Its my way of naming Classes

 

Here Is my code:

First we make a class that holds all information about the connected user/client.

 

Imports System.Net
Imports System.Net.Sockets
Namespace Network
Public Class Client_Class
Public MySocket As Socket
Public SocketID As UInt32
End Class
End Namespace

 

 

MySocket holds all information about the connection, and SocketID is used by my/your code to find the «right» connection to send/receive bytes.

 

The server need a way to store the incoming/outgoing bytes along with the ID of Sender/Receiver. So you'll need this message class

 

Namespace Network
Public Class Message_Class
Private _ID As UInt32
Private _Message() As Byte
Public Sub New(ID As UInt32, Message() As Byte)
 _ID = ID
 _Message = Message
End Sub
Public ReadOnly Property ID As UInt32
 Get
 Return _ID
 End Get
End Property
Public ReadOnly Property Message As Byte()
 Get
 Return _Message
 End Get
End Property
End Class
End Namespace

 

As you can see the key word End is all over the place. It represent the end curly braces }

This class has two fields, two properties and a constructor

 

That was easy, now the hard part. Doing section by section I will try to explain what is going on.

 

 

Imports System.Threading
Imports System.Net
Imports System.Net.Sockets
Namespace Network
Public Module ConnectionMGR
Private _ConnectionMGRThread As New Thread(AddressOf ConnectionLoop)
Private _ListOfConnections As New Dictionary(Of UInt32, Client_Class)
Private _SendQueue As New Queue(Of Message_Class)
Private _serverIP As IPAddress = IPAddress.Parse("127.0.0.1")
Private _ServerPort As UInt16 = 5000
Private _ServerConnection As TcpListener
Private _SocketID As UInt32 = 0

 

_ConnectionMGRThread is the thread that handles all the TCP communication, and starts in the Sub ConnectionLoop

_ListOfConnections stores all connections as Key Value pair in a Dictionary (strongly typed Hastable)

_SendQueue holds all messages that is going to be sent as Message_Class we made earlier.

_ServerConnection is the mother of all the Sockets used.

_SocketID is the unique value so we can find the correct client(will increase for each connection)

 

 

Public Sub Start()
 _ConnectionMGRThread.Start()
End Sub
Public Sub Send(Message As Message_Class)
 _SendQueue.Enqueue(Message)
End Sub
Private Function NewSocketID() As UInt32
 _SocketID += CUInt(1)
 Return _SocketID
End Function

 

Start starts the communication thread

Send puts the message in the send queue. This should be thread safe since only one thread Peeks/Dequeues the queue

NewSocketID returns +1 each time

 

Private Sub NewConnections()
 Dim Client As New Client_Class
 Client.SocketID = NewSocketID()
 Client.MySocket = _ServerConnection.AcceptSocket
 _ListOfConnections.Add(Client.SocketID, Client)
End Sub

 

NewConnections is called every time a new connection is about to be established. Then we create a new Client instance of the Client_Class. Then we set the public fields .SocketID and .MySocket. Then we add it to _ListOfConnections

 

 

Private Sub ConnectionLoop()
 Console.WriteLine("Communication Loop is listening")
 _ServerConnection = New TcpListener(_serverIP, _ServerPort)
 _ServerConnection.Start()
 Dim ReceiveBuffer() As Byte
 Dim ReceivedBytes As Integer
 Do While True
 If _ServerConnection.Pending() Then
	 NewConnections()
 End If
 For Each Client As Client_Class In _ListOfConnections.Values
	 If Client.MySocket.Available > 0 Then
	 ReDim ReceiveBuffer(Client.MySocket.Available - 1)
	 ReceivedBytes = Client.MySocket.Receive(ReceiveBuffer, ReceiveBuffer.Length, 0)
	 Dim NewMessage As New Message_Class(Client.SocketID, ReceiveBuffer)
	 Crypto.DeCryptMGR.DeCrypt(NewMessage)
	 End If
 Next
 Do While _SendQueue.Count > 0
	 Dim SendClient As Client_Class
	 Dim SendMessage As Message_Class
	 SendMessage = _SendQueue.Dequeue()
	 SendClient = _ListOfConnections.Item(SendMessage.ID)
	 SendClient.MySocket.Send(SendMessage.Message, 0, SendMessage.Message.Length, SocketFlags.None)
 Loop
 Thread.Sleep(1)
 Loop
End Sub
End Module

End Namespace

 

When the thread is started, it begins in this Sub. All handling of any TCP/sockets is done by this thread. Hopefully it will keep it thread safe.

ReciveBuffer is the array of bytes form incoming connections is stored.

RecivedBytes is not in use here, but stores the amount of bytes recived.

Now we enter the Loop that will never stop since True is always True.

 

The first IF statement checks if someone is trying to connect

 

For Each iterates through every client in the ListOfConnections to check if they have any incoming messages. If there is, resize the ReceiveBuffer to fit the new message. Create a new Message_Class and send it where you want. The code sends it to Crypto.....

 

Do While checks if the _SendQueue contains any messages to send. If it does finds the correct Client and sends it of and does this until the _SendQueue.Count = 0. Then we Sleep for 1 millisecond and starts all over again.

 

As you might have noticed, there are no error checking here. I was going for a minimalistic code example. But remember Murphy's law «everything that can go wrong will go wrong»

 

This example doesn't take into account that connections might be slow/faulty/disconnected or otherwise broken.

 

So to start everything up

 

Module StartupSequence
Sub Main()
Network.ConnectionMGR.Start()
Dim Message As Network.Message_Class
Dim clientID As UInt32
Do While True
 clientID = CUInt(Console.ReadLine())
 Message = New Network.Message_Class(clientID, {97, 95, 96, 93, 91, 100})
 Network.ConnectionMGR.Send(Message)
Loop
End Sub
End Module

 

Sub Main() is where everything starts. The first client connected gets ID 1 next 2 and so on, in the console window you can write the ID you want to send to, and the client will receive the Array of Bytes {97, 95, 96, 93, 91, 100}

 

Hope you will find it useful and create your own version in different languages. And share them with the non VB.Net users smile.png

 

Theoretically this server could have 4.3 bill connections, but somehow I think its gonna meet the max long before that ( memory, network buffers, time for each iterations)

 

Zellpop

  • Upvote 1
Link to comment
Share on other sites

  • 4 years later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...