【问题标题】:How can I unbind a socket in C#?如何在 C# 中取消绑定套接字?
【发布时间】:2011-02-18 18:36:00
【问题描述】:

在我制作的测试应用程序中重用服务器套接字时遇到了一些问题。基本上,我有一个同时实现客户端和服务器端的程序。我运行该程序的两个实例以进行测试,一个实例开始托管,另一个实例连接。这是监听代码:

private void Listen_Click(object sender, EventArgs e)
{
    try
    {
        server = new ConnectionWrapper();
        HideControls();
        alreadyReset = false;

        int port = int.Parse(PortHostEdit.Text);
        IPEndPoint iep = new IPEndPoint(IPAddress.Any, port);

        server.connection.Bind(iep); // bellow explanations refer to this line in particular
        server.connection.Listen(1);
        server.connection.BeginAccept(new AsyncCallback(OnClientConnected), null);
        GameStatus.Text = "Waiting for connections on port " + port.ToString();
    }
    catch (Exception ex)
    {
        DispatchError(ex);
    }
}
private void OnClientConnected(IAsyncResult iar)
{
    try
    {
        me = Player.XPlayer;
        myTurn = true;
        server.connection = server.connection.EndAccept(iar); // I will only have one client, so I don't care for the original listening socket.
        GameStatus.Text = server.connection.RemoteEndPoint.ToString() + " connected";
        StartServerReceive();
    }
    catch (Exception ex)
    {
        DispatchError(ex);
    }
}

这第一次运行良好。然而,过了一会儿(当我的小游戏结束时),我在 server 对象上调用 Dispose(),实现如下:

public void Dispose()
{
    connection.Close(); // connection is the actual socket
    commandBuff.Clear(); // this is just a StringBuilder
}

我在对象构造函数中也有这个:

public ConnectionWrapper()
{
    commandBuff = new StringBuilder();
    connection = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

    connection.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
}

当我再次单击Listen 按钮时,我没有收到任何错误消息。客户端连接得很好,但是我的服务器端没有第二次检测到客户端连接,这基本上使服务器无论如何都无用。我猜它正在连接到旧的、挥之不去的套接字,但老实说,我不知道为什么会发生这种情况。这是客户端连接代码:

private void Connect_Click(object sender, EventArgs e)
{
    try
    {
        client = new ConnectionWrapper();
        HideControls();
        alreadyReset = false;

        IPAddress ip = IPAddress.Parse(IPEdit.Text);
        int port = int.Parse(PortConnEdit.Text);
        IPEndPoint ipe = new IPEndPoint(ip, port);
        client.connection.BeginConnect(ipe, new AsyncCallback(OnConnectedToServer), null); 
    }
    catch (Exception ex)
    {
        DispatchError(ex);
    }
}

如果我在 CMD 中执行netstat -a,我看到我使用的端口仍然是绑定的,并且它的状态是LISTENING,即使在调用Dispose() 之后也是如此。我读到这是正常的,并且该端口“未绑定”存在超时。

有没有办法强制该端口解除绑定或设置一个非常短的超时时间,直到它自动解除绑定?现在,只有当我退出程序时它才会解除绑定。也许我在我的服务器上做错了什么?如果是这样,那会是什么?为什么客户端可以正常连接,但是服务端检测不到第二次?

我可以让套接字始终监听,而不是释放它,并使用单独的套接字来处理服务器连接,这可能会修复它,但我希望其他程序能够在连续播放会话之间使用该端口。

我记得看到另一个问题问这个问题,但我的情况没有令人满意的答案。

【问题讨论】:

  • 当您运行netstat 时,您是否看到它处于TIME_WAIT 状态?
  • @Aaronaught:不,上面写着LISTENING

标签: c# .net sockets asynchronous


【解决方案1】:

端口保持打开状态可能有几个原因,但我认为您应该能够通过在套接字上使用明确的LingerOption 来解决您的问题:

LingerOption lo = new LingerOption(false, 0);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, lo);

这基本上将套接字关闭变为中止关闭而不是正常关闭。如果您希望它优雅但不要等待那么久,请在构造函数中使用 true 并为超时指定一个小但非零的值。

我刚刚注意到这一行,这几乎毫无疑问是您的问题的一部分:

server.connection = server.connection.EndAccept(iar); // I will only have one client, so I don't care for the original listening socket.

您在这里写的评论是错误的。您的包装类真的不应该允许写入connection。但是您不能简单地将侦听套接字替换为客户端套接字 - 它们是两个不同的套接字!

这里将发生的事情是 (a) 监听套接字超出范围,因此永远不会被明确关闭/处置 - 这将在随机时间发生,可能是在一个令人讨厌的时间。并且 (b) 您关闭的套接字只是客户端套接字,它不会关闭侦听套接字,因此您在重新绑定另一个侦听套接字时遇到问题也就不足为奇了。

您实际看到的不是套接字超时,而是垃圾收集器意识到侦听套接字已死并释放/完成它所需的时间。要解决这个问题,您需要停止覆盖侦听套接字;您的包装类的Dispose 方法应处理原始 监听套接字,并且应单独跟踪客户端套接字,并在您实际完成时处理它。

事实上,你根本不需要重新绑定另一个监听套接字。监听套接字始终保持活动状态。实际连接仅由客户端套接字表示。您应该只需要在最终关闭服务器时释放监听套接字。

【讨论】:

  • 这不起作用。如果我使用它而不是重用地址选项,我会得到“只有一个连接......”错误,如果我同时使用我描述的相同的事情发生。
  • @IVlad:我已经更新了这个答案,我花了一段时间才意识到发生了什么,直到我读到其中一个 cmets 才注意到......
  • 太好了,解决了,谢谢。那将教会我再次过早地进行优化...如果您还想回答一个后续问题,我将非常感激:在我分配 @ 返回的客户端套接字后,是否可以处理监听套接字987654327@ 到另一个套接字,或者我应该只在游戏真正结束时才将其丢弃?这两种方法现在似乎都有效,但正确的方法是什么?
  • @IVlad:老实说我从来没有尝试过,可能是因为我还没有编写一个只接受一个客户端的套接字服务器。我认为您可以在拥有客户端套接字后关闭侦听套接字,但实际上没有理由这样做,因为如果您将其设置为积压 1,侦听套接字无论如何都不会接受新连接. 至少按照惯例,“正确”的方式是一直监听直到服务器关闭。
  • 不需要使用 LingerOption(true, 0) 吗? LingerOption.Enabled 的文档说,如果它为 false,则使用默认超时,这与立即中止关闭不同。
【解决方案2】:

我同意前面的回答,您还应该“关闭”以允许任何现有活动完成,然后关闭标记它以供重用的套接字...

socket.Shutdown(SocketShutdown.Both);
socket.Disconnect(true);

【讨论】:

  • 有趣。这会阻止服务器。这里不是讽刺,但我不知道为什么这会阻止,因为我只在按钮点击时发送小字符串。我在想有什么东西阻止了连接,因此让它保持打开状态,但我不知道是什么。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-05
  • 1970-01-01
  • 2023-03-23
  • 2010-11-13
  • 2017-11-29
  • 1970-01-01
  • 2012-09-24
相关资源
最近更新 更多