Почему мое TCP-соединение теряет пакеты на стороне сервера?

Сейчас я пишу крошечный фреймворк на основе SocketAsyncEventArgs, этот класс создан на основе IOCP, который намного эффективнее, чем режим APM. но здесь у меня возникли проблемы при запуске теста. вот код сервера:

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)
    {

    }
}
}

вот мой код клиента:

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");
    }
}
}

Но когда я запускаю несколько клиентов, я обнаружил, что иногда сервер может правильно получать сообщения; но иногда сервер может получить только первое сообщение, оставшиеся сообщения, кажется, все "потеряны", кто-нибудь может посоветовать? Спасибо.

Я слышал от кого-то, что я должен реализовать свой собственный протокол для передачи данных, но кто-нибудь может сказать мне, как определить? Спасибо

ниже приведен снимок экрана со стороны сервера:введите описание изображения здесь

1 ответ

Решение

Описанная проблема возникает потому, что в коде сервера метод ReceiveAsync вызывается только один раз, независимо от того, было ли получено все сообщение или нет. Остальное просто не читается.

Как упомянуто в документации ReceiveAsync на MSDN: "Для сокетов в стиле байтового потока входящие данные помещаются в буфер до тех пор, пока буфер не заполнится, соединение не будет закрыто или данные из внутренней буферизации не будут исчерпаны". В вашем случае, если сообщение, отправленное клиентом, разбивается на несколько частей, то, когда первая часть данных достигает сервера, она помещается системой во внутренний буфер сокета. Если у вас есть метод ReceiveAsync, ожидающий данных, он считывает данные из внутренней буферизации до тех пор, пока они не будут исчерпаны, а затем вернется, даже если это только первый блок, и данные еще есть. Вам понадобится другая операция ReceiveAsync, чтобы получить это. Если вы хотите проверить, что это правда, вы можете попробовать Thread.Sleep(200) в цикле for, который отправляет 5 сообщений от клиента. В этом случае шансы сервера получить только первую часть сообщения станут очень высокими, поскольку TCP использует некоторые алгоритмы для эффективной отправки данных, и этот тайм-аут будет определять его для отправки 5 сообщений отдельно. Однако вы не можете контролировать, как сообщение фрагментируется в сети между клиентом и сервером. Может потребоваться несколько операций ReceiveAsync, даже если все сообщение было отправлено с использованием только одной операции SendAsync.

Чтобы решить проблему чтения частичных сообщений на сервере, вам нужно знать, сколько байтов вы ожидаете. Это может быть сделано либо с использованием постоянной длины сообщения, либо с помощью некоторого протокола для определения длины, например, добавление к каждому сообщению, переданному от клиента, префикса с количеством байтов сообщения, которое будет отправлено. Сервер должен будет выполнить несколько вызовов ReceiveAsync, пока не будет получена вся длина. Для этого серверу необходимо сохранить количество байтов, оставшихся для получения. Вы можете найти полный и объясненный пример клиент-серверного приложения SocketAsyncEventArgs в CodeProject, и его понимание поможет вам в решении вашей проблемы.

Другие вопросы по тегам