TcpClient处理订单

时间:2016-05-16 01:41:21

标签: c# mstest tcpclient

我正在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方法的两个测试中抛出一个异常,认为端口已经在使用。

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

如果我挑起异常,我甚至不需要处理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(或等待一段时间)。

1 个答案:

答案 0 :(得分: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的答案中找到的建议......所有这一切都会隐藏问题,并可能导致现实代码中的数据损坏。)< / p>