TcpClient:如何关闭并重新连接它?

9

你好,感谢您的帮助。 这次我想问一下TcpClient相关的问题。 我有一个服务器程序,正在编写一个客户端程序。 这个客户端使用TcpClient。它通过创建一个新的客户端来启动。

clientSocket=new TcpClient(); 

(顺便问一下,这会导致异常吗?以防万一我把它放在try-catch中,但我不确定是否真的需要) 无论如何,稍后我进入一个循环,在这个循环内我连接到服务器。

clientSocket.Connect("xx.xx.xx.xx",port);

然后我使用以下代码创建一个NetworkStream:

clientStream=clientSocket.GetStream();

然后通过Read开始等待从服务器获取数据。我知道这是阻塞的,因此我还设置了ReadTimeOut(例如1秒)。

到目前为止还不错。稍后,如果我没有从服务器收到任何内容,我会尝试向其发送一些内容。如果连续发生3次,我想要关闭连接并重新连接到服务器

(注意,当服务器某种方式宕机时会出现完全不同的问题,因为这会在客户端中引起其他类型的错误-也许我以后会再问这个问题)

那么,我该怎么做呢?

 if(clientSocket.Connected)
            {
                Console.WriteLine("Closing the socket");
                clientSocket.Close();
            }

我关闭了socket连接。 循环结束后,我会再次回到开头尝试连接服务器。

clientSocket.Connect("xx.xx.xx.xx",port);

然而,这会导致一个错误(实际上是未处理的异常)"无法访问已释放的对象"。
所以,我的问题是如何关闭并重新连接到服务器? 再次感谢任何帮助。

谢谢。我添加了一个标签,可以吗?(顺便问一下,在注释中如何换行...如果我按回车键,它只会添加注释。尝试过Ctrl-Enter、Shift-Enter,但都不起作用...) - KansaiRobot
1
是的,这太好了。谢谢!注释不支持段落或换行符。它们总是一块连续的文本。它们是注释,不是段落。:-) 很少(如果有的话)需要一个长到足以需要多个段落的注释。 - Ken White
只有在明确知道要捕获哪个异常并且可以有意义地处理它时,才应该将代码放在 try/catch 块中。仅仅捕获异常是一种反模式。请参见 https://blogs.msdn.microsoft.com/ericlippert/2008/09/10/vexing-exceptions/。 - Enigmativity
3个回答

15

TcpClient 实例只能用于一次连接。您可以简单地实例化一个新的 TcpClient,而不是尝试重新打开已关闭的实例。


谢谢。是这样吗?所以我必须实例化一个新的……在我的一些C++代码中,他们使用套接字并且只是“创建”一个新的套接字(他们正在使用异步)。但在TcpClient中没有“创建”,所以我想应该只是使用一个新的,对吧?顺便说一句,我还有另一个相关的问题,等我解决了这个问题后再发布它。非常感谢您的帮助 :) - KansaiRobot
顺便问一下,实例化一个新的TcpClient会引发异常吗?我应该把它放在try-catch里面吗? - KansaiRobot
1
看起来它正在工作 :) 顺便问一下,当我关闭TcpClient时,所有资源都被处理了吗?还是垃圾回收器会处理它?我不想每次执行新的实例化迭代时都留下东西..... - KansaiRobot
1
我不认为构造函数会抛出异常。TcpClient 实现了 IDisposable 接口,这意味着当你在它上面调用 Dispose 方法时,它会自行清理。使用 IDisposable 的惯例是使用 using 语句。请参阅 https://msdn.microsoft.com/zh-cn/library/yh598w02.aspx。 - Curtis Lusmore
3
“我不认为构造函数可以抛出异常”是错误的。构造函数确实可以抛出异常。TcpClient 在构造过程中会创建其底层的 Socket 对象,并且 Socket 构造函数 如果本机 socket 对象无法创建将抛出 SocketException 异常。 - Peter Duniho
你说得对,反编译TcpClient的无参数构造函数显示它确实会使用new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);来实例化一个套接字,这可能会抛出SocketException异常。 - Curtis Lusmore

11

正如在其他答案中解释的那样,TcpClient对象只能连接一次。如果你想重新连接到服务器,你必须创建一个新的TcpClient对象并再次调用Connect()方法。

话虽如此,你在问题中存在一些明显的误解:

  1. 首先且最重要的是,如果你打算再次使用TcpClient对象(例如向服务器发送一些数据),你不应该使用ReceiveTimeout。一旦超时时间到期,底层套接字便无法再使用。

    如果你想在没有从服务器接收到数据的情况下定期向服务器发送数据,你应该使用异步I/O(尽管它有一定的学习曲线),并使用常规计时器对象来跟踪你从服务器接收数据以来的时间长度。
  2. TcpClient构造函数可能会抛出异常。至少,任何尝试创建引用类型对象的new操作都可能抛出OutOfMemoryException异常,并且在TcpClient中,它最终尝试创建本地套接字句柄,这也可能失败。

    虽然所有I/O对象和方法都可能抛出异常,但你只应该捕获自己能够优雅处理的异常。因此,在将try/catch块添加到代码之前,请决定在发生异常时要执行什么操作,以确保代码不会破坏任何数据并继续正常运行。通常情况下,无法优雅地处理OutOfMemoryException(并且在任何情况下都难以保护new的所有用途),但你肯定可以捕获SocketException,因为它可能由构造函数抛出。如果抛出该异常,则应立即放弃创建和使用TcpClient的尝试,并向用户报告错误,以便他们可以尝试纠正阻止套接字创建的任何问题。
  3. 如果服务器预期向你发送数据,并且你没有收到数据,则关闭连接并重试不太可能改善情况。这只会给服务器增加额外的负载,使其更有可能无法响应。同样地,重复发送相同的数据也不会有所改变。你应该发送请求一次,等待足够长的时间以获取来自服务器的响应,如果在期望的时间内没有收到响应,则向用户报告错误并让他们决定下一步该怎么做。

    请注意,在这种情况下,你可以使用ReceiveTimeout属性,因为如果在规定时间内没有收到响应,则所有你要做的就是放弃连接,这是没问题的。

谢谢您的建议。我想您是在说ReadTimeout属性吗? - KansaiRobot
@KansaiRobot:TcpClient 上的相关属性是 ReceiveTimeout。但是,没错,这等同于 NetworkStream.ReadTimeout。两者最终都只是调用 Socket.SetSocketOption() 并设置 SocketOptionName.ReceiveTimeout - Peter Duniho
@PeterDuniho 我不同意你所说的第三点。TCP 连接可能会中断... 服务器可能会重新启动,也可能是从 Azure 到 ISP 到本地网络中的任何地方出现了网络故障。不过除此之外,这是一个非常好的答案,谢谢你。 - Mr. Boy

7
非常简单:
client.Close();
client = new TcpClient();
client.Connect(host, port);

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