简体   繁体   English

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

[英]C# Asynchronous Socket client/server

I am having troubles programming a TCP client/server using sockets. 我在使用套接字编程TCP客户端/服务器时遇到麻烦。 I've made a little programme that use the MSDN examples i found here : http://msdn.microsoft.com/en-us/library/bew39x2a%28v=vs.110%29.aspx but with a few modifications like a queue on the send function: 我制作了一个小程序,使用我在这里找到的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);
        }
    }
}

The data are read by the server via the readcallback function : 服务器通过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);

        }
    }

Well, now here is my problem : the whole point of my application is to send variable to a server at high frequency (up to 60 time per seconds for each variable). 好了,现在这是我的问题 :我的应用程序的重点是将变量高频发送到服务器(每个变量最多每秒60次)。 BUT, when i try to update very quickly my data, it seems the string builder of my state object doesn't have the time to clear properly as i reiceive multiple data at the same time within my OnMessageReceived function (which is a problem because i serialize every data i send so i end up with multiple root element in the message received by the server). 但是,当我尝试快速更新数据时,似乎我的状态对象的字符串生成器没有时间正确清除,因为我同时在OnMessageReceived函数中接收到多个数据(这是一个问题,因为我序列化我发送的每个数据,因此我在服务器接收到的消息中以多个根元素结尾)。

The whole project is available here if you want to have a closer look (not really sure my explanations are clear...) 如果您想仔细看一下,可以在这里找到整个项目(不确定我的解释是否清楚...)

Thank you in advance for your help & your time :) 预先感谢您的帮助和您的时间:)


EDIT : Sorry, i will try to give a beter explanation of my problem :p 编辑:对不起,我将尝试对我的问题进行更好的解释:p

Here is a message example sent & received correctly when i update a data solely. 这是我仅更新数据时正确发送和接收的消息示例。

<MetrixData>
  <DataId>IntTest</DataId>
  <Type>System.Int32</Type>
  <Value TimeStamp="22/07/14 22:22:19">10</Value>
</MetrixData>

And here is what my server receive if i make multiple update of my data in a very short period of time. 如果我在很短的时间内多次更新数据,这就是服务器收到的信息。

<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>

(can receive 2, 3... or up to 10 messages at the same time) (可以同时接收2、3 ...或最多10条消息)

I can't figure out why my ReadCallback doesn't detect the end of the current message and doesn't reset the buffer which should be done by this sample of ReadCallBack 我不知道为什么我的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 offers you a stream of bytes. TCP为您提供字节流。 There are no messages in TCP. TCP中没有消息。 You are probably receiving two logical messages in one read. 您可能在一次读取中收到了两条逻辑消息。 That causes your processing to be executed once when you wanted it to run twice. 当您希望处理运行两次时,这会使处理执行一次。

If you want message semantics you have to implement them yourself. 如果需要消息语义,则必须自己实现它们。 Usually, raw TCP connections are a mistake to begin with. 通常,原始TCP连接一开始是错误的。 Why don't you use a higher-level primitive like HTTP or web services? 为什么不使用HTTP或Web服务这样的高级原语? At the very least, use protopuf as the serialization format. 至少要使用protopuf作为序列化格式。 Had you done any of these the problem would not have occurred. 如果您执行了任何这些操作,则不会发生此问题。 Many more landmines are ahead of you when you implement a wire protocol yourself. 当您自己实施有线协议时,还有更多地雷在您面前。

Also, your string encoding will fail you once you go outside the ASCII range (in other words, when this app goes into production). 另外,一旦超出ASCII范围(换句话说,当此应用投入生产时),字符串编码将使您失败。 Again, this problem only exists because you are doing all this low-level work. 同样,此问题仅存在是因为您正在执行所有这些低级工作。 Sending messages and strings over a network cable has been automated for you. 通过网络电缆发送消息和字符串已自动完成。 Utilize that work. 利用这项工作。

This is speculation on my part. 这是我的推测。 I think that without packet-orientation (UDP) you just have a continuous TCP stream. 我认为没有数据包定向(UDP)的情况下,您只有连续的TCP流。 You need to implement one of these: 您需要实现以下之一:

  • revert to UDP (maybe using Lidgren) => very expensive and time consuming; 恢复为UDP(可能使用Lidgren)=>非常昂贵且耗时;
  • break up the stream with a separator character => requires a clearly defined set of the alphabet and the separator, example: in BSON (binary JSON) you have the base-64 alphabet and may use any non-base-64 character as a separator suche as '|'; 用分隔符分隔流=>需要一组明确定义的字母和分隔符,例如:在BSON(二进制JSON)中,您具有base-64字母,并且可以使用任何非base-64字符作为分隔符如“ |”;
  • implement a message envelop that allows you to separate the single messages => this is the natural solution for what you are doing. 实现一个消息信封,使您可以分隔单个消息=>这是您正在执行的自然解决方案。

The message envelop is as easy as this: 邮件信封很简单:

  • use a MemoryStream to buffer output data; 使用MemoryStream缓冲输出数据;
  • use a BinaryWriter to write into that buffer; 使用BinaryWriter写入该缓冲区;
  • have a "send/transmit/submit/commit" method that closes the envelop and sends it on the NetworkStream. 有一个“发送/传输/提交/提交”方法,可以关闭信封并将其发送到NetworkStream上。

This is a quick whip-up: 这是一个快速的提示:

// 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
}

On the other end of the line you will open the envelop and extract the message with the reverse procedure: 在该行的另一端,您将打开信封并按照相反的过程提取消息:

// 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