15

With respect to C# and .NET's System.Net.Sockets.Socket.AcceptAsync method, one would be required to handle a return value of "false" in order to handle the immediately available SocketAsyncEventArgs state from the synchronously processed connection. Microsoft provides examples (found on the System.Net.Sockets.SocketAsyncEventArgs class page) which will cause a stack overflow if there are a large amount of pending connections, which can be exploited on any system that implements their handling model.

Other ideas for getting around this issue are to make a loop that calls the handler method, with the condition being that the value Socket.AcceptAsync returns is equal to false, and to break the loop (to allow deferred processing) if the value is indicating that the operation is being completed asynchronously (true). However, this solution also causes a stack overflow vulnerability because of the fact that the callback associated with the SocketAsyncEventArgs passed to Socket.AcceptAsync has at the end of the method, a call to Socket.AcceptAsync, which also has a loop for immediately available, synchronously accepted, connections.

As you can see, this is a pretty solid problem, and I've yet to find a good solution that does not involve System.Threading.ThreadPool and creating tons of other methods and scheduling processing. As far as I can see, the asynchronous socket model relating to Socket.AcceptAsync requires more than what is demonstrated in the examples on MSDN.

Does anyone have a clean and efficient solution to handling immediately pending connections that are accepted synchronously from Socket.AcceptAsync without going into creating separate threads to handle the connections and without utilizing recursion?

Michael J. Gray
  • 9,784
  • 6
  • 38
  • 67
  • Yeah, the examples on MSDN are not always the best (or best practice, for that matter). Can you clarify your question? Are you looking for a way to work with asyc sockets that will not cause StackOverflowExceptoin? – Oded Oct 17 '10 at 18:43
  • That is correct; I am looking for a non-recursive way to handle immediately pending connections. – Michael J. Gray Oct 17 '10 at 18:44
  • See also http://stackoverflow.com/questions/2002143/asynchronous-sockets-handling-false-socket-acceptasync-values – Lucero Oct 17 '10 at 19:44

5 Answers5

8

I wouldn't use AcceptAsync, but rather BeginAccept/EndAccept, and implement the common async pattern correctly, that is, checking for CompletedSynchronously to avoid callbacks in the callback thread on operations which completed .

See also AsyncCallBack CompletedSynchronously


Edit regarding the requirement to use AcceptAsync:

The MSDN documentation explicitly says that the callback will NOT be invoked for operations which completed synchronously. This is different to the common async pattern where the callback is always invoked.

Returns true if the I/O operation is pending. The SocketAsyncEventArgs.Completed event on the e parameter will be raised upon completion of the operation. Returns false if the I/O operation completed synchronously. The SocketAsyncEventArgs.Completed event on the e parameter will not be raised and the e object passed as a parameter may be examined immediately after the method call returns to retrieve the result of the operation.

I currently don't see how a loop would not solve the stack overflow issue. Maybe you can be more specific on the code that causes the problem?


Edit 2: I'm thinking of code like this (only in regard to AcceptAsync, the rest was just to get a working app to try it out with):

static void Main(string[] args) {
    Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    listenSocket.Bind(new IPEndPoint(IPAddress.Loopback, 4444));
    listenSocket.Listen(100);
    SocketAsyncEventArgs e = new SocketAsyncEventArgs();
    e.Completed += AcceptCallback;
    if (!listenSocket.AcceptAsync(e)) {
        AcceptCallback(listenSocket, e);
    }
    Console.ReadKey(true);
}

private static void AcceptCallback(object sender, SocketAsyncEventArgs e) {
    Socket listenSocket = (Socket)sender;
    do {
        try {
            Socket newSocket = e.AcceptSocket;
            Debug.Assert(newSocket != null);
            // do your magic here with the new socket
            newSocket.Send(Encoding.ASCII.GetBytes("Hello socket!"));
            newSocket.Disconnect(false);
            newSocket.Close();
        } catch {
            // handle any exceptions here;
        } finally {
            e.AcceptSocket = null; // to enable reuse
        }
    } while (!listenSocket.AcceptAsync(e));
}
Community
  • 1
  • 1
Lucero
  • 59,176
  • 9
  • 122
  • 152
  • Unfortunately BeginAccept/EndAccept don't fit my needs, this is because of the requirement to instantiate the IAsyncResult objects for each operation. My server requires a high volume of operations, and profiling revealed that this was stressful point in the application. And even then, the provided solution defeats the purpose of synchronous completion by invoking the callback on a different thread, or having some weird system of keeping accept calls up to speed, from a separate thread. The other response someone made is essentially identical to yours. – Michael J. Gray Oct 17 '10 at 18:58
  • In response to your edit, the problem comes up in the case of handling the immediately available SocketAsyncEventArgs by calling the AcceptAsync handler from the same thread, for example you check if the return value is false, and then simply call the handler with the argument of the SocketAsyncEventArgs which was used in Socket.AcceptAsync. And then, inside the accept handler, you use that same pattern at the end to ensure that all connections (synchronous or asynchronous) are handled properly. – Michael J. Gray Oct 17 '10 at 19:15
  • In response to your second edit with the code sample, that's exactly what I had in mind when I posted my answer to my question. Thanks for posting the code, because some people may not have understood what I was trying to get across textually. I believe it was your response that triggered my thought about how to solve the issue in the first place; thanks again. Although I must say that mixing synchronous and asynchronous socket work is obnoxious, heh heh. But hey, it gets the idea across. – Michael J. Gray Oct 17 '10 at 19:46
  • Thanks for accepting the answer. Of course putting sync code in an async handler is not how to deal with things, but that's why I explicitly explained that this is just to test out the `AcceptAsync` code (you can actually paste that into a console app main class and it will run). ;-) – Lucero Oct 17 '10 at 20:04
3

I have resolved this problem by simply changing the placement of the loop. Instead of recursively calling the accept handler from within itself, wrapping the code in a do-while loop with the condition being "!Socket.AcceptAsync(args)" prevents a stack overflow.

The reasoning behind this is that you utilize the callback thread for processing the connections which are immediately available, before bothering to asynchronously wait for other connections to come across. It's re-using a pooled thread, effectively.

I appreciate the responses but for some reason none of them clicked with me and didn't really resolve the issue. However, it seems something in there triggered my mind into coming up with that idea. It avoids manually working with the ThreadPool class and doesn't use recursion.

Of course, if someone has a better solution or even an alternative, I'd be happy to hear it.

Michael J. Gray
  • 9,784
  • 6
  • 38
  • 67
  • Sounds pretty much like what I have been hacking together while you wrote that comment. That's why I asked how a loop would not solve the issue, since that callback is not called while you're looping there is no recursion... – Lucero Oct 17 '10 at 19:33
  • @Michael I realize this is almost 4 years old, but I'm currently tackling the same issue and I couldn't really understand exactly what you did. Do you happen to remember? – Rotem Jun 17 '14 at 07:17
  • @Rotem Yeah, I do! Basically I was originally calling `AcceptAsync` from within the `Completed` event callback and in the event that it completed synchronously I was just invoking the same event callback. This will cause a stack overflow if many calls are completed synchronously, as is common under frequent loads. Instead of doing it this way, I just made a loop like: `while (!acceptor.AcceptAsync(state)) { /* do stuff */ }` – Michael J. Gray Jun 18 '14 at 19:09
1

I haven't looked carefully, but it smells like this might be helpful (see the section called "stack dive"):

http://blogs.msdn.com/b/mjm/archive/2005/05/04/414793.aspx

Brian
  • 117,631
  • 17
  • 236
  • 300
  • We're not talking about Windows Communication Foundation. Although this is a similar issue, it's rather irrelevant. It also looks as if they are simply defeating the purpose of having actions complete synchronously, by starting the handler on a separate thread. In turn, having it on a separate thread can just cause a stack overflow on the handler thread. – Michael J. Gray Oct 17 '10 at 18:47
  • To be clear, the part I called out isn't just about WCF, it's about writing any async loop with the begin-end pattern. – Brian Oct 17 '10 at 18:57
  • 1
    I re-read it a few times and did check into it, and then clarified my comment. My apologies if it came off as dismissive. It's just not what I'm looking for, and I appreciate your input. – Michael J. Gray Oct 17 '10 at 18:59
1
newSocket.Send(Encoding.ASCII.GetBytes("Hello socket!")); 
newSocket.Disconnect(false); 
newSocket.Close(); 

The problem with this snippet above is that this will block your next accept operation.

A better way is like this:

while (true)
{
   if (e.SocketError == SocketError.Success)
   {
      //ReadEventArg object user token
      SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop();
      Socket socket = ((AsyncUserToken)readEventArgs.UserToken).Socket = e.AcceptSocket;

      if (!socket.ReceiveAsync(readEventArgs))
         ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessReceiveEx), readEventArgs); .
    }
    else
    {
       HadleBadAccept(e);
    }

    e.AcceptSocket = null;

    m_maxNumberAcceptedClients.WaitOne();
    if (listenSocket.AcceptAsync(e))
       break;
}
takrl
  • 6,356
  • 3
  • 60
  • 69
simsure
  • 21
  • 1
  • This is a comment for my 2 year old downvote. I downvoted this because it makes no sense for the context of the question. I didn't post a code example, so it seems like you're replying to an answer to this question and not the question itself. This should have been a two sentence comment on the accepted answer. – Michael J. Gray May 27 '14 at 20:56
0

The SocketTaskExtensions contains useful method overloads for the Socket class. Rather than using the AsyncCallback pattern, the AcceptAsync extension method can be called with ease. It is also compatible with the task asynchronous programming (TAP) model.

There is two basic operation to consider:

  1. Start the listening: As usual socket needs to Bind to a specific IP address and port. Then place the socket in listening state (Listen method). After that it is ready to handle the incoming communication.

  2. Stop the listening: It stops accepting the incoming requests.

    bool _isListening = false;
    
    public Task<bool> StartListening()
    {
        Socket listeningSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
        listeningSocket.Bind(new IPEndPoint(IPAddress.Any, 0));
        listeningSocket.Listen(10);
    
        return HandleRequests(listeningSocket);
    }
    
    public void StopListening()
    {
        _isListening = false;
    }
    

In order to handle incoming requests, the listening socket accepts (AcceptAsync) the incoming client connection. Then Send or Receive message from the accepted socket. It accepts incoming connection until StopListening was called.

internal async Task<bool> HandleRequests(Socket listeningSocket)
{
    try
    {
        _isListening = true;
        while (_isListening)
        {
            byte[] message = Encoding.UTF8.GetBytes("Message");
            byte[] receivedMessage = new byte[1024];
            using (Socket acceptedSocket = await listeningSocket.AcceptAsync())
            {
                // Send messages
                acceptedSocket.Send(message);

                // Receive messagges
                acceptedSocket.Receive(receivedMessage);
            }
        }
    }
    catch (SocketException)
    {
        // Handle error during communication.
        return false;
    }

    return true;
}

Note:

  • Messages could be exceed the buffer size. In that case try continuously receive until end of the data. Stephen Clearly message framing blog post is good starting point.
  • Sending and receiving also could be asynchronous. NetworkStream can be created from the accepted socket then we can await to the ReadAsnyc and WriteAsync operations.
Péter Szilvási
  • 362
  • 4
  • 17