我正在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(或等待一段时间)。
答案 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>