Question

I am now writing a tiny framework based on SocketAsyncEventArgs , this class is created based on IOCP , which is much more efficient than APM mode. but here, I got some problems when running test. here is the server code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Windows.Forms;

namespace SocketServer
{
public class Server
{
    Socket serverSocket;
    SocketAsyncEventArgs socketAsyncEventArgs;
    SocketAsyncEventArgsPool readWritePool;
    HandleMessage handleMessage;
    BufferManager buffeManager;

    const int PrefixSize = 11;

    public void Init(int port,int connections,int receiveBufferSize)
    {
        buffeManager = new BufferManager(receiveBufferSize * connections * 2, receiveBufferSize);

        buffeManager.InitBuffer();

        readWritePool = new SocketAsyncEventArgsPool(connections);

        SocketAsyncEventArgs socketAsyncEventArgsPooling;
        for (int i = 0; i < connections; i++)
        {
            socketAsyncEventArgsPooling = new SocketAsyncEventArgs();
            socketAsyncEventArgsPooling.Completed += readEventArgsIO_Completed;

            buffeManager.SetBuffer(socketAsyncEventArgsPooling);
            readWritePool.Push(socketAsyncEventArgsPooling);
        }

        handleMessage = new HandleMessage();

        IPAddress[] addressList = Dns.GetHostEntry(Environment.MachineName).AddressList;
        IPEndPoint localEndPoint = new IPEndPoint(addressList[addressList.Length - 1], port);

        this.serverSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);


        if (localEndPoint.AddressFamily == AddressFamily.InterNetworkV6)
        {
            this.serverSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false);
            this.serverSocket.Bind(new IPEndPoint(IPAddress.IPv6Any, localEndPoint.Port));
        }
        else
        {
            this.serverSocket.Bind(localEndPoint);
        }

        this.serverSocket.Listen(100);

        StartAccept(null);
    }

    private void StartAccept(SocketAsyncEventArgs acceptSocketAsyncEventArgs)
    {
        if (acceptSocketAsyncEventArgs == null)
        {
            acceptSocketAsyncEventArgs = new SocketAsyncEventArgs();
            acceptSocketAsyncEventArgs.Completed += socketAsyncEventArgs_Completed;
        }
        else
        {
            acceptSocketAsyncEventArgs.AcceptSocket = null;
        }

        Boolean willRaiseEvent = this.serverSocket.AcceptAsync(acceptSocketAsyncEventArgs);
        if (!willRaiseEvent)
        {
            this.ProcessAccept(acceptSocketAsyncEventArgs);
        }
    }

    private void socketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e)
    {
        ProcessAccept(e);
    }

    private void readEventArgsIO_Completed(object sender, SocketAsyncEventArgs e)
    {
        switch (e.LastOperation)
        {
            case SocketAsyncOperation.Receive:
                this.ProcessReceive(e);
                break;
            case SocketAsyncOperation.Send:
                //this.ProcessSend(e);
                break;
            default:
                throw new ArgumentException("The last operation completed on the socket was not a receive or send");
        }
    }


    private void ProcessAccept(SocketAsyncEventArgs e)
    {

        SocketAsyncEventArgs readEventArgs = this.readWritePool.Pop();
        //SocketAsyncEventArgs readEventArgs = new SocketAsyncEventArgs();
        readEventArgs.UserToken = e.AcceptSocket;

        Console.WriteLine("---------------------------------------------------");
        Console.WriteLine("Client Connected {0}",e.AcceptSocket.RemoteEndPoint);

        Boolean willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs);

        if (!willRaiseEvent)
        {
            this.ProcessReceive(readEventArgs);
        }

        this.StartAccept(e);
    }

    private void ProcessReceive(SocketAsyncEventArgs e)
    {
        if (e.BytesTransferred > 0)
        {
            if (e.SocketError == SocketError.Success)
            {
                Console.WriteLine("receiving data, {0} bytes", e.BytesTransferred);
                Socket socket = e.UserToken as Socket;

                int bytesTransferred = e.BytesTransferred;

                string received = Encoding.ASCII.GetString(e.Buffer, e.Offset, bytesTransferred);


                Console.WriteLine("Received:{0}", received);

                string[] msgArray = handleMessage.GetActualString(received);

                foreach (var msg in msgArray)
                {
                    Console.WriteLine("After Split:{0}", msg);
                }

               // Array.Clear(e.Buffer, e.Offset, bytesTransferred);

                Boolean willRaiseEvent = socket.SendAsync(e);
                if (!willRaiseEvent)
                {
                    this.ProcessSend(e);
                }

                readWritePool.Push(e);
            }
        }

    }

    private void ProcessSend(SocketAsyncEventArgs e)
    {

    }
}
}

here is my client code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;

namespace SocketClient
{
class Program
{
    static void Main(string[] args)
    {
        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("192.168.2.129"), 1234);

        SocketAsyncEventArgs connectArgs = new SocketAsyncEventArgs();

        connectArgs.RemoteEndPoint = ipEndPoint;
        connectArgs.Completed += OnConnected;

        socket.ConnectAsync(connectArgs);

        socket.SendBufferSize = Int16.MaxValue;

        //NetworkStream streamToServer = new NetworkStream(socket);
        string text = "[length=12]Hello server";
        byte[] sendBuffer = Encoding.ASCII.GetBytes(text);


        for (int i = 0; i < 5; i++)
        {
            SocketAsyncEventArgs sendArgs = new SocketAsyncEventArgs();

            sendArgs.UserToken = socket;
            sendArgs.SetBuffer(sendBuffer,0,sendBuffer.Length);
            sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSend);


            socket.SendAsync(sendArgs);
        }

        Console.ReadLine();
    }

    private static void OnSend(object sender, SocketAsyncEventArgs e)
    {
        Console.WriteLine("SendOk: {0}", e.UserToken.ToString());
    }

    private static void OnConnected(object sender, SocketAsyncEventArgs e)
    {
        Console.WriteLine("Conectioned");
    }
}
}

But when I start several clients, i found that sometimes the server can receive the messages correctly; but sometimes, the server can only receive the first message, the remain messages seems are all "lost", anyone can advice? thx.

I heard from someone that, I should realize my own protocol to transfer data, but anyone can tell me how to define? thx

below is the capture of the screen shoot of server side: enter image description here

Was it helpful?

Solution

The issue described happens because in the the server code the ReceiveAsync method is called only once no matter if the entire message has been received or not. The rest of it is simply not read.

As it is mentioned in the ReceiveAsync documentation on MSDN, "For byte stream-style sockets, incoming data is placed into the buffer until the buffer is filled, the connection is closed, or the internally buffered data is exhausted. ". In your case, if the message sent by the client is split into several chunks, then when the first part of data reaches the server it is placed by the system in the internal buffer of the socket. If you have a ReceiveAsync method waiting for data it reads the internally buffered data until it exhausted and then it return, even this is only the first chunk and there is still data to come. You will need another ReceiveAsync operation to get that. If you want to check that this is true you could try to place a Thread.Sleep(200) in the for loop that send the 5 messages from the client. In this case the server chances to receive only the first part of the message will became very high, because TCP uses some algorithms to efficiently send the data and this timeout will determine it to send the 5 messages separately. However you cannot control how the message is fragmented on the network between the client and the server. Multiple ReceiveAsync operations may be needed even if the entire message was send using only one SendAsync operation.

To solve the problem of reading partial messages on the server you will have to know how many bytes you are expecting. This can be done either by using a constant message length or by having some protocol to determine the length, for example prefixing each message sent from the client with the number of bytes of the message that will be sent. The server will have to make several ReceiveAsync calls until the entire length is received. In order to do that the server will need to keep the count of the bytes remaining to receive. You can find a complete and explained example of a SocketAsyncEventArgs client-server application on CodeProject and understanding it will help you in solving your issue.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top