Use C to write an asynchronous Socket server

  • 2021-07-06 11:37:45
  • OfStack

Introduction

I recently needed to prepare an internal threaded communication mechanism for a. net project consisting of multiple servers and clients using ASP. NET, Windows forms and console applications. Considering the possibility of implementation, I was determined to use the native socket instead of many. NET components that have been built for us in advance, such as the so-called pipeline, NetTcpClient and Azure service bus.

The server in this article is based on the System. Net. Sockets class asynchronous methods. These allow you to support a large number of socket clients, and one client connection is only one blocking mechanism. The blocking time is negligible, so the server is basically operating as a multithreaded socket server.

Background

Native socket has the advantage of providing you with complete control over the communication level, while it has great flexibility in dealing with different data types. You can even send serialized CLR objects over socket, although I won't do that here. This project will show you how to send text between socket.
Application of code

With the following code, you initialize an Server class and run the Start () method:


Server myServer = new Server();
myServer.Start();

If you plan to manage the server in an Windows form, I recommend using an BackgroundWorker, because the socket method (1 would normally be ManualResentEvent) will block the GUI thread.

Server class:


using System.Net.Sockets;
 
public class Server
{
  private static Socket listener;
  public static ManualResetEvent allDone = new ManualResetEvent(false);
  public const int _bufferSize = 1024;
  public const int _port = 50000;
  public static bool _isRunning = true;
 
  class StateObject
  {
    public Socket workSocket = null;
    public byte[] buffer = new byte[bufferSize];
    public StringBuilder sb = new StringBuilder();
  }
 
  // Returns the string between str1 and str2
  static string Between(string str, string str1, string str2)
  {
    int i1 = 0, i2 = 0;
    string rtn = "";
 
    i1 = str.IndexOf(str1, StringComparison.InvariantCultureIgnoreCase);
    if (i1 > -1)
    {
      i2 = str.IndexOf(str2, i1 + 1, StringComparison.InvariantCultureIgnoreCase);
      if (i2 > -1)
      {
        rtn = str.Substring(i1 + str1.Length, i2 - i1 - str1.Length);
      }
    }
    return rtn;
  }
 
  // Checks if the socket is connected
  static bool IsSocketConnected(Socket s)
  {
    return !((s.Poll(1000, SelectMode.SelectRead) && (s.Available == 0)) || !s.Connected);
  }
 
  // Insert all the other methods here.
}


ManualResetEvent is an NET class that implements the events in your socket server. We need this project to signal the code when we want to issue blocking operations. You can use bufferSize in Experiment 1 to fit your needs. If you can anticipate the size of the message, Use byte units to set the message size parameter bufferSize. port is the port parameter for listening to TCP. Be aware of the interface used for servo for other applications. If you want to be able to stop the server easily, You need to implement a mechanism to set _ isRunning to false. This can be done by using an BackgroundWorker, where you can use myWorker. CancellationPending instead of _ isRunning. The reason I mentioned _ isRunning is to give you a direction in dealing with cancel operations and show you that listeners can easily stop.

Between () and IsSocketConnected () are auxiliary methods.


Now turn to the methods. The first is the Start () method:


public void Start()
{
  IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
  IPEndPoint localEP = new IPEndPoint(IPAddress.Any, _port);
  listener = new Socket(localEP.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  listener.Bind(localEP);
 
  while (_IsRunning)
  {
    allDone.Reset();
    listener.Listen(10);
    listener.BeginAccept(new AsyncCallback(acceptCallback), listener);
    bool isRequest = allDone.WaitOne(new TimeSpan(12, 0, 0)); // Blocks for 12 hours
 
    if (!isRequest)
    {
      allDone.Set();
      // Do some work here every 12 hours
    }
  }
  listener.Close();
}

This method initializes the listener socket and starts waiting for the user connection to arrive. The main mode in the project is to use asynchronous delegation. Asynchronous delegation is a method that is called asynchronously when the state in the caller changes. isRequest tells you whether WaitOne has exited because of a client connection or a timeout.

If you have a large number of client connections occurring at the same time, consider increasing the queue parameters of the Listen () method.


Now let's look at the next method, acceptCallback. This method is called asynchronously by listener. BeginAccept. When the method finishes executing, the listener immediately listens for the new client.


static void acceptCallback(IAsyncResult ar)
{
  // Get the listener that handles the client request.
  Socket listener = (Socket)ar.AsyncState;
 
  if (listener != null)
  {
    Socket handler = listener.EndAccept(ar);
 
    // Signal main thread to continue
    allDone.Set();
 
    // Create state
    StateObject state = new StateObject();
    state.workSocket = handler;
    handler.BeginReceive(state.buffer, 0, _bufferSize, 0, new AsyncCallback(readCallback), state);
  }
}

acceptCallback will derive another asynchronous assignment: readCallback. This method will read the actual data from socket. I have my own control over sending and receiving data, which is unchanged for _ bufferSize. All strings sent to the server must be < !--SOCKET-- > And < !--ENDSOCKET-- > Likewise, when the client receives the response from the server, it must unwrap the response message, which is < !--RESPONSE-- > And < !--ENDRESPONSE-- > Wrap it up.


static void readCallback(IAsyncResult ar)
{
  StateObject state = (StateObject)ar.AsyncState;
  Socket handler = state.workSocket;
 
  if (!IsSocketConnected(handler)) 
  {
    handler.Close();
    return;
  }
 
  int read = handler.EndReceive(ar);
 
  // Data was read from the client socket.
  if (read > 0)
  {
    state.sb.Append(Encoding.UTF8.GetString(state.buffer, 0, read));
 
    if (state.sb.ToString().Contains("<!--ENDSOCKET-->"))
    {
      string toSend = "";
      string cmd = ts.Strings.Between(state.sb.ToString(), "<!--SOCKET-->", "<!--ENDSOCKET-->");
           
      switch (cmd)
      {
        case "Hi!":
          toSend = "How are you?";
          break;
        case "Milky Way?":
          toSend = "No I am not.";
          break;
      }
 
      toSend = "<!--RESPONSE-->" + toSend + "<!--ENDRESPONSE-->";
 
      byte[] bytesToSend = Encoding.UTF8.GetBytes(toSend);
      handler.BeginSend(bytesToSend, 0, bytesToSend.Length, SocketFlags.None
        , new AsyncCallback(sendCallback), state);
    }
    else 
    {
      handler.BeginReceive(state.buffer, 0, _bufferSize, 0
          , new AsyncCallback(readCallback), state);
    }
  }
  else
  {
      handler.Close();
  }
}

readCallback will derive another method, sendCallback, which will send a request to the client. If the client does not close the connection, sendCallback will send a signal to socket for more data.


static void sendCallback(IAsyncResult ar)
{
  StateObject state = (StateObject)ar.AsyncState;
  Socket handler = state.workSocket;
  handler.EndSend(ar);
 
  StateObject newstate = new StateObject();
  newstate.workSocket = handler;
  handler.BeginReceive(newstate.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(readCallback), newstate);
}

I will leave writing an socket client as a link to the reader. The socket client should use the same programming mode as asynchronous invocation. I hope you will enjoy this article and put it into practice like an socket programmer!

Key points

I used this code in a production environment where the socket server is a free text search engine. SQL Server lacks support for free text search (you can use free text indexes, but they are slow and expensive). The socket server is loaded with a large amount of text data directed to IEnumerables and uses Linq to search for text. The response from the socket server searches millions of lines of Unicode text data in milliseconds. We also used three distributed Sphinx servers (www. sphinxsearch. com). The socket server acts as the cache for the Sphinx server. If you need a fast free text search engine, I strongly recommend using Sphinx.


Related articles: