繁体   English   中英

C#异步套接字客户端/服务器

[英]C# Asynchronous Socket client/server

我在使用套接字编程TCP客户端/服务器时遇到麻烦。 我制作了一个小程序,使用我在这里找到的MSDN示例: http : //msdn.microsoft.com/en-us/library/bew39x2a%28v=vs.110%29.aspx,但做了一些修改,例如在发送功能上排队:

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次)。 但是,当我尝试快速更新数据时,似乎我的状态对象的字符串生成器没有时间正确清除,因为我同时在OnMessageReceived函数中接收到多个数据(这是一个问题,因为我序列化我发送的每个数据,因此我在服务器接收到的消息中以多个根元素结尾)。

如果您想仔细看一下,可以在这里找到整个项目(不确定我的解释是否清楚...)

预先感谢您的帮助和您的时间:)


编辑:对不起,我将尝试对我的问题进行更好的解释: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
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM