The SocketTools .NET Edition includes the InternetServer class that was designed to simplify the implementation of a TCP/IP server application. With only a few lines of code, you can create the framework for a multi-threaded server that is completely event driven. You only need to create event handlers and then write your server code to respond to those network events.
Although the class makes developing server applications significantly easier, there are a few important things to keep in mind when using this class:
Although the code that you’ll write will look like typical single-threaded code that responds to events, it’s actually a multi-threaded application, with each client session isolated in its own thread. When network events are raised, they are being raised within the context of the thread that’s generated them. For example, this means that when the OnRead event fires, it’s not firing within the thread that created that instance of the InternetServer class (e.g.: the main UI thread). Any code that you have inside that event handler is executing in the context of the thread that’s managing that specific client session.
Because network events are being raised in the context of the client session threads, this means you cannot update user interface controls directly from within the event handlers. .NET does not allow one thread to modify a control that was created in another thread (usually the main UI thread). This is an absolute rule, and if you break it, your program is either going to hang or throw an exception. If you want to change any property values or call methods in a control, you need to create a delegate and marshal the call to the UI thread to using the Invoke method. Both the VB.NET and C# examples show how to do this; in the example, it’s used to update a ListBox control. Frequently updating the user interface from within an event handler can have a very negative impact on the overall performance of your program. Marshalling calls across threads to those UI controls is an expensive operation, and while you’re doing it, you’re not servicing network events like OnRead. You should keep it to a minimum, only updating the UI when absolutely necessary.
Another important consideration is that you should never modify shared objects without first synchronizing access to that object. In VB.NET it’s typically done using the SyncLock statement; in C# it’s done using the lock statement. The VB.NET and C# examples show to do this using a shared instance of a Hashtable class which is used to keep track of the number of bytes echoed by the server. While our lower-level API provides thread-safe queuing functions (InetServerEnqueue, InetServerDequeue, etc.) this doesn’t exist in the .NET class, largely because it would be redundant. If you want to use a queue, then you use the .NET Queue class, but the same rule applies; make sure that you synchronize access to the queue. While the InternetServer class does have Lock and Unlock methods, you should never use it for general purpose synchronization in your program. Calling the Lock method will cause every single client thread to block, and so while using Lock and Unlock would seem to be the easiest approach, it’s not a good idea for performance reasons.
The InternetServer class provides two general forms of many methods which are used to exchange data with the clients, ones which requires a handle to the client socket, and ones that do not. It’s not required that you specify a client handle if your code is inside an event handler (or in a function that’s being called by an event handler); the class knows which client raised the event and calls to Read and Write will work with that client (referred to as the “current client” in the documentation). However, if you want to exchange data with a client outside of an event handler, or with a client other than the current client, then you must specify a handle. Also, certain properties only return meaningful results from within an event handler. For example, the ClientAddress property (which returns the IP address of the client) should only be referenced from within an event handler such as OnConnect. The documentation notes when a property or method should only be used inside an event handler.
Finally, while it is possible to exchange data in an event handler with a client other than the one that generated the event, it’s not recommended. This is a case where you should use the Lock method to ensure that you’re the only thread that is attempting to read or write on that socket at the time. Two threads that attempt to read or write data on the same socket has undefined results; there’s no guarantee which thread would “get there first”, and the end result can be really unpredictable behavior by your program. One way to think about this is to think of your server program as a house, with each client connection being a room inside that house. You can do pretty much anything you want inside your own room (from within the event handler code that you write). However, if you want to go into another room and start rearranging the furniture, you need to make sure that every other thread is locked out while you do that. When you’re done and back in your own room, then you unlock the server and continue on.