(显然)优雅地关闭UDPClient会导致套接字被阻塞。

7
以下代码,尽管看起来关闭了UDP Socket,但它仍然挂起,并且无法重新连接到相同的地址/端口。
这些是我使用的类变量:
    Thread t_listener;
    List<string> XSensAvailablePorts;
    private volatile bool stopT_listener = false;        
    volatile UdpClient listener;
    IPEndPoint groupEP;

我使用一个方法创建和启动一个新的线程,该方法将处理Socket连接和监听:

private void startSocketButton_Click(object sender, RoutedEventArgs e)
        {
            stopT_listener = false;
            closeSocketButton.IsEnabled = true;
            startSocketButton.IsEnabled = false;
            t_listener = new Thread(UDPListener);
            t_listener.Name = "UDPListenerThread";
            t_listener.Start();
        }

以下是方法(我在Receive上使用了超时,以防止在套接字上没有发送任何内容并发出Stop时被阻塞):
    private void UDPListener()
    {
        int numberOfPorts = XSensAvailablePorts.Count();
        int currAttempt = 0;
        int currPort = 0;
        bool successfullAttempt = false;
        while (!successfullAttempt && currAttempt < numberOfPorts)
        {
            try
            {
                currPort = Int32.Parse(XSensAvailablePorts[currAttempt]);
                listener = new UdpClient(currPort);
                successfullAttempt = true;
            }
            catch (Exception e)
            {
                currAttempt++;
            }
        }
        if (successfullAttempt)
        {   
            //timeout = 2000 millis
            listener.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 2000);
            //Tried with and without, no change: listener.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            statusTB.Dispatcher.BeginInvoke((Action)delegate() { statusTB.Text = "Connected on port:" + currPort; });
            groupEP = new IPEndPoint(IPAddress.Parse("143.225.85.130"), currPort);

            byte[] receive_byte_array;
            try
            {
                while (!stopT_listener)
                {
                    try
                    {
                        receive_byte_array = listener.Receive(ref groupEP);
                        if (receive_byte_array.Length == 0 || receive_byte_array == null)
                            continue;

                        ParseData(receive_byte_array, samplingDatagramCounter);

                    }
                    catch (SocketException ex)
                    {
                        if (ex.SocketErrorCode == SocketError.TimedOut)
                            continue;
                    }


                }
            }
            catch (Exception e)
            {
                Debug.Print(e.Message);
            }
            finally
            {
                if (listener != null)
                {
                    listener.Client.Shutdown(SocketShutdown.Both);
                    listener.Close();
                }
            }
        }
        statusTB.Dispatcher.BeginInvoke((Action)delegate() { statusTB.Text = "Not Connected"; });
        return;
    }

我使用以下方法来停止线程/套接字:

thread.stop()

private void closeSocketButton_Click(object sender, RoutedEventArgs e)
        {
            stopT_listener = true;
            closeSocketButton.IsEnabled = false;
            startSocketButton.IsEnabled = true;
            t_listener.Join();
            if (listener.Client != null)
            {
                listener.Client.Shutdown(SocketShutdown.Both);
                listener.Close();
            }
            if (t_listener.IsAlive)
            {
                t_listener.Abort();
                statusTB.Text = "Aborted";
            }
            else
                statusTB.Text = "Not Connected";
        }

尽管我在调试中检查了套接字已经关闭,但是如果我重试连接到同一端口,由于引发SocketException,因此无法这样做,该异常消息为“一般只允许一个端口/地址使用一次”。


当你点击按钮时,listener.Client 是否为 null?如果是,那么你将不会调用 listener.Close() - Sinatr
@Sinatr 我已经在调试中检查过了,情况并非如此。 - Saverio Terracciano
.Close() 应该完全处理对象的释放。可能是其他原因,比如没有正确关闭套接字(可能由于某些未处理且被简单跳过的异常)。我不知道,因为我只是使用 .BeginReceive 异步方法来处理数据。;p - porkchop
6个回答

0

我认为绑定或重用可以解决这个问题(即使套接字还没有关闭也可以被重用而不会抛出错误) 示例代码:

udpClient = new UdpClient();
udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, p));

0

我也遇到了同样的问题,问题出在UDPClient.Receive()上,即使你调用Close/shutdown/...方法,她仍然会保持套接字处于使用状态。

try{ // receive loop}
catch{}
 finally {
UDP_Listner.Close();
                    UDP_Listner = null;
                }

编辑:

t_listener = new Thread(UDPListener);//replace by :
 t_listener = new Thread(new ThreadStart(UDPListener));

`

安全关闭套接字和线程(http://msdn.microsoft.com/zh-cn/library/system.threading.threadstart(v=vs.110).aspx)


0

将监听器对象设置为NULL,以释放资源,这也应该释放连接。


1
与其将对象设置为null并等待GC释放资源,我强制使用Close() / Dispose()释放资源,但仍然没有起作用。 - Saverio Terracciano

0
我将您提供的代码放在一个简单的表格中运行,但是...我无法直接重现您的问题。虽然我还没有向客户端发送任何数据,但据我了解,这不应该改变任何内容,因为它是UDP协议,而我们正在调查重新打开套接字,而不是传输数据。
当点击开始/停止按钮时,套接字始终正确打开和关闭,重新打开也按预期工作。 对于我来说,强制出现您提到的SocketException的唯一方法是引入一些明显的套接字逻辑误用:
  1. 运行两个应用程序实例并在两者中都单击“开始”。
  2. 删除Shutdown和Close的BOTH出现(Stop不会关闭套接字)。
  3. 运行应用程序,打开套接字,不关闭套接字关闭应用程序,再次运行应用程序,尝试打开套接字。
我在您的代码中做出的唯一更改是删除ParseData(...)行,并添加一些端口到XSensAvailablePorts列表中。
您能否检查端口在您明显关闭它后是否仍然打开?您可以使用netstat -an或出色的工具ProcessExplorer进行检查。您还可以检查t_listener线程是否正确终止(标准任务管理器或ProcessExplorer可以帮助您)。

1
我无法访问我正在通信的硬件/软件(正如您可能从我正在访问XSens身体套装的套接字流的变量名称中了解到的那样),但我将在下周进行检查。我已经遇到了这个组件的数据流的很多问题,我怀疑它可能是服务器出了问题导致一些东西出错。 - Saverio Terracciano

0

我有同样的问题,我是一个最安全的程序员,我总是很好地关闭所有东西。但我发现 .net 类不能快速地关闭 socket。因为如果我慢慢操作就不会出现这种情况,但如果我快速地打开、关闭(完全清理)并再次打开它,我会得到同样的错误,特别是如果用户想再次运行相同的代码并重新打开端口。


这是一个答案还是一个评论? - heltonbiker

0

可能是一个老的答案,但在你尝试寻找可用端口时失败了,我建议在下一次迭代之前处理你测试过的监听器实例。

            try
            {
                currPort = Int32.Parse(XSensAvailablePorts[currAttempt]);
                listener = new UdpClient(currPort);
                successfullAttempt = true;
            }
            catch (Exception e)
            {
                currAttempt++;

                if(listener != null)
                {
                   listener.Close();
                }
            }

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