.NET C# 同步接收不会阻塞。

6

最近我遇到了一个奇怪的.Net同步接收方法的行为。我需要编写一个应用程序,其中节点通过发送/接收数据进行通信。每个服务器都有一个接收循环,该循环是同步的,在接收序列化类之后,它会对其进行反序列化和处理。之后,它使用AsynchSendTo异步地将此序列化类发送到一些选择的节点。

MSDN明确指出:

"如果您正在使用面向连接的Socket,则Receive方法将读取尽可能多的可用数据,直到缓冲区的大小为止。如果远程主机使用Shutdown方法关闭Socket连接,并且已接收到所有可用数据,则Receive方法将立即完成并返回零字节。"

在我的情况下,这不是真的。有一些随机情况,当建立连接后,Receive不会阻塞并立即返回0字节(非确定性情况)。我100%确定发送方至少发送了1000字节。还有一个有趣的事实:在接收之前加入Sleep(500),一切都正常工作。以下是接收代码:

_listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
    _listener.Bind(_serverEndpoint);
    _listener.Listen(Int32.MaxValue);
    while (true)
    {
        Console.WriteLine("Waiting for connection...");
        Socket handler = _listener.Accept();

        int totalBytes = 0;
        int bytesRec;
        var bytes = new byte[DATAGRAM_BUFFER];
        do
        {
            //Thread.Sleep(500);
            bytesRec = handler.Receive(bytes, totalBytes, handler.Available, SocketFlags.None);
            totalBytes += bytesRec;
        } while (bytesRec > 0);

        handler.Shutdown(SocketShutdown.Both);
        handler.Close();
    }
}
catch (SocketException e)
{
    Console.WriteLine(e);
}

还有发送部分:
public void AsynchSendTo(Datagram datagram, IPEndPoint recipient)
{

    byte[] byteDatagram = SerializeDatagram(datagram);
    try
    {
        var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        socket.BeginConnect(recipient, ConnectCallback, new StateObject(byteDatagram, byteDatagram.Length, socket));
    }
    catch (SocketException e)
    {
        Console.WriteLine(e);
    }
}

public void ConnectCallback(IAsyncResult result)
{
    try
    {
        var stateObject = (StateObject)result.AsyncState;
        var socket = stateObject.Socket;
        socket.EndConnect(result);
        socket.BeginSend(stateObject.Data, 0, stateObject.Data.Length, 0, new AsyncCallback(SendCallback), socket);
    }
    catch (Exception ex)
    {
        Console.WriteLine("catched!" + ex.ToString());
    }
}

public void SendCallback(IAsyncResult result)
{
    try
    {
        var client = (Socket)result.AsyncState;
        client.EndSend(result);
        client.Shutdown(SocketShutdown.Both);
        client.Close();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }
}

class StateObject
{
    public Byte[] Data { get; set; }
    public int Size;
    public Socket Socket;
}

我的问题是:我是否错误地使用了同步接收?为什么它没有阻塞,即使有数据可以接收?


由于您的问题并不特别涉及到在读取之前进行睡眠解决的问题。如果一次读取了很多内容,每个循环半秒钟的延迟可能会对延迟产生很大影响,您是否尝试过在读取后使用更小的睡眠时间(如100毫秒)进行睡眠?这对我从串行端口读取数据时有效,但那也是在数据接收事件发生时。 - Jeff LaFay
这不是 Socket 工作的方式。通过删除所有 try/catch 块来开始诊断此问题。 - Hans Passant
2个回答

6

你可能在这里遇到了并发问题。在接受连接后,你直接进入接收状态。发送进程可能没有足够的时间来调用发送操作,因此你的handler.Available为0,接收操作返回。

这也是为什么当你添加500毫秒的休眠时,“bug”不会出现的原因。


我认为我也解释了为什么500毫秒可以解决它。我没有复制你的答案... - Tudor
抱歉,我的意图并不是让你的答案看起来像是从我的复制过来的。尽管如此,它们本质上是相同的 - 我的回答隐含地解决了延迟行为的不同之处,使用了“在最开始时...”这句话。 ;) - Lucero

6
你正在自毁前程。
bytesRec = handler.Receive(bytes, totalBytes, handler.Available, SocketFlags.None);

在连接的最开始,Available 的值为0,这会强制它立即返回0。相反,你应该指定缓冲区中剩余的空闲字节数(例如:bytes.Length-totalBytes),这样才会阻塞。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接