非阻塞UDP Socket .NET中的“无法立即完成非阻塞套接字操作”异常。

3

我用C#编写了一个小型UDP客户端服务器类,用于在Linux和Windows机器之间提供通信。

C#中UDP客户端和服务器的实现是从我最初为Linux编写的C++代码直接重写而来的。

在Linux机器之间运行时没有问题,但是Linux和Windows之间链接时偶尔会出现问题。

由于应用程序需要快速、非阻塞操作UDP套接字,因此出现了这个问题。

由于一个客户端是Linux,所以在C#下的代码我不得不使用一些魔法进行编组。

以下是代码:

    public bool Connect(string sIPAddr, int portNumber)
    {
        try
        {
            if (portNumber > 65535 && portNumber < 0)
            {
                this._isReady = false;
                return this._isReady;
            }

            this._ipPort = portNumber;
            this._ipAddress = IPAddress.Parse(sIPAddr);
            IPEndPoint ipep = new IPEndPoint(this._ipAddress, this._ipPort);
            this._myUDPClient = new Socket(ipep.Address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
            this._myUDPClient.Blocking = false;
            this._myUDPClient.Connect(this._ipAddress, this._ipPort);

            this._isReady = true;
            return this._isReady;
        }
        catch (Exception)
        {
            this._isReady = false;
            return this._isReady;
        }
    }

我使用UDP连接来简化发送和接收调用。

问题出现在我尝试从套接字中读取数据时。

更多代码:

    public bool NewMessageReceived()
    {
        try
        {
            if (this._newMessaageReceived)
            {
                return this._newMessaageReceived;
            }
            else
            {
                _messageBuffer = new byte[65507];
                int numBytesRcvd = _myUDPClient.Receive(this._messageBuffer, 65507, SocketFlags.None);
                Marshal.Copy(_messageBuffer, 0, _pmessageBuffer, 65507);

                if (numBytesRcvd < 0)
                {
                    this._newMessaageReceived = false;

                    // TODO: Add Socket Error Checking
                }
                else
                {
                    this._newMessaageReceived = true;
                }

                Array.Clear(_messageBuffer, 0, _messageBuffer.GetLength(0));
                return this._newMessaageReceived;
            }
        }
        catch (Exception e)
        {
            System.Windows.Forms.MessageBox.Show(e.Message);
            return false;
        }
    }

我在两台机器上运行Wireshark,可以看到从Linux机器发送的数据包安全地到达Windows机器。但是UDP客户端Receive调用会抛出异常,错误信息为:"A non-blocking socket operation could not be completed immediately",我理解这是WSAEWOULDBLOCK错误。然而,我明确将阻塞选项设置为false。
事件顺序如下: Windows机器在端口2上发送数据报,并在端口1上监听确认消息。我使用了while循环来实现超时。
代码:
        DateTime TimeAtStart = new DateTime();
        TimeAtStart = DateTime.Now;
        TimeSpan TimeOut = new TimeSpan(0,0,0,0,800);

        IntPtr RecievedTelPtr = new IntPtr();
        bool UnpackingResult;

        while (TimeOut > (DateTime.Now - TimeAtStart))
        {
            if (!NackAckRecieveConnection.GetIsReady())
            {
                ErrorEventArguements.SetAllHmiNetworkEventArgs(ID, -3, 2);
                return (false);
            }
            if (NackAckRecieveConnection.NewMessageReceived())
            {
                RecievedTelPtr = NackAckRecieveConnection.GetMessage();
                UnpackingResult = UnpackHmiTelegram(RecievedTelPtr, AckNackType);
                NackAckRecieveConnection.MessageRetrieved();
                return (UnpackingResult);
            }
        }
        //if escape loop return timeout err msg
        ErrorEventArguements.SetAllHmiNetworkEventArgs(ID, -4, (AckNackType == 0) ? (1) : (3));
        return (false);

我希望能够理解问题所在,为什么会出现这个问题,以及如何修复它,因为我已经没有任何创意了。谢谢。

2
将Blocking标志设置为false,告诉套接字如果数据不存在,不要阻塞,在这种情况下你会收到错误信息。如果Blocking标志为true,调用将仅阻塞直至接收到信息并从调用中返回。你需要做一些其他的事情,然后稍后再尝试。 - undefined
消息接收如何触发/调用? - undefined
我修改了原始帖子,包括如何调用MessageReceived。基本上它在一个while循环中,这是一个超时操作。 - undefined
2个回答

2

我不会回答这个问题,但我需要指出一些非常重要的事情:

    catch (Exception)
    {
        this._isReady = false;
        return this._isReady;
    }

不要像这样隐藏异常。当某些东西失败时,你将没有任何机会尝试修复它,因为你永远不会知道为什么出现了故障。请使用适当的异常处理。
由于我需要快速、非阻塞操作UDP套接字才能运行应用程序,但这种说法是不正确的。非阻塞套接字并不更快,只是在操作完成之前返回。
我建议您切换回阻塞式套接字,因为您似乎是新手套接字编程。首先让应用程序运行起来,然后再尝试优化它。

1
@Serge: 很遗憾,我倾向于同意jgauffin的观点。非阻塞套接字虽然更有趣,可以让你用更少的线程管理更多的并发连接,但并不更快。它们也更复杂,所以除非你有理由使用它们,否则不应该使用。看着你的while循环,实际上它还是在套接字上阻塞,所以你并没有从使用非阻塞套接字中获得明显的好处。 - undefined
猜测一下@Serge评论并删除了评论?非阻塞套接字并不更快。可以自由进行基准测试。会降低性能的是为每个客户端使用一个线程,但不是直接地,需要有一大批同时连接的客户端出现才会发生这种情况(当发生这种情况时,我建议您切换到异步套接字方法而不是非阻塞套接字)。在真正需要之前,请不要做任何更复杂的事情。 - undefined
不详细说明,每个服务器只有一个客户端。Linux机器是一个硬实时系统,从Windows UI机器接收命令并执行相应的任务。我同意你关于非阻塞套接字的说法,但正如我所说,我不经常为Windows编写代码,也不太熟悉.NET,因此我重新编写了适用于实时环境的C++套接字实现。因此,我不能坐在那里无限等待一个阻塞套接字,我必须分配时间,并在失败时跳过它。因此,非阻塞给我更多的自由。所描述的问题与.NET有关。 - undefined
然后在Linux应用程序中切换到异步方法,而不是非阻塞套接字,并在Windows应用程序中坚持使用阻塞套接字。这样可以强制你以一种特定的方式编码,并且Linux应用程序仍然具有响应性,因为异步方法(BeginReceive/EndReceive)不会阻塞。 - undefined
据我所了解,您在这两个应用程序中都使用了.NET平台?如果是的话,我的建议将会让您的工作更加轻松。如果不是的话,只需更改Windows应用程序即可。您是否查看过MSDN上的示例:http://msdn.microsoft.com/en-us/library/system.net.sockets.udpclient.aspx? - undefined
显示剩余3条评论

0
你正在将从中读取消息的套接字设置为非阻塞。这会指示套接字在操作无法立即完成时不会阻塞。实际上,这意味着如果你尝试从套接字读取数据,而没有等待读取的内容,调用将不会成功返回。
我不知道如何调用MessageReceived,但我认为调用它的任何东西在调用之前都没有检查套接字是否真的准备好被读取。
由于你遇到的是间歇性问题,这表明大部分时间在调用MessageReceived时,套接字中有可读取的数据。
如果你想继续使用非阻塞IO,你需要改变逻辑,以便在捕获IO异常并经过短暂延迟后重试(如果你确定那里会有数据),或者在尝试执行读取之前检查套接字中是否确实有可读取的数据。
一种在尝试从套接字读取之前检查套接字上是否有信息可用的方法是使用Socket.Poll。类似于以下代码:
if (_myUDPClient.Poll(myTimeoutInMicroSeconds, SelectMode.SelectRead)){
       // Try to read the new messsage
} else {
    continue;  // go back to top of loop and try again
}

你可能还需要检查 SelectError 状态,以确定套接字是否发生故障。我的大部分套接字编程都是用 C++ 进行的,所以对于 .Net 的具体情况我不太确定。

在我的while循环中,只要定时器还没有过期,我会一次又一次地进行检查。一旦成功接收到消息,我会指示代码清除缓冲区并准备进行下一次接收。 - undefined
UDP保留消息边界,并在可供阅读之前自动进行分段和重组,就我所了解的情况而言。此外,在我的情况下,网络中飞行的UDP数据报大小设置为32字节。在C++中,我使用peek来检查是否有待读取的数据,我猜Poll在这里也会做类似的事情。非常感谢! - undefined

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