我正在构建一个简单的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;
}
}
答案 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);
}
不需要魔法。 :)