C#异步套接字服务器接收问题

4
我已经按照这篇文章中的说明实现了我的服务器应用程序:http://www.codeguru.com/csharp/csharp/cs_network/sockets/article.php/c8781#Client1 总结:我正在使用异步套接字(async Sockets),即BeginAccept(...),BeginReceive(...)。我的服务器能够处理多个客户端,并且一切都很正常,直到一个客户端执行了两个或更多同步发送操作而没有等待一段时间。客户端不会收到任何错误消息,也就没有得到通知,服务器没有收到第二条消息!如果客户端在第一次发送操作后等待大约100毫秒,一切都正常运行。我认为当我使用TCP时,可以确保服务器接收到消息(除非抛出异常)!你能提供一个解决方案来修复这个问题吗?
以下是我在服务器上实现的WaitForData(...) 和OnDataReceive(...) 方法。
public void WaitForData(MyClient client)
{
    try
    {
        if (pfnCallBack == null)
        {
            pfnCallBack = new AsyncCallback(OnDataReceived);
        }

        iarResult = client.Socket.BeginReceive(client.DataBuffer,
                                                0, client.DataBuffer.Length,
                                                SocketFlags.None,
                                                pfnCallBack,
                                                client);
    }
    catch (SocketException se)
    {
        MessageBox.Show("SocketException@WaitForData" + se.Message);
    }
}
public void OnDataReceived(IAsyncResult asyn)
{
    try
    {
        MyClient user= (MyClient)asyn.AsyncState;
        int iRx = user.Socket.EndReceive(asyn);

        byte[] receivedData = user.DataBuffer;

        MemoryStream memStream = new MemoryStream();
        BinaryFormatter binForm = new BinaryFormatter();
        memStream.Write(receivedData, 0, receivedData.Length);
        memStream.Seek(0, SeekOrigin.Begin);
        MyMessage msg = (MyMessage)binForm.Deserialize(memStream);

        switch (msg.Command)
        {
            case (MyMessage.MyCommand.ConnId):
                this.connId = (int) msg.MyObject;
                tsslConnStatus.Text += " | ID: " + connId.ToString();
            break;

            case (MyMessage.MyCommand.Text):
                MessageBox.Show(msg.MyObject.ToString());
                break;
        }
        WaitForData(server);
    }
    catch (ObjectDisposedException ode)
    {
        MessageBox.Show("ObjectDisposedException@OnReceiveData" + ode.Message);
    }
    catch (SocketException se)
    {
        MessageBox.Show("SocketException@OnReceiveData" + se.Message);
    }
}

客户端调用同步发送方法两次或更多次!服务器实例是MyClient。
if (server.Socket.Connected)
{
    BinaryFormatter bf = new BinaryFormatter();
    MemoryStream ms = new MemoryStream();
    bf.Serialize(ms, message);
    MyMessage = new MyMessage(something);
    server.Socket.Send(ms.ToArray());
}

因此,我认为这些代码片段足以让您理解我试图使用的想法!如果您需要更多详细信息或代码片段,请告诉我,我会发布它!

谢谢!


通过SerialPort.OnDataReceived句柄我学到,当发生“空闲”->“接收”转换时会调用处理程序。我需要读取直到没有数据可用。这里可能有类似的情况。 - harper
我不会期望在处理程序中收到完整的消息。要小心处理不完整的消息。 - harper
说实话,我不知道这些“转换”如何被访问和读取? - Martin F.
只是猜测:我会尝试使用Socket.Available属性。 - harper
1个回答

14

TCP是基于流而不是基于消息的。一个Read可能包含以下任何一种情况:

  • 一点点消息
  • 半个消息
  • 恰好一个消息
  • 一个半消息
  • 两个消息

因此,您需要使用某种方法来查看是否已经到达完整的消息。最常见的方法有:

  • 添加尾部(例如空行),表示消息结束
  • 添加固定长度的头部,包含消息的长度

更新

只有长度作为头部的简单示例。

服务器端:

var buffer = binaryFormmater.Serialize(myobj);
var length = buffer.Length;
networkStream.Send(length);
networkStream.Send(buffer, 0, buffer.Length);

客户端:

var header = new buffer[4];
// TODO: You need to make sure that 4 bytes have been read.
networkStream.Read(header, 0, 4);
var length = BitConverter.ToInt32(buffer);

var readbuffer= new byte[65535];
var bytesLeft = length;
var messageStream = new MemoryStream();
while (bytesLeft > 0)
{
     var read = networkStream.Read(readbuffer, 0, bytesLeft);
     messageStream.Write(readbuffer, 0, read);
     bytesLeft -= read,
}

messageStream.Seek(0, SeekOrigin.Begin);
MyMessage msg = (MyMessage)binForm.Deserialize(messageStream);

这个答案非常正确,如果可以的话我会点赞两次。基于消息的应用层协议不在TCP的范围之内。你可以自己实现一个或使用现有的许多框架之一。 - Jake T.
好的,感谢您的回答。但我现在不知道从哪里开始。只要OnReceiveData(..)方法没有被调用,我什么也做不了,问题是,这个方法没有被调用两次。此外,正如您在代码中看到的那样,我将整个接收缓冲区反序列化为单个MyMessage对象,没有失败。如果我没错的话,如果此流包含第二条消息的其他信息,必须会出现异常?也许您可以让我更接近我现在实际需要做的事情!谢谢! - Martin F.
在解决了提到的问题之前,我不会做任何其他事情。当您执行“读取”操作时,您可能会收到两条完整的消息,由于您只反序列化其中一条,另一条可能会被丢弃。 - jgauffin
我在我的示例中使用了同步(Receive)方法。你需要使用异步方法(BeginReceive/EndReceive)。 - jgauffin
你怎么能假设头部在前4个数组项中呢?buffer[4]。如果长度只有一个小值,那该怎么办? - Martin F.
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接