c#异步Socket客户端/服务器
本文关键字:服务器 客户端 Socket 异步 | 更新日期: 2023-09-27 18:14:07
我在使用套接字编程TCP客户端/服务器时遇到了麻烦。我做了一个小程序,使用我在这里找到的MSDN示例:http://msdn.microsoft.com/en-us/library/bew39x2a%28v=vs.110%29.aspx但是通过一些修改,比如send函数上的队列:
public void Send(String data)
{
// Wait for server connection
if (connectDone.WaitOne())
{
// If already sending data, add to the queue
// it will be sent by SendCallback method
if (_sending)
{
_queue.Enqueue(data);
}
else
{
// Convert the string data to byte data using ASCII encoding.
byte[] byteData = Encoding.ASCII.GetBytes(data);
// Begin sending the data to the remote device.
_sending = true;
SocketHolder.BeginSend(byteData, 0, byteData.Length, 0, SendCallback, SocketHolder);
}
}
}
服务器通过readcallback函数读取数据:
public void ReadCallback(IAsyncResult ar)
{
string content = string.Empty;
// Retrieve the state object and the handler socket
// from the asynchronous state object.
StateObject state = (StateObject)ar.AsyncState;
Socket handler = state.workSocket;
// Read data from the client socket.
int bytesRead = handler.EndReceive(ar);
if (bytesRead > 0)
{
// There might be more data, so store the data received so far.
state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
// Check for end of message tag & raise event
content = state.sb.ToString();
if (content.IndexOf("</MetrixData>") > -1)
{
Console.WriteLine("Read {0} bytes from socket. 'n Data : {1}", content.Length, content);
OnMessageReceived(EventArgs.Empty, content);
state.sb = new StringBuilder(); // Clear the message from state object string builder
}
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, ReadCallback, state);
}
}
好,现在这是我的问题:我的应用程序的全部要点是以高频率向服务器发送变量(每个变量每秒高达60次)。但是,当我尝试非常快地更新我的数据时,似乎我的状态对象的字符串生成器没有时间正确清除,因为我同时在我的onmessagerreceived函数中接收多个数据(这是一个问题,因为我序列化我发送的每个数据,所以我最终在服务器收到的消息中有多个根元素)。
如果你想仔细看看,可以在这里找到整个项目(不确定我的解释是否清楚…)
提前感谢您的帮助&你的时间:)
编辑:对不起,我会尽量给我的问题一个更好的解释:p
这是一个发送的消息示例&当我单独更新数据时,正确接收。
<MetrixData>
<DataId>IntTest</DataId>
<Type>System.Int32</Type>
<Value TimeStamp="22/07/14 22:22:19">10</Value>
</MetrixData>
如果我在很短的时间内多次更新我的数据,这是我的服务器收到的。
<MetrixData>
<DataId>IntTest</DataId>
<Type>System.Int32</Type>
<Value TimeStamp="22/07/14 22:25:06">12</Value>
</MetrixData><MetrixData>
<DataId>IntTest</DataId>
<Type>System.Int32</Type>
<Value TimeStamp="22/07/14 22:25:06">13</Value>
</MetrixData><MetrixData>
<DataId>IntTest</DataId>
<Type>System.Int32</Type>
<Value TimeStamp="22/07/14 22:25:06">14</Value>
</MetrixData>
(可以接收2,3…或同时发送最多10条消息)
我不明白为什么我的ReadCallback不检测当前消息的结束,也不重置缓冲区,这应该由这个ReadCallback示例完成
// Check for end of message tag & raise event
content = state.sb.ToString();
if (content.IndexOf("</MetrixData>") > -1)
{
Console.WriteLine("Read {0} bytes from socket. 'n Data : {1}", content.Length, content);
OnMessageReceived(EventArgs.Empty, content);
state.sb = new StringBuilder(); // Clear the message from state object string builder
}
TCP提供一个字节流。TCP中没有消息。您可能在一次读取中接收到两个逻辑消息。这将导致您的处理只执行一次,而您希望它运行两次。
如果你想要消息语义,你必须自己实现它们。通常,原始TCP连接一开始就是一个错误。为什么不使用HTTP或web服务等更高级的原语呢?至少,使用protopuf作为序列化格式。如果你做了这些,问题就不会发生了。当你自己实现一个有线协议时,更多的地雷就在你面前。此外,一旦超出ASCII范围(换句话说,当此应用程序投入生产时),您的字符串编码将失败。同样,这个问题的存在只是因为你在做这些底层的工作。通过网络电缆发送消息和字符串已经为您自动化了。
这是我的推测。我认为,如果没有面向包(UDP),你只是有一个连续的TCP流。您需要实现以下其中一个:
- 恢复到UDP(可能使用Lidgren) =>非常昂贵和耗时;
- 用分隔符分隔流=>需要一组明确定义的字母表和分隔符,例如:在BSON(二进制JSON)中,您有base-64字母表,并且可以使用任何非base-64字符作为分隔符,例如'|';
- 实现一个消息信封,允许您分离单个消息=>这是您正在做的自然解决方案。
消息信封就像这样简单:
- 使用MemoryStream缓冲输出数据; 使用BinaryWriter写入缓冲区;
- 有一个"发送/传输/提交/提交"方法来关闭信封并将其发送到NetworkStream上。
这是一个快速的鞭策:
// NetworkStream ns; <-- already set up
using(MemoryStream memory = new MemoryStream())
using(BinaryWriter writer = new BinaryWriter(memory))
{
// all output to writer goes here
// now close the envelop and send it:
byte[] dataBuffer, sizeBuffer;
dataBuffer = memory.ToArray();
sizeBuffer = BitConverter.GetBytes(dataBuffer.Length);
ns.SendBytes(sizeBuffer, 0, 4); // send message length (32 bit int)
ns.SendBytes(dataBuffer, 0, dataBuffer.Length); // send message data
}
在线路的另一端,您将打开信封并使用相反的过程提取消息:
// NetworkStream ns; <-- already set up
byte[] sizeBuffer, dataBuffer;
int size;
sizeBuffer = new byte[4];
ns.ReadBytes(sizeBuffer, 0, 4); // read message length
size = BitConverter.ToInt(sizeBuffer);
dataBuffer = new byte[size];
ns.ReadBytes(dataBuffer, 0, size); // read message data
using(MemoryStream memory = new MemoryStream(dataBuffer))
using(BinaryReader reader = new BinaryReader(memory))
{
// all input from reader goes here
}