C# Socket.Receive 消息长度

发布于 2024-10-25 23:32:49 字数 2855 浏览 6 评论 0原文

我目前正在开发一个 C# Socket 服务器,它可以接受来自多个客户端计算机的多个连接。服务器的目标是允许客户端“订阅”和“取消订阅”服务器事件。

到目前为止,我已经在这里仔细查看了: http://msdn.microsoft.com/en-us/library/5w7b7x5f(v=VS.100).aspxhttp://msdn.microsoft.com/en-us/library/fx6588te.aspx 寻求想法。

我发送的所有消息都是加密的,因此我获取要发送的字符串消息,将其转换为 byte[] 数组,然后在将消息长度添加到数据之前加密数据并通过连接发送出去。

令我印象深刻的一件事是:在接收端,当只收到一半消息时,Socket.EndReceive()(或相关的回调)可能会返回。有没有一种简单的方法可以确保每条消息都被“完整”接收并且一次只能收到一条消息?

编辑: 例如,我认为 .NET / Windows 套接字不会“包装”消息以确保在一次 Socket.Receive() 调用中收到使用 Socket.Send() 发送的单个消息?或者确实如此?

到目前为止我的实现:

private void StartListening()
{
    IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
    IPEndPoint localEP = new IPEndPoint(ipHostInfo.AddressList[0], Constants.PortNumber);

    Socket listener = new Socket(localEP.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
    listener.Bind(localEP);
    listener.Listen(10);

    while (true)
    {
        // Reset the event.
        this.listenAllDone.Reset();

        // Begin waiting for a connection
        listener.BeginAccept(new AsyncCallback(this.AcceptCallback), listener);

        // Wait for the event.
        this.listenAllDone.WaitOne();
    }
}

private void AcceptCallback(IAsyncResult ar)
{
    // Get the socket that handles the client request.
    Socket listener = (Socket) ar.AsyncState;
    Socket handler = listener.EndAccept(ar);

    // Signal the main thread to continue.
    this.listenAllDone.Set();

    // Accept the incoming connection and save a reference to the new Socket in the client data.
    CClient client = new CClient();
    client.Socket = handler;

    lock (this.clientList)
    {
        this.clientList.Add(client);
    }

    while (true)
    {
        this.readAllDone.Reset();

        // Begin waiting on data from the client.
        handler.BeginReceive(client.DataBuffer, 0, client.DataBuffer.Length, 0, new AsyncCallback(this.ReadCallback), client);

        this.readAllDone.WaitOne();
    }
}

private void ReadCallback(IAsyncResult asyn)
{
    CClient theClient = (CClient)asyn.AsyncState;

    // End the receive and get the number of bytes read.
    int iRx = theClient.Socket.EndReceive(asyn);
    if (iRx != 0)
    {
        // Data was read from the socket.
        // So save the data 
        byte[] recievedMsg = new byte[iRx];
        Array.Copy(theClient.DataBuffer, recievedMsg, iRx);

        this.readAllDone.Set();

        // Decode the message recieved and act accordingly.
        theClient.DecodeAndProcessMessage(recievedMsg);

        // Go back to waiting for data.
        this.WaitForData(theClient);
    }         
}

I'm currently in the process of developing a C# Socket server that can accept multiple connections from multiple client computers. The objective of the server is to allow clients to "subscribe" and "un-subscribe" from server events.

So far I've taken a jolly good look over here: http://msdn.microsoft.com/en-us/library/5w7b7x5f(v=VS.100).aspx and http://msdn.microsoft.com/en-us/library/fx6588te.aspx for ideas.

All the messages I send are encrypted, so I take the string message that I wish to send, convert it into a byte[] array and then encrypt the data before pre-pending the message length to the data and sending it out over the connection.

One thing that strikes me as an issue is this: on the receiving end it seems possible that Socket.EndReceive() (or the associated callback) could return when only half of the message has been received. Is there an easy way to ensure each message is received "complete" and only one message at a time?

EDIT: For example, I take it .NET / Windows sockets does not "wrap" the messages to ensure that a single message sent with Socket.Send() is received in one Socket.Receive() call? Or does it?

My implementation so far:

private void StartListening()
{
    IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
    IPEndPoint localEP = new IPEndPoint(ipHostInfo.AddressList[0], Constants.PortNumber);

    Socket listener = new Socket(localEP.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
    listener.Bind(localEP);
    listener.Listen(10);

    while (true)
    {
        // Reset the event.
        this.listenAllDone.Reset();

        // Begin waiting for a connection
        listener.BeginAccept(new AsyncCallback(this.AcceptCallback), listener);

        // Wait for the event.
        this.listenAllDone.WaitOne();
    }
}

private void AcceptCallback(IAsyncResult ar)
{
    // Get the socket that handles the client request.
    Socket listener = (Socket) ar.AsyncState;
    Socket handler = listener.EndAccept(ar);

    // Signal the main thread to continue.
    this.listenAllDone.Set();

    // Accept the incoming connection and save a reference to the new Socket in the client data.
    CClient client = new CClient();
    client.Socket = handler;

    lock (this.clientList)
    {
        this.clientList.Add(client);
    }

    while (true)
    {
        this.readAllDone.Reset();

        // Begin waiting on data from the client.
        handler.BeginReceive(client.DataBuffer, 0, client.DataBuffer.Length, 0, new AsyncCallback(this.ReadCallback), client);

        this.readAllDone.WaitOne();
    }
}

private void ReadCallback(IAsyncResult asyn)
{
    CClient theClient = (CClient)asyn.AsyncState;

    // End the receive and get the number of bytes read.
    int iRx = theClient.Socket.EndReceive(asyn);
    if (iRx != 0)
    {
        // Data was read from the socket.
        // So save the data 
        byte[] recievedMsg = new byte[iRx];
        Array.Copy(theClient.DataBuffer, recievedMsg, iRx);

        this.readAllDone.Set();

        // Decode the message recieved and act accordingly.
        theClient.DecodeAndProcessMessage(recievedMsg);

        // Go back to waiting for data.
        this.WaitForData(theClient);
    }         
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

筱武穆 2024-11-01 23:32:49

是的,您可能每次只接收到部分消息,而且情况可能更糟,在传输过程中仅发送部分消息。通常您可以在网络状况不佳或网络负载过重的情况下看到这一点。

需要明确的是,在网络级别 TCP 保证按指定顺序传输您的数据,但不保证部分数据与您发送的数据相同。该软件的原因有很多(例如,查看 Nagle 算法)、硬件(跟踪中的不同路由器),操作系统实现,因此通常您不应该假设数据的哪一部分已经传输或接收。

抱歉,介绍很长,以下是一些建议:

  1. 尝试为高性能套接字服务器使用相关的“新”API,此处示例.NET v4.0 的网络示例

  2. 不要假设您始终发送完整数据包。 Socket.EndSend() 返回实际计划发送的字节数,它在网络负载较重的情况下甚至可以是 1-2 个字节。因此,您必须在需要时重新发送缓冲区的其余部分。

    MSDN 上有警告:

    <块引用>

    无法保证数据
    您发送的内容将出现在网络上
    立即地。增加网络
    效率,底层系统可能
    延迟传输直到显着
    收集输出数据量。
    圆满完成了
    BeginSend方法意味着
    底层系统有空间
    缓冲网络发送的数据。

  3. 不要假设您总是收到完整的数据包。将接收到的数据加入某种缓冲区中,并在有足够数据时对其进行分析。

  4. 通常,对于二进制协议,我添加字段来指示传入的数据量、具有消息类型的字段(或者您可以为每个消息类型使用固定长度(通常不好,例如版本控制问题))、版本字段(如果适用)并将 CRC 字段添加到消息末尾。

  5. 它并不是真的需要阅读,有点旧并且直接适用于 Winsock,但也许值得研究:Winsock 程序员常见问题解答

  6. 查看ProtocolBuffers,值得学习:http://code.google .com/p/protobuf-csharp-port/http://code. google.com/p/protobuf-net/

希望有帮助。

PS 可悲的是,您在 MSDN 上引用的示例有效地破坏了其他答案中所述的异步范例。

Yes, it is possible you'll have only part of message per one receiving, also it can be even worse during transfer only part of message will be sent. Usually you can see that during bad network conditions or under heavy network load.

To be clear on network level TCP guaranteed to transfer your data in specified order but it not guaranteed that portions of data will be same as you sent. There are many reasons for that software (take a look to Nagle's algorithm for example), hardware (different routers in trace), OS implementation, so in general you should never assume what part of data already transferred or received.

Sorry for long introduction, below some advices:

  1. Try to use relatevely "new" API for high-performance socket server, here samples Networking Samples for .NET v4.0

  2. Do not assume you always send full packet. Socket.EndSend() returns number of bytes actually scheduled to send, it can be even 1-2 bytes under heavy network load. So you have to implement resend rest part of buffer when it required.

    There is warning on MSDN:

    There is no guarantee that the data
    you send will appear on the network
    immediately. To increase network
    efficiency, the underlying system may
    delay transmission until a significant
    amount of outgoing data is collected.
    A successful completion of the
    BeginSend method means that the
    underlying system has had room to
    buffer your data for a network send.

  3. Do not assume you always receive full packet. Join received data in some kind of buffer and analyze it when it have enough data.

  4. Usually, for binary protocols, I add field to indicate how much data incoming, field with message type (or you can use fixed length per message type (generally not good, e.g. versioning problem)), version field (where applicable) and add CRC-field to end of message.

  5. It not really required to read, a bit old and applies directly to Winsock but maybe worth to study: Winsock Programmer's FAQ

  6. Take a look to ProtocolBuffers, it worth to learn: http://code.google.com/p/protobuf-csharp-port/, http://code.google.com/p/protobuf-net/

Hope it helps.

P.S. Sadly sample on MSDN you refer in question effectively ruin async paradigm as stated in other answers.

×眷恋的温暖 2024-11-01 23:32:49

你的代码是非常错误的。这样做循环违背了异步编程的目的。异步IO用于不阻塞线程而是让它们继续做其他工作。通过这样的循环,您将阻塞线程。

void StartListening()
{
    _listener.BeginAccept(OnAccept, null);
}

void OnAccept(IAsyncResult res)
{
    var clientSocket = listener.EndAccept(res);

    //begin accepting again
    _listener.BeginAccept(OnAccept, null);

   clientSocket.BeginReceive(xxxxxx, OnRead, clientSocket);
}

void OnReceive(IAsyncResult res)
{
    var socket = (Socket)res.Asyncstate;

    var bytesRead = socket.EndReceive(res);
    socket.BeginReceive(xxxxx, OnReceive, socket);

    //handle buffer here.
}

请注意,我已删除所有错误处理以使代码更清晰。该代码不会阻塞任何线程,因此效率更高。我会将代码分为两类:服务器处理代码和客户端处理代码。它使得维护和扩展变得更加容易。

接下来要了解的是 TCP 是一种流协议。它不保证消息在一次接收中到达。因此,您必须知道消息有多大或何时结束。

第一个解决方案是为每条消息添加一个标头作为前缀,您首先解析该标头,然后继续阅读,直到获得完整的正文/消息。

第二种解决方案是在每条消息的末尾放置一些控制字符并继续读取,直到读取控制字符。请记住,如果该字符可以存在于实际消息中,则应该对该字符进行编码。

Your code is very wrong. Doing loops like that defeats the purpose of asynchronous programming. Async IO is used to not block the thread but let them continue doing other work. By looping like that, you are blocking the thread.

void StartListening()
{
    _listener.BeginAccept(OnAccept, null);
}

void OnAccept(IAsyncResult res)
{
    var clientSocket = listener.EndAccept(res);

    //begin accepting again
    _listener.BeginAccept(OnAccept, null);

   clientSocket.BeginReceive(xxxxxx, OnRead, clientSocket);
}

void OnReceive(IAsyncResult res)
{
    var socket = (Socket)res.Asyncstate;

    var bytesRead = socket.EndReceive(res);
    socket.BeginReceive(xxxxx, OnReceive, socket);

    //handle buffer here.
}

Note that I've removed all error handling to make the code cleaner. That code do not block any thread and is therefore much more effecient. I would break the code up in two classes: the server handling code and the client handling code. It makes it easier to maintain and extend.

Next thing to understand is that TCP is a stream protocol. It do not guarentee that a message arrives in one Receive. Therefore you must know either how large a message is or when it ends.

The first solution is to prefix each message with an header which you parse first and then continue reading until you get the complete body/message.

The second solution is to put some control character at the end of each message and continue reading until the control character is read. Keep in mind that you should encode that character if it can exist in the actual message.

残月升风 2024-11-01 23:32:49

您需要发送固定长度的消息或在标头中包含消息的长度。尝试使用一些可以让您清楚地识别数据包开始的东西。

You need to send fixed length messages or include in the header the length of the message. Try to have something that allows you to clearly identify the start of a packet.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文