C#网络编程和资源使用

7

我一直在研究如何用C#编写“正确”的网络代码。

我看到很多例子都使用了C#的“using”语句,我认为这是一个不错的方法,但我也看到了在各种表达式中对它的不一致使用。

例如,假设我有以下代码:

TcpClient tcpClient = new TcpClient("url.com", 80);
NetworkStream tcpStream = tcpClient.GetStream();
StreamReader tcpReader = new StreamReader(tcpStream);
StreamWriter tcpWriter = new StreamWriter(tcpStream);

显然,这段代码非常脆弱。我看到有些代码将using放在tcpClient上,这似乎不错。但是,NetworkStream是否也有需要清理的资源?还有StreamReader/Writer呢?
我需要将所有4个语句包装在嵌套的using语句中吗?
如果需要,当时间到了需要进行处理时会发生什么?StreamWriter会关闭流,因此也会关闭套接字。那么当StreamReader、NetworkStream和TcpClient依次进行处理时会发生什么?
这带来了另一个问题。既然StreamReader和StreamWriter都由同一个流组成,那么谁拥有它?他们两个都认为自己拥有它,因此都会试图销毁它吗?还是框架知道流已经被销毁,只是悄悄地忽略它?
看起来,using语句似乎只对链中的最后一个语句必要,但是如果在GetStream()中抛出异常会发生什么?我认为它不会正确地清理套接字,因此似乎需要冗余的using以确保这种情况不会发生。
有没有人知道有关使用.NET和C#进行网络编程并包括有关异常处理和资源管理章节的好书籍或文章?或者在线上有哪些好文章?我能找到的所有书都是.NET 1.1时代的(如《Microsoft .NET Framework网络编程》、《.NET中的网络编程》等),因此这似乎是需要一些好资源的主题。
编辑:
请不要让Marc的很好的评论阻止其他人对此发表评论。
我想听到其他人对资源管理的书籍推荐或意见,特别是关于异步使用。
3个回答

14

通常,对象应该内部处理多个Dispose()调用,并且只执行一次主要代码;因此,流被多次Dispose()不是通常的问题。个人而言,我会使用大量using语句;请注意,您不需要缩进/嵌套,除非不同级别具有不同的生命周期:

using(TcpClient tcpClient = new TcpClient("url.com", 80))
using(NetworkStream tcpStream = tcpClient.GetStream())
using(StreamReader tcpReader = new StreamReader(tcpStream))
using(StreamWriter tcpWriter = new StreamWriter(tcpStream))
{
   ...
}
正如你所说,这可以确保在初始化期间发生错误时,仍会正确清理一切。这也确保每个级别都有机会(按正确顺序)正确处理任何缓冲数据等。
关于所有权问题; 实际上,NetworkStream本来就是一个奇怪的东西......大多数流要么是输入流xor输出流。 NetworkStream弯曲了一些规则,并将两个方向塞入一个API中;所以这是一个例外......通常所有权会更加清晰。此外,许多包装器都有一个标志来确定它们是否应关闭已包装的流。 StreamReader 没有,但有些有(例如具有leaveOpen ctor选项的GZipStream)。如果您不想流动所有权,则可以使用此选项-或使用非关闭流中介-这里有一个这里NonClosingStream 或类似) 。
至于书籍; 我买了一本《C#中的TCP / IP套接字:程序员实用指南》(此处)-尚可,但不是很好。

感谢您的反馈。这大致是我所怀疑的。我同意,NetworkStream是一个奇怪的东西。我不确定创建单独的读取和写入流是否会更少出现问题,但它就是这样。感谢您的书籍推荐。 - Erik Funkenbusch
另外,在您提到的网站上,我没有看到 NonClosingStream 的实现。 - Erik Funkenbusch
它在那里 - MiscUtil.IO.NonClosingStreamWrapper - Marc Gravell

0
如果一个对象支持IDisposable,最好将其放在using {}块中,因为dispose方法会自动调用。这也可以减少您的代码量。重要的是要注意,使用“using”不处理任何异常。如果您想处理任何错误,仍然需要这样做。一旦using块超出范围,您的对象也会超出范围。
Old Style Code

object obj;

try
{
   obj= new object();
   //Do something with the object
}
catch
{
   //Handle Exception
}
finally
{

  if (obj != null)
  {  
     obj.Dispose();
  }
}  

Newer Style Code

try
{   
  using (object obj = new object())
  {
     //Do something with the object
  }
catch
{
   //Handle Exception
}

-1

关于套接字呢? 这样做可以吗:

serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Connect(serverEndPoint, m_NegotiationPort);
.
.
.
serverSocket.Close();

或者更好
using (Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
{
.
.
.
}

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