Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Async TCP Server overkill?

This is really an implementation question so I feel it's best to start with my specific case.

I've got a C# server that listens for TCP connections asynchronously from mobile clients. When a mobile client connects a new thread is started, the client sends a little (<100 bytes usually) text message and receives one of similar size back. After the server responds, it closes the connection and ends the thread.

Current basic usage is a user logs in, checks on stuff for sometimes up to 5 minutes, sending little messages and thus creating new threads on the server in rapid succession, and they disconnect only to reconnect a few hours later. Also, every user has their own server they run on their PC, and as such most servers will only ever have one client connected at any given time, in RARE cases two.

Right now I'm running into the following error, An existing connection was forcibly closed by the remote host, and it has got me thinking, am I doing this wrong?

So my question(s):

  1. Is my current setup appropriate here?
  2. If so, should I be ending the thread after a little message is sent or keeping it alive and closing after a given period of idle?
  3. On the off chance that I'm doing everything correctly, highly unlikely, should I be avoiding the error by simply retrying a few times before giving up?
  4. Fourth and finally, that error completely kills the server (the server is spawned by another process and any untrapped exception kills it), if we've made it this far, and my implementation is OK, how can I avoid that?

EDIT:

In response to some of the questions here:

  • The exception is occurring before I receive all the data, but only in instances where a user has sent multiple messages in quick succession.
  • From what I recall max backlog is 5 unless a user is running Windows Server, however I have not set mine and I don't know what the default is, I'll try explicitly setting it to 5.

Async Server Code:

    public void StartListening()
    {
        //Data buffer for incoming data.
        byte[] bytes = new Byte[1024];

        //Establish the local endpoint for the socket.
        IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
        IPAddress ipAddress = ipHostInfo.AddressList[0];
        IPEndPoint localEndPoint = new IPEndPoint(ipAddress, Port);

        //Create a TCP/IP socket.
        Socket listener = new Socket(AddressFamily.InterNetwork,
            SocketType.Stream, ProtocolType.Tcp);
        listener.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.DontLinger,1);
        listener.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.ReuseAddress,1);

        //Bind the socket to the local endpoint and listen for
        //incoming connections.
        try
        {
            listener.Bind(localEndPoint);
            listener.Listen(100);

            while (listening)
            {
                //Set the event to nonsignaled state.
                allDone.Reset();    

                //Start an asychronous socket to listen for connections.
                Print("Waiting for a connection...");
                listener.BeginAccept(
                new AsyncCallback(AcceptCallback),
                    listener);

                //Wait until a connection is made before continuing.
                allDone.WaitOne();
            }
        }
        catch (Exception e)
        {
            Print(e.ToString());    
        }

        listener.Close();
    }

    public void AcceptCallback(IAsyncResult arg)
    {
        //Signal the main thread to continue.
        allDone.Set();


        try
        {
            //Get the socket that handles the client request.
            Socket listener = (Socket) arg.AsyncState;
            Socket handler = listener.EndAccept(arg);


            //Create the state object.
            StateObject state = new StateObject();
            state.workSocket = handler;

            handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                new AsyncCallback(ReadCallback), state);
        }
        catch (ObjectDisposedException ex)
        {
            Print("Server terminated from another thread.");    
        }
    }

    public void ReadCallback(IAsyncResult arg)
    {
        String content = String.Empty;

        //Retrieve the state object and the handler socket
        //from the asynchronous state object.
        StateObject state = (StateObject) arg.AsyncState;
        Socket handler = state.workSocket;

        //Read data from the client socket.
        int bytesRead = 0;
        try 
        {
            bytesRead = handler.EndReceive(arg);
        }
        catch (ObjectDisposedException ex)
        {
            Print("Process was terminated from another thread.");   
        }

        if (bytesRead > 0)
        {
            //There might be more data, so store the data received so far.
            state.sb.Append(Encoding.ASCII.GetString(
                state.buffer,0,bytesRead));

            //Check for end-of-file tag. If it is not there, read
            //more data.
            content = state.sb.ToString();
            if (content.IndexOf("<EOF>") > -1)
            {
                content = content.Remove(content.Length-6);
                //All the data has been read from the
                //client. Display it on the console.
                Print("Read " + content.Length + " bytes from socket. \n Data : " + content);
                Respond(handler, content);
            }
            else
            {
                //Not all data received. Get more.
                handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                    new AsyncCallback(ReadCallback), state);
            }
        }
    }

    private void Send(Socket handler, String data)
    {
        //Convert the string data to byte data using ASCII encoding.
        byte[] byteData = Encoding.ASCII.GetBytes(data);

        //Begin sending the data to the remote device.
        handler.BeginSend(byteData,0,byteData.Length,0,
            new AsyncCallback(SendCallback),handler);
    }

    private void SendCallback(IAsyncResult arg)
    {
        try
        {
            //Retrieve the socket from the state object.
            Socket handler = (Socket) arg.AsyncState;

            //Complete sending the data to the remote device.
            int bytesSent = handler.EndSend(arg);
            Print("Sent " + bytesSent + " bytes to client.");

            handler.Shutdown(SocketShutdown.Both);
            //need to make this not linger around
            handler.LingerState = new LingerOption(true,1);
            handler.Close();
        }
        catch (Exception e)
        {
            Print(e.ToString());    
        }
    }
like image 917
zkwentz Avatar asked Nov 06 '22 03:11

zkwentz


2 Answers

Ideally, you'd be using the .NET threadpool, which would be much more efficient than creating a new thread for every connection. Can you please share your exact "async" code - if you're using the existing async pattern on TCPListener then you're probably already using the threadpool.

With respect to the exception, that's what you'd expect to see when your clients disconnect from the server. Is it occurring before you manage to receive all the data? Are you flushing your socket on the client side?

In terms of completely crashing the server, just keep testing, and log any globally unhandled exceptions. That way you'll learn about everything that can be expected.

like image 180
Rob Fonseca-Ensor Avatar answered Nov 09 '22 04:11

Rob Fonseca-Ensor


You might want to have a look at this article, which has a good list of several things to check. For example, what is your backlog set to when you .Listen() on your socket?

like image 34
mpontillo Avatar answered Nov 09 '22 05:11

mpontillo