Monday 6 February 2012

Non-blocking Sockets (A Chat Program)

This is a tutorial to explain the use of nonblocking sockets. Non-blocking sockets are the opposite of blocking sockets, and non-blocking sockets do not stop code execution to await incoming data. Non- blocking sockets are more powerful than blocking sockets, but they are also more difficult. I have noticed an advantage to using the Socket class over the TcpListener or TcpClient classes. The TcpListener and TcpClient classes only allow one connection per port number. With the Socket class, you can have as many clients connected to a port as you would like.

This program uses primarily the Socket class. The class TcpSock was built around the Socket class to add ease and functionality.

To start the program you must initialize the Server socket. As you can see, the statement "Server.blocking = false" tells the socket not to block. After initializing the Server socket and binding it to an IP address and port number, the socket is ready to Listen. Now comes the main loop. The first thing we need to check for are incoming connections. Use the Poll method along with the SelectRead argument to do this. When this returns true, we know there is a client trying to connect. So the next thing to do is Accept the connection.

After we have accepted a connection, we can send and receive data. To send the data we simply use the Send method. Receiving data is the hard part. First, we again use the Poll method to check for data available to read. If it returns true, then we call the Recv method, which receives a chunk of data. Telnet does not echo the data unless specifically set to do so, so we will echo the data for telnet. Next we check to see if the person pressed enter... This is this critical part, because we need to know when they have entered a complete line of text, not just a few characters. Once we know they pressed enter, we can send the data to all the other clients using a for loop and the Send method. It's pretty straight- forward.

Lastly, we must close the socket if they disconnect. We already have the information we need. When we Polled the Socket, it returns the number of bytes received. If the number of bytes received = 0, then the Client closed his end of the connection. Now we close our end with the Shutdown and Close methods. Finally, we remove the instance of the TcpSock class from Client, our dynamic array.

This chat program is just a very simple example. If you were ambitious you could add names, commands, and adminstrators.

using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace TcpSock
{
class TcpSock
{
int    tcpIndx = 0;
int    tcpByte = 0;
byte[] tcpRecv = new byte[1024];

////////////////////////////////////////
public Socket tcpSock;
////////////////////////////////////////

public int Recv  (ref string tcpRead)
{
    tcpByte = tcpSock.Available;
    if (tcpByte > tcpRecv.Length - tcpIndx)
        tcpByte = tcpRecv.Length - tcpIndx;

    tcpByte = tcpSock.Receive(tcpRecv, tcpIndx, tcpByte, 
        SocketFlags.Partial);
    tcpRead = Encoding.ASCII.GetString
        (tcpRecv, tcpIndx, tcpByte);
    tcpIndx+= tcpByte;
    return    tcpRead.Length;
}

public int RecvLn(ref string tcpRead)
{
    tcpRead = Encoding.ASCII.GetString
        (tcpRecv, 0, tcpIndx);
    tcpIndx = 0;
    return    tcpRead.Length;
}

public int Send  (string tcpWrite)
{
    return tcpSock.Send(Encoding.ASCII.GetBytes(tcpWrite));
}

public int SendLn(string tcpWrite)
{
    return tcpSock.Send(Encoding.ASCII.GetBytes(tcpWrite + "\r\n"));
}
}


class Tcp
{
[STAThread]
static void Main()
{
    ////////////////////////////////////////////////////////////////////////////////////////////
    ///class IPHostEntry : Stores information about the Host and is required 
    ///for IPEndPoint.
    ///class IPEndPoint  : Stores information about the Host IP Address and 
    ///the Port number.
    ///class TcpSock     : Invokes the constructor and creates an instance.
    ///class ArrayList   : Stores a dynamic array of Client TcpSock objects.

    IPHostEntry Iphe   = Dns.Resolve   (Dns.GetHostName());
    IPEndPoint  Ipep   = new IPEndPoint(Iphe.AddressList[0], 4444);
    Socket      Server = new Socket    (Ipep.Address.AddressFamily, 
    SocketType.Stream, ProtocolType.Tcp);

    ////////////////////////////////////////////////////////////////////////////////////////////
    ///Initialize
    ///Capacity : Maximux number of clients able to connect.
    ///Blocking : Determines if the Server TcpSock will stop code execution 
    ///to receive data from the Client TcpSock.
    ///Bind     : Binds the Server TcpSock to the Host IP Address and the Port Number.
    ///Listen   : Begin listening to the Port; it is now ready to accept connections.

    ArrayList Client = new ArrayList ();
    string    rln    = null;

    Client.Capacity =   256;
    Server.Blocking = false;
    Server.Bind  (Ipep);
    Server.Listen( 32 );

    Console.WriteLine("{0}: listening to port {1}", Dns.GetHostName(), 
        Ipep.Port);

    ////////////////////////////////////////////////////////////////////////////////////////////
    ///Main loop
    ///1. Poll the Server TcpSock; if true then accept the new connection.
    ///2. Poll the Client TcpSock; if true then receive data from Clients.

    while (true)
    {
        //Accept
        if (Server.Poll(0, SelectMode.SelectRead))
        {
            int i = Client.Add(new TcpSock());
            ((TcpSock)Client[i]).tcpSock = Server.Accept ();
            ((TcpSock)Client[i]).SendLn("Welcome.");
            Console.WriteLine("Client {0} connected.", i  );
        }

        for (int i = 0; i < Client.Count; i++)
        {
            //check for incoming data
            if (((TcpSock)Client[i]).tcpSock.Poll(0, SelectMode.SelectRead))
            {
                //receive incoming data
                if (((TcpSock)Client[i]).Recv(ref rln) > 0)
                {
                    //echo incoming data
                    ((TcpSock)Client[i]).Send(rln);

                    //check for new line
                    if (rln == "\r\n")
                    {
                       ///receive line
                     ((TcpSock)Client[i]).RecvLn(ref rln);

                     //send the line to all clients
                     for (int y = 0; y < Client.Count; y++)
                        if (y != i)
                        ((TcpSock)Client[y]).Send(i.ToString() + ": " + rln);

                         Console.Write("{0}: {1}", i, rln);
                     }
                }
                    //recv returned <= 0; close the socket
                else
                {
                    ((TcpSock)Client[i]).tcpSock.Shutdown(SocketShutdown.Both);
                    ((TcpSock)Client[i]).tcpSock.Close();
                    Client.RemoveAt (i);
                    Console.WriteLine("Client {0} disconnected.", i);
                }
            }
        }
    }
}
}
}

No comments:

Post a Comment