4

I'm developing a Server application which will receive messages and respond. Nothing really new. So, actually I'm following this answer and this post, but I can't get the AcceptAsync() method to fire the Completed event. Searched everywhere on internet, tried similar problem's solutions, but nothing seems to work for me.

I've also tried to call server.Start() from a Task.Run(), but no luck. I know the server is listening fine because I can see it on netstat -an, I'm also able to connect using telnet if I break after Listen().

From what I've understood, if AcceptAsync() method returns true, it'll raise SocketAsyncEventArgs.Completed event which in turn will call again StartAccept() method and will loop until I force quit.

The SocketAsyncEventArgs.Completed is right also: http://prntscr.com/8l3x8p, but still doesn't work.

Here is my piece of code:

public class TTSServer
{
    private Socket m_serverSocket;
    private IPEndPoint m_serverEndPoint;

    [DefaultValue(39454)]
    public int Port { get; set; }
    [DefaultValue(100)]
    public int IncommingQueueSize { get; set; }
    [DefaultValue(512)]
    public int BufferSize { get; set; }

    //Listeners to hold event when something happens.

    public static void Main(string[] args)
    {
        TTSServer server = new TTSServer(39454, 100, 512);
        server.Start();
    }

    public TTSServer(int port, int queueSize, int bufferSize)
    {
        Port = port;
        IncommingQueueSize = queueSize;
        BufferSize = bufferSize;
    }


    public void Start()
    {
        Console.WriteLine("Starting TTS Server (Port: {0}, QueueSize: {1}, BufferSize: {2})", Port, IncommingQueueSize, BufferSize);
        m_serverEndPoint = new IPEndPoint(IPAddress.Any, Port);
        m_serverSocket = new Socket(m_serverEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        Console.WriteLine("Binding ({0})", m_serverEndPoint.ToString());
        m_serverSocket.Bind(m_serverEndPoint);
        Console.WriteLine("Listening");
        m_serverSocket.Listen(IncommingQueueSize);

        StartAccept(null);
    }

    public void Stop()
    {

    }

    /// <summary>
    /// Receive a incomming connection attemp in an asynchronous way.
    /// </summary>
    /// <param name="socketEvent">If is null, a new object is created, else, it'll be used for cache friendly reason.</param>
    private void StartAccept(SocketAsyncEventArgs socketEvent)
    {
        //If received reference points to null, let's create a new object.
        if (socketEvent == null)
        {
            Console.WriteLine("Accepting new connections...");
            socketEvent = new SocketAsyncEventArgs();
            socketEvent.Completed += AcceptCompleted; //Add a callback on completed accept incomming connections attemp.
        }
        else
        {
            //Clear current incomming connection socket, so object may be reused.
            socketEvent.AcceptSocket = null;
        }

        //If there is an incomming connection pending(pooled), this method will receive the connection in a async way (witch returns true) 
        //and will call SocketAsyncEventArgs.Completed callback when done. 
        //Else, it waits for a new connection, returns false, and don't won't SocketAsyncEventArgs.Completed callback when done, so we have to 
        //call it manually.
        bool async = true;

        //When I debug this code, async receive true from AcceptAsync.
        async = m_serverSocket.AcceptAsync(socketEvent);

        if (!async)
        {
            AcceptCompleted(this, socketEvent);
        }
    }

    /// <summary>
    /// Handles a incomming connection after it's fully received. This function will do the business logic for incomming connections and prepare
    /// to receive data.
    /// </summary>
    /// <param name="sender">Object who posted this function</param>
    /// <param name="socketEvent">Information of the incomming connection</param>
    private void AcceptCompleted(object sender, SocketAsyncEventArgs socketEvent)
    {
        Connection connection = new Connection(this, socketEvent.AcceptSocket, BufferSize);

        SocketAsyncEventArgs connectedSocketEvent = new SocketAsyncEventArgs();
        connectedSocketEvent.UserToken = connection;

        //Add a receive callback, to be called whenever the Receive function is finished.
        connectedSocketEvent.Completed += FlushBuffer;
        ReceiveData(connectedSocketEvent);

        //Accept next incomming connection.
        StartAccept(socketEvent);
    }

Don't know why, the AcceptCompleted is never fired, even when the AcceptAsync() method returns true.

Community
  • 1
  • 1
Afonso Lage
  • 281
  • 5
  • 18
  • This is not an answer but the question really is moot. You should develop a socket app by using an await wrapper around the APM pattern. Do not use socketeventargs at all. All of this is obsolete. Use a ready-made wrapper from the web. The problem will just go away. Also not that 99% of socket tutorials are broken or deeply misguided. – usr Sep 27 '15 at 18:11
  • Would you recommend me one? – Afonso Lage Sep 27 '15 at 19:00
  • You just need something that adds await to Socket. It's a few dozen lines of code. – usr Sep 27 '15 at 19:41
  • To emphasize the previous comment. It should be firing at least once when you telnet or any other connect if you can see it listening with netstat. But it only fires ONCE per connection. Try connecting with a second client at the same time to see what I mean. With this pattern it is the Connection object in the userToken that does the per connection per message events. – Sql Surfer Sep 27 '15 at 20:17
  • @SqlSurfer I think you misunderstood. It should fire regardless of connections. Even if there is no connection, it should fire `SocketAsyncEventArgs.Completed` due to IOCP completion pending or not. – Afonso Lage Sep 27 '15 at 20:41

2 Answers2

1

It seems the root cause is the default buffering:

Optionally, a buffer may be provided in which to receive the initial block of data on the socket after the ConnectAsync method succeeds. In this case, the SocketAsyncEventArgs.Buffer property needs to be set to the buffer containing the data to receive and the SocketAsyncEventArgs.Count property needs to be set to the maximum number of bytes of data to receive in the buffer. These properties can be set using the SocketAsyncEventArgs.SetBuffer method. Part of the buffer passed in will be consumed internally for use by the the underlying Winsock AcceptEx call. This means that the amount of data returned will always be less than the value of the SocketAsyncEventArgs.Count property on the System.Net.Sockets.SocketAsyncEventArgs instance provided. The amount of the buffer used internally varies based on the address family of the socket. The minimum buffer size required is 288 bytes. If a larger buffer size is specified, then the Socket will expect some extra data other than the address data received by the Winsock AcceptEx call and will wait until this extra data is received. If a timeout occurs, the connection is reset. So if extra data is expected of a specific amount, then the buffer size should be set to the minimum buffer size plus this amount.

Socket.AcceptAsync Method (SocketAsyncEventArgs), MSDN.

Assuming the buffering is not required, disabling the buffering seems to be a solution:

SocketAsyncEventArgs args = ...;
args.SetBuffer(null, 0, 0);
Community
  • 1
  • 1
-1

EDIT: Put a blocking line of code after server.Start in your example.

On a whim would you consider changing one line.

Change this

socketEvent.Completed += AcceptCompleted; //Add a callback on completed accept incomming connections attemp. 

To This

socketEvent.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptCompleted); //Add a callback on completed accept incomming connections attemp.
Sql Surfer
  • 1,344
  • 1
  • 10
  • 25