TCP / IP聊天程序正在向我发送混合消息

时间:2011-01-12 09:58:21

标签: c# .net sockets tcp

我正在构建一个简单的tcp / ip聊天程序,而且我很难单独发送消息。例如,如果我发送两条消息并且两者的内容都大于可以容纳20个字符的缓冲区,则发送第一条消息的20个字符,然后发送下一条消息的20个字符,然后发送第一条消息的其余部分,然后最后一条消息的其余部分。因此,当我解析和连接字符串时,我得到两条消息,分别是第一条消息的开头和第二条消息的开头,第一条消息的结尾和第二条消息的结尾。我想知道如何发送消息,并将下一条消息排队,直到第一条消息已经发送。作为旁注,我正在使用异步方法调用而不是线程。

我的代码:

客户端:

protected virtual void Write(string mymessage)
{


               var buffer = Encoding.ASCII.GetBytes(mymessage);
               MySocket.BeginSend(buffer, 0, buffer.Length, 
SocketFlags.None,EndSendCallBack, null);

               if (OnWrite != null)
               {
                   var target = (Control) OnWrite.Target;
                   if (target != null && target.InvokeRequired)
                   {
                       target.Invoke(OnWrite, this, new EventArgs());
                   }
                   else
                   {
                       OnWrite(this, new EventArgs());
                   }
               }
        }

和两个混合的电话:

 client.SendMessage("CONNECT",Parser<Connect>.TextSerialize(connect));
  client.SendMessage("BUDDYLIST","");

最后读取函数(我在每条消息的开头使用一个数字来知道消息何时以括号结束):

private void Read(IAsyncResult ar)
        {

            string content;
            var buffer = ((byte[]) ar.AsyncState);
            int len = MySocket.EndReceive(ar);
            if (len > 0)
            {
                string cleanMessage;
                content = Encoding.ASCII.GetString(buffer, 0, len);
                if (MessageLength == 0)
                {
                    MessageLength = int.Parse(content.Substring(1, content.IndexOf("]", 1) - 1));
                    cleanMessage = content.Replace(content.Substring(0, content.IndexOf("]", 0) + 1), "");
                }
                else
                    cleanMessage = content;

                if(cleanMessage.Length <1)
                {
                    if(MySocket.Connected)
                        MySocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Read), buffer);
                    return;
                }

                MessageLength = MessageLength > cleanMessage.Length? MessageLength - cleanMessage.Length : 0;
                amessage += cleanMessage;

                if(MessageLength == 0)
                {
                    if (OnRead != null)
                    {
                        var e = new CommandEventArgs(this, amessage);
                        Control target = null;
                        if (OnRead.Target is Control)
                            target = (Control)OnRead.Target;
                        if (target != null && target.InvokeRequired)
                            target.Invoke(OnRead, this, e);
                        else
                            OnRead(this, e);
                    }
                    amessage = String.Empty;
                }
                MySocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Read), buffer);
                    return;
            }
        }

2 个答案:

答案 0 :(得分:1)

TCP 保证您将在一次阅读中收到整条消息。因此,您需要能够检测消息的开始位置和结束位置。

您通常会在邮件末尾添加一些特殊字符。或者在实际消息之前使用长度标题。

您通常不需要在客户端中使用BeginSend。发送应该足够快,也会降低复杂性。此外,我通常不会在服务器中使用BeginSend,除非服务器应该真的具有高性能。

<强>更新

实际的套接字实现永远不会混合您的消息,只有您的代码才能这样做。您不能通过调用多个发送来发送消息,因为如果您的应用程序是多线程的,那么您的消息将被混合。

换句话说,这不起作用:

_socket.BeginSend(Encoding.ASCII.GetBytes("[" + message.Length + "]"))
_socket.BeginSend(Encoding.ASCII.GetBytes(message));

你必须通过一次发送发送所有内容。

更新2

您的读取实现没有考虑到两条消息可以在同一个Read中。这很可能是你混合信息的原因。

如果您发送:

[11]Hello world
[5]Something else

他们可以到达:

[11]Hello World[5]Some
thing else

换句话说,第二条消息的一部分可以到达第一个BeginRead。您应该始终使用所有收到的内容构建一个缓冲区(使用StringBuilder)并删除已处理的部分。

伪代码:

method OnRead
    myStringBuilder.Append(receivedData);
    do while gotPacket(myStringBuilder)
        var length = myStringBuilder.Get(2, 5)
        if (myStringBuilder.Length < 7 + length)
           break;

        var myMessage = myStringBuilder.Get(7, length);
        handle(myMessage);

        myStringBuilder.Remove(0, 7+length);
    loop
end method

你看到我在做什么吗?我总是在字符串构建器中附加接收到的数据,然后删除完整的消息。我正在使用一个循环,因为多个消息可以一次到达。

答案 1 :(得分:1)

如果您在一个发送呼叫中发送整个消息,它将按顺序到达。您需要一个分隔符来分隔您的邮件。在接收端,您需要将消息缓冲到缓冲区。如果你没有发送大量数据,你可以让自己有点草率,但很容易编码来处理收到的消息:

// Note: Untested pseudocode
buffer += stringDataRead;
while (buffer.Contains("\r\n")) {
  // You may want to compensate for \r\n by doing a -2 here and +2 on next line
  line = buffer.Substring(0, buffer.IndexOf("\r\n"));
  buffer = buffer.Remove(0, line.Length);

  DoSomehingWithThisLine(line);
}

不需要魔法。 :)