Code archives/Networking/TCP Socket Server
This code has been declared by its author to be Public Domain code.
Download source code
| |||||
TCPSocketServer I thought I'd throw my hat into the "TCPSocket Ring". This is an OOP approach to creating a TCPSocket server. Callbacks are used for extensibility. You can create functions to handle events and add whatever functionality you like. You can create many different servers listening to different ports, and assign different functionality to each server. The benefit of doing this is that each server is running withing the same process, and therefor causes less overhead drain on your system resources. The Event Callback handles are: NewConnectionCallback Triggered whenever a new client connects. The callback function takes one parameter of type TCPSocketConnection. Your callback function can access the various fields and methods of the connection. A link back to the connection's server is also provided to give you access to the various fields and methods of the server. LostConnectionCallback Triggered whenever a client disconnects from the server. This callback also takes a TCPSocketConnection. After the server calls the callback function it removes the connection from it's collection. MsgRcvdCallback Triggered whenever a client sends a message to the server. The callback function takes one parameter of type TCPSocketConnection. Your callback function can access the message in the connection object's "Buffer" field. The field is a string, but can easily be converted to an array of bytes for whatever processing you need to do. This function is where you would add any "Intelligence" to your server, ie, processing commands, etc. Methods Broadcast(Message:String, [conn:TCPSocketConnection = Null]) If a TCPSocketConnection is provided the server only broadcasts the message to that specific connection. Otherwise the server will broadcast the message to all of it's connections. Listen() Updates the server connections and triggers events as they occure. TCPSocketConnections A TCPSocketConnection is the "pipeline" through which the server communicates with the clients that are connected. Each server object has it's own collection of TCPSocketConnection objects. Methods Recieves messages from your program and tells the server to trigger the MsgRcvdCallback event. This works in a "Non-Blocking" fashion, so as not to hold up your application while waiting for input. You're program causes the server to trigger this event by sending a "Stop Sequence". The server defaults this stop sequence to a Carriange Return/Line Feed {~r~n}. But you can change it to whatever you want by setting the server's "StopSeq" field. | |||||
Rem Title: TCPSocket Server Author: Jim Pishlo (CoderLaureate) (J9T@Jimmy9Toes.com) End Rem Strict Type TCPSocketServer 'Properties Field MyPort:Int = 3849 Field MySocket:TSocket Field MyStream:TSocketStream Field Connections:TList = New TList Field StopSeq:String = "~r~n" 'Callback handles Field NewConnectionCallback(Conn:TCPSocketConnection) Field MsgRcvdCallback(Conn:TCPSocketConnection) Field LostConnectionCallback(Conn:TCPSocketConnection) 'Constructor (for lack of a better word) Function Create:TCPSocketServer(_Port:Int = 3849) Local s:TCPSocketServer = New TCPSocketServer s.MyPort = _Port s.MySocket = CreateTCPSocket() s.MyStream = CreateSocketStream(s.MySocket) s.MySocket.Bind(_Port) s.MySocket.Listen(0) Return s End Function 'The main method. 'Monitors connections and triggers events. Method Listen() Local newConn:TSocket = MySocket.Accept(0) If newConn <> Null Then Local conn:TCPSocketConnection = TCPSocketConnection.Create(Self,newConn) 'Add the new connection to the collection Connections.AddLast(conn) 'If a callback has been assigned call it. If NewConnectionCallback <> Null Then NewConnectionCallback(conn) 'Pass a refernce to the new connection End If End If 'Clean up closed connections For Local c:TCPSocketConnection = EachIn Connections If Not c.MySocket.Connected() Then If LostConnectionCallback <> Null Then LostConnectionCallback(c) End If Connections.Remove(c) 'Remove connection from collection End If Next 'Receive Data from connections 'Trigger callback if neccesary For Local c:TCPSocketConnection = EachIn Connections If c.MySocket.Connected() Then Local t:String = c.Receive() If t <> "" Then If MsgRcvdCallback <> Null Then MsgRcvdCallback(c) c.Buffer = "" End If End If End If Next End Method Method Broadcast(Message:String, Conn:TCPSocketConnection = Null) If Conn <> Null Then Conn.Send(Message) Else Local c:TCPSocketConnection For c = EachIn Connections If c.MySocket.Connected() Then c.Send(Message) End If Next End If End Method End Type Type TCPSocketConnection Field MyID:String Field MyServer:TCPSocketServer Field MySocket:TSocket Field MyStream:TSocketStream Field Buffer:String = "" Field StopSeq:String Function Create:TCPSocketConnection(s:TCPSocketServer, NewSocket:TSocket) Local c:TCPSocketConnection = New TCPSocketConnection c.MyServer = s c.MySocket = NewSocket c.MyStream = CreateSocketStream(c.MySocket) c.StopSeq = c.MyServer.StopSeq c.MyID = DottedIP(c.MySocket.RemoteIP()) Return c End Function Method Send(Text:String) MySTream.WriteString(Text) End Method Method Receive:String() Local nBytes:Int = MySocket.ReadAvail() Local s:String = StopSeq If nBytes Then Local in:String = ReadString(MyStream,nBytes) Buffer:+ in If Buffer.Length >= s.Length And Right$(Buffer,s.Length) = s Then Local t:String = Buffer.Replace(s,"") 'Strip out the stop sequence Return t End If End If End Method End Type Function CreateTCPSocketServer:TCPSocketServer(_Port:Int = 3849) Return TCPSocketServer.Create(_Port) End Function 'Test Code: This is a simple Telnet Chat Server. Run this program, then ' open up a command prompt and type: ' ' Telnet localhost 3849 ' ' You can telnet into this server from anywhere in the world ' and do *very basic* text chatting. To log in from another ' computer, type: ' ' Telnet {host computer ip} 3849 ' ' This is just a sample program to show you what you can do ' with the TCPSocketServer object. '---------------------------------------------------------------------- 'Create callback functions for interaction with the TCPSocketServer Object. 'Function to handle data recieved by server '------------------------------------------ Function TextHandler(C:TCPSocketConnection) C.MyServer.BroadCast(C.MyID + ": " + C.Buffer) End Function 'Greet new users and assign an ID '-------------------------------- Function Greet(C:TCPSocketConnection) C.MyID = "User [" + MilliSecs() + "]" C.MyServer.BroadCast("Welcome!~r~n",C) C.MyServer.BroadCast(C.MyID + " has entered the room.~r~n") End Function 'Alert other users when a user leaves '------------------------------------ Function LostConnection(C:TCPSocketConnection) C.MyServer.BroadCast("~r~n" + C.MyID + " has left the room.~r~n~r~n") End Function 'Create an Instance of the TCPSocketServer Class '------------------------------------------------- Global Server:TCPSocketServer = CreateTCPSocketServer() 'Assign Function Pointers to Server's callback handles '----------------------------------------------------- Server.MsgRcvdCallback = TextHandler Server.NewConnectionCallback = Greet Server.LostConnectionCallback = LostConnection 'The Main Loop '-------------- While Not KeyHit(KEY_ESCAPE) Server.Listen() 'That's it! Wend |
Comments
| ||
Very nice example :) I wish I had found it sooner though , as I spent the last two nights figuring out how to do exactly what you did here :) |
| ||
Sorry double post |
| ||
Thanks for the comment! |
| ||
Is this true multi-threading? |
| ||
Here's an optimisation: In the Listen method you iterate through the connections twice. Once to see if the users are connected and once to get the messages. Here's some pseudo code to speed it up a bit. If Not user.Connected() userList.Remove(user) Else user.CheckForMsg Endif That way you only go through the connections once. |
| ||
Nice. I've written an HTTP server in BlitzMax, and was precisely going to add some kind of productor/consumer pattern. More precisely I wanted to implement the Proactor design pattern and reimplement the server with. I may as well use your code. I have some critics though: your code is not correct in that you just read as much data as there is until now (ReadString) and pass that to the callback. But what if the client sends 2 small chunks of data in a row? You're likely to receive it as a whole. This ends up in the handler to get 2 messages in one single call. Worse, you may receive the fist part plus half of the second part, and never detect the terminating CR+LF. Or more simply you may also receive half of the first message. Same problem. Also you never give any explicit way of deciding when to stop reading and return the result to the handler (MsgRcvdCallback), it is always the CRLF delimitor that is used. I propose a fairly simple modification: Add a method to explicitely register interest in a read event. You should be able to specify how to determine enough data has been read. I personnaly see good uses in the two following criterias: - a specified sequence of bytes has been read (example: CR+LF when reading HTTP header fields) - a specified amount of bytes has been read, in which case return no more than that amount of data to the handler (example : reading the body of an http POST request). Finally, add a "timeout" parameter (and a dedicated handler callback) to stop the wait after a given amount of time has passed without getting all the expected data (and pass to the timeout callback the data read until now). These parameters should be set by connection, not by server. This would make the whole pretty much perfect. Though I'd make one final modification: store the read data as raw bytes (and provide a method returning the same data as a string). This would improve efficiency for the cases where the ahndler will act on raw bytes anyway (removes 2 unneeded conversions). |
| ||
Is this true multi-threading? Unfortunately no, but CoderLaureate did a nice job of making it work so as to seem that way, since all you have to call is the Listen() method. |
| ||
Finally I implemented the corrections I mentioned: There are certainly still some enhancements to do, but it's already more flexible, and it works (well I think, it's not like I tested it extensively). EDIT(21/03/2007): updated code to correct some bugs that real usage only could easily reveal. |
| ||
Wow! It looks like you've taken my little program and really vamped it up! Excellent Job. Do you mind if I use your new code in a project of mine? |
| ||
Absolutely not, go on. |
| ||
nice code! i was wondering wheter there maybe an update for the things todo that are still open in this one. is there an update? thanks! |
Code Archives Forum