7

I'm writing a server for a game, and I want to be able to handle thousands of concurrent users. For this reason, I went with non-blocking sockets and use the poll method. However, I do create multiple threads to handle database and web calls, and some of these threads will send a response to the user. In one of these threads, on send, I get the error "A non-blocking socket operation could not be completed immediately". What could cause this problem? I imagine it's because a poll is occurring at the same time as send is called. If I used beginAsync, would it take stop this error? I thought about locking the socket, but I don't want my main thread to be blocked for this.

M.Babcock
  • 18,753
  • 6
  • 54
  • 84
Nikhil
  • 1,121
  • 2
  • 11
  • 27

4 Answers4

6

I don't know what kind of non-blocking-polling socket calls are you using, but I would recommend that you use the Async socket calls (instead of the Begin). For more information on the difference between Async calls vs Begin see: What's the difference between BeginConnect and ConnectAsync?

The asynchronous calls automatically do "polling" on the OS level, which will be much more efficient than your polling. As a matter of fact, they use IO completion ports, which are probably the fastest and most efficient thing you can use on Windows to handle a large amount of client connections/requests.

As far as the error, I would consider this to be the normal operation of non-blocking sockets, so you just have to handle it gracefully.

Update

Your server should probably do something like this:

// Process the accept for the socket listener.
private void ProcessAccept(SocketAsyncEventArgs e)
{
    Socket s = e.AcceptSocket;
    if (s.Connected)
    {
        try
        {
            SocketAsyncEventArgs readEventArgs = this.readWritePool.Pop();
            if (readEventArgs != null)
            {
                // Get the socket for the accepted client connection and put it into the 
                // ReadEventArg object user token.
                readEventArgs.UserToken = new Token(s, this.bufferSize);

                Interlocked.Increment(ref this.numConnectedSockets);
                Console.WriteLine("Client connection accepted. 
            There are {0} clients connected to the server",
                    this.numConnectedSockets);

                if (!s.ReceiveAsync(readEventArgs))
                {
                    this.ProcessReceive(readEventArgs);
                }
            }
            else
            {
                Console.WriteLine("There are no more available sockets to allocate.");
            }
        }
        catch (SocketException ex)
        {
            Token token = e.UserToken as Token;
            Console.WriteLine("Error when processing data received from {0}:\r\n{1}", 
            token.Connection.RemoteEndPoint, ex.ToString());
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }

        // Accept the next connection request.
        this.StartAccept(e);
    }
}

Code sample courtesy of code project: http://www.codeproject.com/Articles/22918/How-To-Use-the-SocketAsyncEventArgs-Class

Community
  • 1
  • 1
Kiril
  • 39,672
  • 31
  • 167
  • 226
  • I think he's using the normal calls, but has set the socket set to non blocking mode. – CodesInChaos Mar 23 '12 at 18:16
  • @CodeInChaos ah, got, thanks! I would still recommend that the OP uses IOCP instead of trying to do the polling. No need to reinvent the wheel, especially since the `Socket` class already provides IOCP with the Async* family of calls. – Kiril Mar 23 '12 at 18:19
  • I use connectasync for the silverlight client. Do you have an example for the server? While I look into connectasync, do you have any recommendations on handling it gracefully? – Nikhil Mar 23 '12 at 19:28
  • @Nikhil I don't have an example of my own, but you can check this [codeproject example](http://www.codeproject.com/Articles/22918/How-To-Use-the-SocketAsyncEventArgs-Class). Handling it "gracefully" often means taking the right action to continue the safe operation of your application. I don't know what the safe operation of your application would look like, so I can't tell you what would be the graceful handling in your case. Some people ignore the error, some log it, others pop up a warning for the user to see... there are many ways to go about it. – Kiril Mar 23 '12 at 19:45
  • Thanks. Right now, I just put a try catch around it. The user misses an update, but if they refresh, they'll get what they're supposed to. Long term, I'll take a look at what you posted. – Nikhil Mar 23 '12 at 19:56
2

I was also receiving this exception on sending data and just found the solution.

You get the exception because the socket's send buffer is full. Because you are trying to send the data via a non-blocking send, the exception is raised to let you know that you MUST send it via a blocking send.

The data is not sent once the exception is raised, so you have to resend it. Your individual send call now becomes;

try
{
    m_socket.Send(buffer, bufferSize, SocketFlags.None);
}
catch (SocketException e)
{
    if(e.SocketErrorCode == WouldBlock)
    {
        m_socket.Blocking = true;
        m_socket.Send(buffer, bufferSize, SocketFlags.None);
        m_socket.Blocking = false;
    }
}

It would also be a good idea to increase the socket's SendBufferSize. By default I think it is 8kb. For my needs I had to increase it to 2MB, and afterwards the Send call no longer threw that exception.

2

When a non-blocking socket tries to read data but finds none you get that error: the socket would like to wait for data but can't because it has to return immediately, being non-blocking.

I'd suggest you switch to blocking sockets, find out why data is missing, adjust accordingly then revert to non-blocking ones. Or, you could handle the error and retry the operation.

Alex
  • 23,004
  • 4
  • 39
  • 73
  • The exception occurs when I attempt to write to the socket, not read. I don't get it on read because I call Poll to make sure something is there before I attempt to read. – Nikhil Mar 23 '12 at 19:21
0

This exception is too general. Per MSDN,

If you receive a SocketException, use the SocketException.ErrorCode property to obtain the specific error code. After you have obtained this code, refer to the Windows Sockets version 2 API error code documentation in the MSDN library for a detailed description of the error.

Sockets error codes are here.

Sherlock
  • 1,022
  • 8
  • 19