Chinese in Canada

Knowledge Management and Collaboration Platform for chinese who are living in Canada.
Welcome to Chinese in Canada Sign in | Join | Help
in
Home Blogs Forums Photos Files Roller

Generic TCP/IP Client/Server

Last post 11-04-2009, 9:02 AM by Johnz. 1 replies.
Sort Posts: Previous Next
  •  11-04-2009, 8:38 AM 12290

    Generic TCP/IP Client/Server

    Screenshot - designview.jpg

    Introduction

    In this article, I hope to show a very useful architecture for building a network server over TCP/IP communication. I know there are a lot of TCP and UDP servers you can buy, and there are some more you can download for free, but the problem is that those you can buy usually gives you a "black box". They also cost money (the good ones cost a lot of money). Another problem is that they were designed to work on heavy and resourceful machines. The others you can download seem to go straight into a certain architecture which is usually incorrect, or at the least, they use an architecture which I believe is not the best we can use to milk a machine. One of my supervisors once told me that the enemy of the "good" is the "excellent". Well, obviously, he was correct! But, I still want the excellent, don't you feel the same?

    Well, enough chit-chat, let's examine the problem

    Sockets can use three modes for communicating:

    1. Synchronous communication - using the block mode which halts execution until something happens in the socket.
    2. Synchronous communication - without using the block mode - but, this means, we will get errors which we will have to catch.
    3. Asynchronous communication - which magically opens a thread per socket activity, thus not halting execution.

    And, of course, it is possible to mix synchronous calls with asynchronous calls if you seriously feel you don't have enough problems to sustain a tremendous amount of humor in your life.

    The first examination is why and when would we want to use asynchronous communication

    Since async calls open a thread for every socket operation, it is a great solution for client programs. It will manage the communication without halting other procedures, it is tested, and it is absolutely not suitable for servers. Why? The server using async calls will open a thread for every socket method, which will mean our server is going to end up having way too many threads than needed, and further, adding more threads at some point will make the entire list of other threads to run slower and slower until the hardware will require an upgrade. Now, that will happen no matter what logic we will implement; but, the question is when is it going to happen? The more threads we open, the sooner we need a hardware upgrade, so for now, we can determine that using async calls is out of the question if we want a low resource waste with maximal optimization in our server.

    So, our goal is to use the smallest number of threads as long as our Latency definition (a.k.a. Lag) is meeting our design requirements. Yes, I know some of you would say something like: "what are you talking about, most servers work with async communication!". They are correct - so if things are as I explained in the previous paragraph, why do so many servers use the async architecture? The answer is in an assumption that will not hold water, and in a non-elegant workaround that brutally forces things to work, as I will explain. Behind this architecture lays an assumption that clients keep communicating to the server, making the threads behind close and open all the time since an operation like receiving data or sending data is a short operation, and since it's short, it takes resources only for few nano-secs and then they are released, and so many threads can open and close without actually wasting resources in a constant way, which is actually very true (so far).

    However, if for example I connect to such a server from my client and I decide not to send data to that server, it would mean that that server has opened a BeginRecieve method that is waiting for my client to send something, and as long as I don't send anything, the thread that waits for data remains open, and does not close.. If we have 2000 clients doing that, the server will be in trouble, and the architect who designed it will be either unemployed, or possibly promoted to a division manager - that depends on where you work. To workaround this problem, the architecture requires a non-idle policy, so threads can close and malicious clients can not use that exploit. One of those policies can be a timer that closes idle clients, another can be based on a ping mechanism, and few would go for even smarter solutions that keep track of resources and disconnect idle clients only when resources are getting low. But, all those are workarounds which point out that they deal with the symptoms and not the problem that causes them! For the async server architecture, which as I explained, this is not suitable.

    Another approach would be to use sync calls in block mode to our sockets - this means that execution will halt, so to workaround this, we can open a thread per client. But again, we shall have many threads running in our system, and that is something we already killed as a server solution.

    The last option remaining is to use the sync calls without block mode. The problem is that 2000 clients connected to our machine will need to be scanned repeatedly, and that would take too long to scan.

    The remedy

    So, the remedy is to use a few threads to scan our clients - for example, every 100 clients will be scanned by a thread. However, we don't have the slightest idea regarding how long would it take to scan 100 clients in different machines. Another problem is that constraining to 100 is an arbitrary definition, which means we will not be dynamic.

    What I did at that point is create a class which I called ServerNode. The ServerNode holds clients, and return latency information of the scan (how long did it take to scan all the clients in the node). In the server base, I created a property which I named RequiredLatencyPerClient. From that point, things became much clearer.

    When the server clients list receives a new client, the server will scan all nodes and decide which node to add the new client to. If no node latency meets the RequiredLatencyPerClient, the server will create a new node and will add the new client to that node.

    Foreman Design Pattern - Is that a new design pattern ? (If not, I got a new and very generic idea instead - I will call it the "Wheel"!)

    Well, so far so good, but while writing the code, I couldn't help thinking that I somehow was doing something which is very generic in many situations. The idea that we have a client that needs to be organized in nodes in order to maintain a certain latency is something that can be useful in many situations. So, I decided it would be even better to take that work pattern out of the server and implement it independently so it can be used dynamically and in other situations as well.

    I seeked the books and the internet to see if there is a known design pattern that actually does that - and found that there isn't, so if no one objects, I am taking the right to call it the Foreman Design Pattern.

    But... there is something that bothers me. What if some clients need a different latency than others ? Hmm.. and what if I want to be able to change a client latency dynamically?

    So, scratching my head, I finally realized that a simple solution was staring at me all the time! The Foreman will not have a RequiredLatencyPerClient. Instead, each client, which from now on we shall call a worker (since we are tinkering with a design pattern and not just a communication server), will have a latency information, and the Foreman will put the worker in a suitable node according to its latency requirements.

    Based on the Foreman Design Pattern, I can build a very cool communication server.

    So, after building the Foreman, I began writing a simple code to wrap the socket communication options. I opened a project and called it NetworkSocketManager - it will be used in two other projects, a server and a client. The NetworkSocketManager will let us choose between a socketing work methodology and it will wrap all the synchronous and asynchronous communication options (including the block mode).

    Having done with that part, I coded a simple client that contains the NetworkSocketManager. The project is called NetworkClient.

    Now, the client itself is also very nice and friendly since it already implements a basic application level transmission control. Any data that is going to be sent is being wrapped in an envelope which contains: | prefix length = 1 digit | Prefix = length of data | data |. So, when receiving, the event for receiving a message is going to happen only when the full data accumulated through the socket. The situation for getting partial data can happen if I don't implement the application level transmission control. If, for example, the server out buffer is set to 1024 and the receive buffer is set to 256 - it would cause partial data coming or full data coming in 4 application level packets. But, as I said, this problem is already solved.

    If you wish to have encryption on your data, make sure to encrypt before you pass on the data to the send, and if you wish to manipulate your message before sending it (for example, if you need integration with another server), you will have to inherit the communication class and override the communication functions to your needs.

    The next task was to build a server that uses the already mentioned ForemanDP. So, I opened another project and called it NetworkServer. In it, I derived the ForemapDP worker class to CommunicationWorker class and the server class derives from Foreman BasicServer. And voila! If everything so far is clear, you will be able to understand my code and why it is so powerful.

    The fact that this is a powerful code implementation lies in the fact that all the programmer needs to do is derive a worker and a server to have an out of the box, running, and opened source client/server architecture which uses the correct resource methodology.

    Comparing to Classic IOCP

    As we can see, the model based on the foreman will "milk" the machine better since the definition of the required worker (client) latency has a meaning regarding who needs more focus from the machine. To be even more exact, the clients that require latency < 100 are treated in a different thread. This is what makes the difference from the classic IOCP, where all clients are treated in the same threadpool - there is a waste there since a client that requires a latency of 1000 (1 sec) will cause the other queued clients to hold until it has finished processing.

    However, ForemanDP knows how to deal with such a case: in the case a client latency is not met, a new thread (client's node) will be opened, and that client will be moved to it to make sure it can receive the latency it requires.

    Using the Code

    OK, so how do we get started?

    Step 1

    The first thing is to either add just references or the projects into your solution. For those who need to have servers, you will need the NetworkSocketManager, ForemanDP, and NetworkServer. For those who need to have clients, you will need the NetworkSocketManager and NetworkClient. For those who need both, just add it all...

    Step 2

    Server developers: In your class, write your code to contain the NetworkServerBase (or your class which derives it).

    Collapse Copy Code
    private NetworkServerBase MyServer = null;

    The next thing is to instantiate the server. You would want to put it in the Form_Load or a constructor of your class:

    Collapse Copy Code
    MyServer = new NetworkServerBase(new TimeSpan(1000));

    // This line will add a hook for events in your class
    MyServer.OnServerNotify +=
    new ForemanDP.BasicServer.DelegateServerNotification(MyServer_OnServerNotify);

    // Note that following line will not work becuase you need to replace the bolded area
    // with the ip you want to link.
    MyServer.AddListener(
    new NetworkServerClientListener(new TimeSpan(2000),
    new System.Net.IPEndPoint("replace this with the ip you want", 5500), 100));

    The last line is interesting because what it actually does is add a listener with a required latency of 2 seconds to the server.

    The NetworkServerClientListener is derived from NetworkClientWorker which derives from ForemanDP worker. The line of code sets port 5500 to be a listening port, and puts a 100 backlog space for queuing clients which need to connect. You can add more listeners - as many as you want! Run a loop on that line or just copy paste it, whatever you need.

    From that point forward, every communication event to that port is piped to MyServer_OnServerNotify:

    Collapse Copy Code
    void MyServer_OnServerNotify(ForemanDP.BasicWorker worker, object data)
    {
    switch (((NetworkServerClientWorker.NetworkClientNotification)data).EventType)
    {
    case NetworkClient.NetworkClientBase.ClientEventTypeEnum.Accepted:
    connectedUsers++;
    break;
    case NetworkClientBase.ClientEventTypeEnum.Disconnected:
    connectedUsers--;
    break;
    }
    }

    You can catch messages and anything else you need from that event.

    Clients: In your class, write your code to contain NetworkClient:

    Collapse Copy Code
    private NetworkClientBase clientCommunication = null;

    ---- your constuctor or Form_Load -----
    clientCommunication = new NetworkClientBase(
    new CommunicationSettings(
    CommunicationSettings.SocketOperationFlow.Asynchronic,
    CommunicationSettings.SocketOperationFlow.Asynchronic,
    CommunicationSettings.SocketOperationFlow.Asynchronic,
    CommunicationSettings.SocketOperationFlow.Asynchronic,
    CommunicationSettings.SocketOperationFlow.Asynchronic, false, 64));

    // and an event hooker!
    clientCommunication.OnClientEvent +=
    new NetworkClientBase.DelegateClientEventMethod(clientCommunication_OnClientEvent);
    :
    :
    :
    ---------------------------------------

    //and then ...

    private void clientCommunication_OnClientEvent(
    NetworkClientBase.ClientEventTypeEnum EventType, object EventData)
    {
    switch (EventType)
    {
    case NetworkClientBase.ClientEventTypeEnum.None:
    break;
    case NetworkClientBase.ClientEventTypeEnum.Accepted:
    break;
    case NetworkClientBase.ClientEventTypeEnum.Connected:
    break;
    case NetworkClientBase.ClientEventTypeEnum.RawDataRecieved:
    break;
    case NetworkClientBase.ClientEventTypeEnum.MessageRecieved:
    break;
    :
    :
    :
    and so on to catch what ever you need or want.

    Latest Version Additional Info

    As an example, I added a simple chat implementation. In order to run it:

    1. From Visual Studio, click the green triangle button (F5) - this will run the chat server.
    2. While the chat server is running, go back to Visual Studio and right click on the chat client project - a popup menu will appear. From the popup-menu, click Debug -> Start New Instance.

    You can repeat step 2 to create and run as many instances as you want.

    Note: The chat-client/server is just an example of how to use the entire architecture. It is not the focus of this article, and it is not intended to be bug-free.

    Benchmark Program Addition

    I have repaired much of the TCPSocket layer which was causing problems in certain situations. If more bugs arise, please let me know so I can fix them as well (in my machine, it works well).

    The benchmark opens a server and multiple async clients to communicate with the server. The server will echo each incoming packet back to the client who sent it (much like ping!).

    Since it was necessary to identify each async client, I created another client which contains a NetworkClientBase and adds identity to it. Normally, you will not need such a client unless you want to work with multiple clients without a server or a main node to manage them inside it.

    Bug fix (28.10.2008)

    Thanks to BeAsT[Ru] from this forum, a bug was found and repaired in a queue which is used inside the accepter to send data. An asymmetric handling was found in the queue and a critical section was implemented in the code. After several tests, I can conclude now that this bug is no more.

    Bug fix (20.12.2008)

    Thanks NapCrisis who fixed a minor bug in the chat example.

    Finally

    Most programmers believe that they have the best code that only God could match in perfectness. And, I am no different! But, in order to really learn something, we must put our ego very far away. So, please let me know what you think, and feel free to criticize.

    The connecter area in the server is not in use - I didn't finish it - but, unless you want a server that can initiate communication to other servers like a proxy, for example, it will not bother you. I promise to finish it if you CodeProject folks want it.

    Last thing: Some might ask why not use WCF instead of all this? WCF is a very advanced request-response architecture, and as progressed as it is, it will not give you "the always online in real time connection". Check this out: MSDN, if you don't believe me.


    http://www.codeproject.com/KB/IP/Generic_TCP_IP_server.aspx
  •  11-04-2009, 9:02 AM 12292 in reply to 12290

    Re: Generic TCP/IP Client/Server

    Synopsis

    nc [-46DdhklnrStUuvz] [-i interval] [-p source_port] [-s source_ip_address] [-T ToS] [-w timeout] [-X proxy_protocol] [

            -x proxy_address[                           :port]] [hostname] [port[s]]

    Description

    The nc (or netcat) utility is used for just about anything under the sun involving TCP or UDP. It can open TCP connections, send UDP packets, listen on arbitrary TCP and UDP ports, do port scanning, and deal with both IPv4 and IPv6. Unlike telnet(1), nc scripts nicely, and separates error messages onto standard error instead of sending them to standard output, as telnet(1) does with some.

    Common uses include:

                 * simple TCP proxies
    * shell-script based HTTP clients and servers
    *
    network daemon testing
    *
    a SOCKS or HTTP ProxyCommand for ssh(1)
    *
    and much, much more

    The options are as follows:

    -4' Forces nc to use IPv4 addresses only.

    -6' Forces nc to use IPv6 addresses only.

    -D' Enable debugging on the socket.

    -d' Do not attempt to read from stdin.

    -h' Prints out nc help.

    -i interval
    Specifies a delay time interval between lines of text sent and received. Also causes a delay time between connections to multiple ports.

    -k' Forces nc to stay listening for another connection after its current connection is completed. It is an error to use this option without the -l option.

    -l' Used to specify that nc should listen for an incoming connection rather than initiate a connection to a remote host. It is an error to use this option in conjunction with the -p, -s, or -z options. Additionally, any timeouts specified with the -w option are ignored.

    -n' Do not do any DNS or service lookups on any specified addresses, hostnames or ports.

    -p source_port
    Specifies the source port nc should use, subject to privilege restrictions and availability. It is an error to use this option in conjunction with the -l option.

    -r' Specifies that source and/or destination ports should be chosen randomly instead of sequentially within a range or in the order that the system assigns them.

    -S' Enables the RFC 2385 TCP MD5 signature option.

    -s source_ip_address
    Specifies the IP of the interface which is used to send the packets. It is an error to use this option in conjunction with the -l option.

    -T ToS
    Specifies IP Type of Service (ToS) for the connection. Valid values are the tokens ''lowdelay'', ''throughput'', ''reliability'', or an 8-bit hexadecimal value preceded by ''0x''.

    -t' Causes nc to send RFC 854 DON'T and WON'T responses to RFC 854 DO and WILL requests. This makes it possible to use nc to script telnet sessions.

    -U' Specifies to use Unix Domain Sockets.

    -u' Use UDP instead of the default option of TCP.

    -v' Have nc give more verbose output.

    -w timeout
    If a connection and stdin are idle for more than timeout seconds, then the connection is silently closed. The -w flag has no effect on the -l option, i.e. nc will listen forever for a connection, with or without the -w flag. The default is no timeout.

    -X proxy_version
    Requests that nc should use the specified protocol when talking to the proxy server. Supported protocols are ''4'' (SOCKS v.4), ''5'' (SOCKS v.5) and ''connect'' (HTTPS proxy). If the protocol is not specified, SOCKS version 5 is used.

    -x proxy_address[
    :port]
    Requests that nc should connect to hostname using a proxy at proxy_address and port. If port is not specified, the well-known port for the proxy protocol is used (1080 for SOCKS, 3128 for HTTPS).

    -z' Specifies that nc should just scan for listening daemons, without sending any data to them. It is an error to use this option in conjunction with the -l option.

    hostname can be a numerical IP address or a symbolic hostname (unless the -n option is given). In general, a hostname must be specified, unless the -l option is given (in which case the local host is used).

    port[s] can be single integers or ranges. Ranges are in the form nn-mm. In general, a destination port must be specified, unless the -U option is given (in which case a socket must be specified).

    CLIENT/SERVER MODEL

    It is quite simple to build a very basic client/server model using nc. On one console, start nc listening on a specific port for a connection. For example:

    $ nc -l 1234

    nc is now listening on port 1234 for a connection. On a second console (or a second machine), connect to the machine and port being listened on:

    $ nc 127.0.0.1 1234

    There should now be a connection between the ports. Anything typed at the second console will be concatenated to the first, and vice-versa. After the connection has been set up, nc does not really care which side is being used as a 'server' and which side is being used as a 'client'. The connection may be terminated using an EOF ('^D').

    Data Transfer

    The example in the previous section can be expanded to build a basic data transfer model. Any information input into one end of the connection will be output to the other end, and input and output can be easily captured in order to emulate file transfer.

    Start by using nc to listen on a specific port, with output captured into a file:

    $ nc -l 1234 > filename.out

    Using a second machine, connect to the listening nc process, feeding it the file which is to be transferred:

    $ nc host.example.com 1234 < filename.in

    After the file has been transferred, the connection will close automatically.

    Talking To Servers

    It is sometimes useful to talk to servers ''by hand'' rather than through a user interface. It can aid in troubleshooting, when it might be necessary to verify what data a server is sending in response to commands issued by the client. For example, to retrieve the home page of a web site:

    $ echo -n "GET / HTTP/1.0\r\n\r\n" | nc host.example.com 80
    Note that this also displays the headers sent by the web server. They can be filtered, using a tool such as sed(1), if necessary.

    More complicated examples can be built up when the user knows the format of requests required by the server. As another example, an email may be submitted to an SMTP server using:

    $ nc localhost 25 << EOF
    HELO host.example.com
    MAIL FROM: <user@host.example.com>
    RCPT TO: <user2@host.example.com>
    DATA
    Body of email.
    .
    QUIT
    EOF

    Port Scanning

    It may be useful to know which ports are open and running services on a target machine. The -z flag can be used to tell nc to report open ports, rather than initiate a connection. For example:

    $ nc -z host.example.com 20-30
    Connection to host.example.com 22 port [tcp/ssh] succeeded!
    Connection to host.example.com 25 port [tcp/smtp] succeeded!
    The port range was specified to limit the search to ports 20 - 30.

    Alternatively, it might be useful to know which server software is running, and which versions. This information is often contained within the greeting banners. In order to retrieve these, it is necessary to first make a connection, and then break the connection when the banner has been retrieved. This can be accomplished by specifying a small timeout with the -w flag, or perhaps by issuing a "QUIT" command to the server:

    $ echo "QUIT" | nc host.example.com 20-30
    SSH-1.99-OpenSSH_3.6.1p2
    Protocol mismatch.

    220 host.example.com IMS SMTP Receiver Version 0.84 Ready

    Examples

    google_protectAndRun("ads_core.google_render_ad", google_handleError, google_render_ad);Open a TCP connection to port 42 of host.example.com, using port 31337 as the source port, with a timeout of 5 seconds:
    $ nc -p 31337 -w 5 host.example.com 42

    Open a UDP connection to port 53 of host.example.com:

    $ nc -u host.example.com 53

    Open a TCP connection to port 42 of host.example.com using 10.1.2.3 as the IP for the local end of the connection:

    $ nc -s 10.1.2.3 host.example.com 42

    Create and listen on a Unix Domain Socket:

    $ nc -lU /var/tmp/dsocket

    Connect to port 42 of host.example.com via an HTTP proxy at 10.2.3.4, port 8080. This example could also be used by ssh(1); see the ProxyCommand directive in ssh_config(5) for more information.

    $ nc -x10.2.3.4:8080 -Xconnect host.example.com 42
    http://linux.die.net/man/1/nc
View as RSS news feed in XML
 
Powered by Community Server, by Telligent Systems