0

我现在正在写一个基于 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");
    }
}
}

但是当我启动几个客户端时,我发现有时服务器可以正确接收消息;但有时,服务器只能收到第一条消息,剩下的消息似乎都“丢失”了,任何人都可以建议吗?谢谢。

听人说,我应该实现自己的数据传输协议,但是谁能告诉我怎么定义?谢谢

下面是服务器端的截图: 在此处输入图像描述

4

1 回答 1

1

发生描述的问题是因为在服务器代码中,ReceiveAsync 方法仅调用一次,无论是否已收到整个消息。其余部分根本不读。

正如MSDN 上的 ReceiveAsync 文档中提到的,“对于字节流样式的套接字,传入的数据被放置到缓冲区中,直到缓冲区被填满、连接关闭或内部缓冲的数据耗尽。”。在您的情况下,如果客户端发送的消息被分成几个块,那么当第一部分数据到达服务器时,系统会将其放置在套接字的内部缓冲区中。如果您有一个等待数据的 ReceiveAsync 方法,它会读取内部缓冲的数据,直到它用尽然后返回,即使这只是第一个块并且仍有数据要来。您将需要另一个 ReceiveAsync 操作来获得它。如果你想检查这是真的,你可以尝试放置一个Thread.Sleep(200)在从客户端发送 5 条消息的 for 循环中。在这种情况下,服务器只接收到消息的第一部分的机会会变得非常高,因为 TCP 使用一些算法来有效地发送数据,而这个超时将决定它分别发送 5 条消息。但是,您无法控制消息在客户端和服务器之间的网络上的分段方式。即使仅使用一个 SendAsync 操作发送整个消息,也可能需要多个 ReceiveAsync 操作。

要解决在服务器上读取部分消息的问题,您必须知道您需要多少字节。这可以通过使用恒定的消息长度或使用某种协议来确定长度来完成,例如在从客户端发送的每条消息前面加上将要发送的消息的字节数。服务器将不得不进行多次 ReceiveAsync 调用,直到收到整个长度。为了做到这一点,服务器需要保留剩余接收字节数。您可以在 CodeProject 上找到 SocketAsyncEventArgs 客户端-服务器应用程序的完整且解释性示例,理解它将帮助您解决问题。

于 2013-04-26T21:07:19.627 回答