【问题标题】:TcpClient dispose orderTcpClient 配置命令
【发布时间】:2016-05-16 01:41:20
【问题描述】:

我正在 TCP 之上编写一个网络层,我在 UnitTest 阶段遇到了一些麻烦。

这是我正在做的(我的库由多个类组成,但我只向您展示导致我的问题的本机指令,以限制帖子的大小):

private const int SERVER_PORT = 15000;
private const int CLIENT_PORT = 16000;
private const string LOCALHOST = "127.0.0.1";

private TcpClient Client { get; set; }
private TcpListener ServerListener { get; set; }
private TcpClient Server { get; set; }

[TestInitialize]
public void MyTestInitialize()
{
    this.ServerListener = new TcpListener(new IPEndPoint(IPAddress.Parse(LOCALHOST), SERVER_PORT));
    this.Client = new TcpClient(new IPEndPoint(IPAddress.Parse(LOCALHOST), CLIENT_PORT));

    this.ServerListener.Start();
}

// In this method, I just try to connect to the server
[TestMethod]
public void TestConnect1()
{
    var connectionRequest = this.ServerListener.AcceptTcpClientAsync();

    this.Client.Connect(LOCALHOST, SERVER_PORT);

    connectionRequest.Wait();

    this.Server = connectionRequest.Result;
}

// In this method, I assume there is an applicative error within the client and it is disposed
[TestMethod]
public void TestConnect2()
{
    var connectionRequest = this.ServerListener.AcceptTcpClientAsync();

    this.Client.Connect(LOCALHOST, SERVER_PORT);

    connectionRequest.Wait();

    this.Server = connectionRequest.Result;

    this.Client.Dispose();
}

[TestCleanup]
public void MyTestCleanup()
{
    this.ServerListener?.Stop();
    this.Server?.Dispose();
    this.Client?.Dispose();
}

首先,如果我想更早地从同一端点连接到同一端口上的服务器,我必须首先处理服务器:

如果你像这样运行我的测试,它会在第一次成功运行。 第二次,在两个测试中,它都会在 Connect 方法上抛出一个异常,认为端口已经在使用中。

我发现避免这种异常的唯一方法(并且能够从同一个端点连接到同一个侦听器)是通过向已处理的客户端发送字节两次(在第一次发送时,没有问题,只有在第二次发送时才抛出异常。

如果我引发异常,我什至不需要 Dispose Server ...

为什么Server.Dispose() 没有关闭连接并释放端口???有没有比引发异常更好的方法来释放端口?

提前致谢。

(对不起我的英语,我不是母语)


这是一个主函数中的示例,更容易结帐:

private const int SERVER_PORT = 15000;
private const int CLIENT_PORT = 16000;
private const string LOCALHOST = "127.0.0.1";

static void Main(string[] args)
{
    var serverListener = new TcpListener(new IPEndPoint(IPAddress.Parse(LOCALHOST), SERVER_PORT));
    var client = new TcpClient(new IPEndPoint(IPAddress.Parse(LOCALHOST), CLIENT_PORT));

    serverListener.Start();

    var connectionRequest = client.ConnectAsync(LOCALHOST, SERVER_PORT);

    var server = serverListener.AcceptTcpClient();

    connectionRequest.Wait();

    // Oops, something wrong append (wrong password for exemple), the client has to be disposed (I really want this behavior)
    client.Dispose();

    // Uncomment this to see the magic happens
    //try
    //{
        //server.Client.Send(Encoding.ASCII.GetBytes("no problem"));
        //server.Client.Send(Encoding.ASCII.GetBytes("oops looks like the client is disconnected"));
    //}
    //catch (Exception)
    //{ }

    // Lets try again, with a new password for example (as I said, I really want to close the connection in the first place, and I need to keep the same client EndPoint !)
    client = new TcpClient(new IPEndPoint(IPAddress.Parse(LOCALHOST), CLIENT_PORT));

    connectionRequest = client.ConnectAsync(LOCALHOST, SERVER_PORT);

    // If the previous try/catch is commented, you will stay stuck here, 
    // because the ConnectAsync has thrown an exception that will be raised only during the Wait() instruction
    server = serverListener.AcceptTcpClient();

    connectionRequest.Wait();

    Console.WriteLine("press a key");
    Console.ReadKey();
}

如果触发错误并且程序拒绝让您连接,您可能需要重新启动 Visual Studio(或等待一段时间)。

【问题讨论】:

  • 您似乎正在测试 .NET 的 TcpClientTcpListener。你应该测试你自己的代码,而不是框架的。
  • 正如我所说,我正在 Tcp 之上编写一个网络层。但我只给你看那些给我带来麻烦的指示。我不会在我的整个封装代码中向您展示它们,以免淹没您。我将编辑我的介绍,使其更加明确!

标签: c# mstest tcpclient


【解决方案1】:

您的端口在使用中。运行netstat 看看。您会发现在 TIME_WAIT 状态下仍然打开的端口。

因为您没有优雅地关闭套接字,网络层必须保持这些端口打开,以防远程端点发送更多数据。否则,套接字可能会接收到用于其他目的的虚假数据,从而破坏数据流。

解决此问题的正确方法是优雅地关闭连接(即使用Socket.Shutdown() 方法)。如果您想包含一个涉及远程端点崩溃的测试,那么您还需要正确处理该场景。一方面,您应该设置一个独立的远程进程,您实际上可以崩溃。另一方面,您的服务器应该正确地适应这种情况,在适当的时间过去之前不要尝试再次使用该端口(即该端口实际上已关闭并且不再位于 TIME_WAIT 中)。

关于后一点,您可能需要考虑实际使用您发现的解决方法:TIME_WAIT 涉及远程端点状态未知的场景。如果您发送数据,网络层可以检测到失败的连接并提前进行套接字清理。

有关其他见解,请参阅例如:
Port Stuck in Time_Wait
Reconnect to the server
How can I forcibly close a TcpListener
How do I prevent Socket/Port Exhaustion?

(但不要使用答案中的建议来使用SO_REUSEADDR/SocketOptionName.ReuseAddress...这样做只是隐藏问题,并可能导致实际代码中的数据损坏。)

【讨论】:

  • 调用Dispose() 不能正常关闭套接字?我对 .NET 框架 T_T 失去了信心。除了笑话,在TcpSocket.Dispose() 之前调用TcpSocket.Client.Shutdown() 也无济于事。我的港口还在 TIME_WAIT ...
  • 不...为什么会这样?正常关闭需要两个端点的合作。 Dispose() 是一种片面的操作。 “调用 TcpSocket.Client.Shutdown()...没有帮助” - 那么你做错了。双方都需要使用Shutdown():发起方调用,值为...Send;响应方将使用...Both。每一方只有在完成发送数据后才会调用该方法;响应方只有在到达套接字数据流的末尾时才会调用...Both(即接收操作以零字节长度完成)。
  • 我尝试了您的解决方案。它仅在服务器(由 TcpListener 实例化的 TcpClient)启动关闭时才起作用。首先在服务器上调用Dispose 也可以。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-01-28
  • 1970-01-01
  • 2014-09-07
  • 2012-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多