简体   繁体   English

如何异步正确编写Socket / NetworkStream?

[英]How do I properly write Socket/NetworkStream asynchronously?

I want to use NetworkStream (or maybe Socket ) to read/write TCP connections. 我想使用NetworkStream (或可能是Socket )来读/写TCP连接。 I want to use non-blocking operations so that I don't have to deal with several threads, or deal with the issue of how to stop the program if some threads might be halted on blocking network operations. 我想使用非阻塞操作,这样我就不必处理多个线程,或者如果某些线程可能在阻塞网络操作时停止,则处理如何停止程序的问题。

The documentation of NetworkStream.Read implies it is non-blocking ("If no data is available for reading, the Read method returns 0"), so I guess I don't need async callbacks for reading... right? NetworkStream.Read文档暗示它是非阻塞的(“如果没有数据可供读取,Read方法返回0”),所以我想我不需要异步回调来读...对吗?

But what about writing? 但是写作呢? Well, Microsoft has one of its usual low-quality tutorials on this subject, and they propose writing a cumbersome AsyncCallback method for every operation. 好吧,微软有一个关于这个主题的常见低质量教程 ,他们建议为每个操作编写一个繁琐的AsyncCallback方法。 What's the point of the code inside this callback? 这个回调中的代码有什么意义?

client.BeginSend(byteData, 0, byteData.Length, SocketFlags.None,
                 new AsyncCallback(SendCallback), client);
    ...
private static void SendCallback(IAsyncResult ar)
{
    try {
        Socket client = (Socket)ar.AsyncState;

        int bytesSent = client.EndSend(ar);

        Console.WriteLine("Sent {0} bytes to server.", bytesSent);
        // Signal that all bytes have been sent.

        sendDone.Set();
    }
    catch (Exception e)
    {
        Console.WriteLine(e.ToString());
    }
}

The documentation of AsyncCallback says it's "a callback method that is called when the asynchronous operation completes". AsyncCallback的文档说它是“异步操作完成时调用的回调方法”。 If the operation is completed already, why do we have to call Socket.EndSend(IAsyncResult) ? 如果操作已经完成,为什么我们必须调用Socket.EndSend(IAsyncResult) Meanwhile, the documentation of NetworkStream.BeginWrite says "You must create a callback method that implements the AsyncCallback delegate and pass its name to the BeginWrite method. At the very minimum, your state parameter must contain the NetworkStream ." 同时, NetworkStream.BeginWrite的文档说“你必须创建一个实现AsyncCallback委托的回调方法,并将其名称传递给BeginWrite方法。至少,你的state参数必须包含NetworkStream 。”

But why "must" I? 但为什么“必须”我呢? Can't I just store the IAsyncResult somewhere... 我不能只是将IAsyncResult存储在某个地方......

_sendOp = client.BeginSend(message, 0, message.Length, SocketFlags.None, null, null);

...and periodically check whether it's finished? ...并定期检查它是否已完成?

if (_sendOp.IsCompleted)
    // start sending the next message in the queue

(I know this implies polling, but it will be in a server that is expected to be active most of the time, and I'm planning some Thread.Sleeps when idle.) (我知道这意味着轮询,但它将在一个服务器中,预计在大多数时间都处于活动状态,并且我在空闲时计划一些Thread.Sleep。)

Another issue. 另一个问题。 The messages I'm sending are broken up into a header array and a body array. 我发送的消息被分解为头数组和主体数组。 Can I can issue two BeginWrites in a row? 我可以连续发出两个BeginWrites吗?

IAsyncResult ar1 = _stream.BeginWrite(header, 0, header.Length, null, null);
IAsyncResult ar2 = _stream.BeginWrite(msgBody, 0, msgBody.Length, null, null);

Also, any opinions are welcome about whether to use Socket or NetworkStream. 此外,欢迎任何关于是否使用Socket或NetworkStream的意见。

I don't have any experience with NetworkStream , but I can say this about asynchronous Socket operations: 我对NetworkStream没有任何经验,但我可以说这是关于异步Socket操作:

  • End* needs to be called when the operation completes because this cleans up OS resources used by the asynchronous operation and allows you to get the result of the operation (even if that result is just whether it succeeded or failed). 操作完成时需要调用End* ,因为这会清除异步操作使用的OS资源,并允许您获取操作的结果(即使该结果只是成功还是失败)。 MSDN has a good overview of this common asynchronous pattern. MSDN对这种常见的异步模式有很好的概述
  • You are not required to pass any particular object as the state parameter. 不需要将任何特定对象作为state参数传递。 When that documentation was written, passing it was the easiest way to keep the object in scope for the completion delegate. 编写该文档时,传递它是将对象保留在完成委托范围内的最简单方法。 These days, with lambda expressions, that practice is not nearly as common. 现在,使用lambda表达式,这种做法并不常见。
  • You can queue up writes to the socket, and under most conditions this will work. 您可以将写入排队到套接字,在大多数情况下,这将起作用。 It is possible, though, that one of the writes will only partially complete (I've never seen this happen, but it is theoretically possible). 但是,有可能其中一个写入只会部分完成(我从未见过这种情况,但理论上可行)。 I did this for years but no longer recommend it. 我这样做多年但不再推荐它了。

I do recommend using completion delegates instead of polling. 我建议使用完成代理而不是轮询。 If you want an easier-to-use wrapper, you could try Nito.Async.Sockets , which wraps the Begin / End operations and serializes them all through a single thread (eg, a UI thread). 如果您想要一个更易于使用的包装器,您可以尝试Nito.Async.Sockets ,它包装Begin / End操作并通过单个线程(例如,UI线程)将它们序列化。

PS If you want to stop a blocking operation, you can close the socket from another thread, which will cause the operation to complete with an error. PS如果要停止阻塞操作, 可以从另一个线程关闭套接字,这将导致操作完成并显示错误。 However, I don't recommend using blocking socket operations. 但是,我不建议使用阻塞套接字操作。

The documentation of NetworkStream.Read implies it is non-blocking ("If no data is available for reading, the Read method returns 0"), so I guess I don't need async callbacks for reading... right? NetworkStream.Read的文档暗示它是非阻塞的(“如果没有数据可供读取,Read方法返回0”),所以我想我不需要异步回调来读...对吗?

I don't see how that would imply a non-blocking operation, unless you never plan for there to be any data available for reading. 我没有看到这意味着如何暗示非阻塞操作,除非您从未计划有任何可用于读取的数据。 If there's data available to be read, the operation will block for as long as it takes to read the data. 如果有可读数据,则只要读取数据,操作就会阻塞。


But what about writing? 但是写作呢? Well, Microsoft has one of its usual low-quality tutorials on this subject, and they propose writing a cumbersome AsyncCallback method for every operation. 好吧,微软有一个关于这个主题的常见低质量教程,他们建议为每个操作编写一个繁琐的AsyncCallback方法。 What's the point of the code inside this callback? 这个回调中的代码有什么意义?

The point is to avoid writing even more-cumbersome thread management code for yourself. 关键是要避免为自己编写更麻烦的线程管理代码。 As multi-threading goes, an AsyncCallback is about as straightforward as it gets, although this example is rather silly. 随着多线程的发展,AsyncCallback就像它获得的那样简单,尽管这个例子相当愚蠢。 In the example, the callback routine signals a ManualResetEvent ( sendDone.Set() ), which would be one way to avoid polling; 在该示例中,回调例程发出一个ManualResetEventsendDone.Set() )信号,这是避免轮询的一种方法; you would have a separate thread that waits on that ManualResetEvent , which causes it to block until something else calls its Set() method. 你将有一个单独的线程等待该ManualResetEvent ,这导致它阻塞,直到其他东西调用其Set()方法。 But wait, wasn't the main point of using an AsyncCallback in the first place to avoid writing your own thread management? 但是等等,首先使用AsyncCallback是不是要避免编写自己的线程管理? So why have a thread that's waiting on a ManualResetEvent ? 那么为什么有一个等待ManualResetEvent的线程呢? A better example would show actually doing something in your AsyncCallback method, but be aware that if you're interacting with any UI elements such as Forms controls, you'll need to use the control's Invoke method, not manipulate it directly. 一个更好的示例将显示在AsyncCallback方法中实际执行某些操作,但请注意,如果您与任何UI元素(如Forms控件)进行交互,则需要使用控件的Invoke方法,而不是直接操作它。


If the operation is completed already, why do we have to call Socket.EndSend(IAsyncResult)? 如果操作已经完成,为什么我们必须调用Socket.EndSend(IAsyncResult)?

From the MSDN documentation for the Stream.BeginWrite Method : Stream.BeginWrite方法的MSDN文档

Pass the IAsyncResult returned by the current method to EndWrite to ensure that the write completes and frees resources appropriately. 将当前方法返回的IAsyncResult传递给EndWrite,以确保写入完成并适当地释放资源。 EndWrite must be called once for every call to BeginWrite. 每次调用BeginWrite时都必须调用一次EndWrite。 You can do this either by using the same code that called BeginWrite or in a callback passed to BeginWrite. 您可以使用调用BeginWrite的相同代码或传递给BeginWrite的回调来执行此操作。 If an error occurs during an asynchronous write, an exception will not be thrown until EndWrite is called with the IAsyncResult returned by this method. 如果在异步写入期间发生错误,则在使用此方法返回的IAsyncResult调用EndWrite之前,不会抛出异常。

You never know what's going to happen with I/O operations, and you can't do normal error handling with an asynchronous operation because execution is taking place on a separate thread in the thread pool. 您永远不会知道I / O操作会发生什么,并且您无法使用异步操作执行正常的错误处理,因为执行是在线程池中的单独线程上进行的。 So the point of calling EndWrite is to give you a chance to handle any errors by putting EndWrite inside a try/catch block within your AsyncCallback method. 因此,调用EndWrite是让您有机会通过将EndWrite放在AsyncCallback方法中的try / catch块中来处理任何错误。


Can't I just store the IAsyncResult somewhere... and periodically check whether it's finished? 我不能只将IAsyncResult存储在某个地方......并定期检查它是否已完成?

You could, if you want to handle all the thread management and safety concerns to ensure that different threads would never try to handle the same IAsyncResult at the same time, but that seems like a lot more trouble to me than writing an AsyncCallback. 你可以,如果你想处理所有线程管理和安全问题,以确保不同的线程永远不会尝试同时处理相同的IAsyncResult,但这似乎比编写AsyncCallback更麻烦。 Again, the point of using asynchronous methods is to make it so you have to deal with threading concerns as little as possible. 同样,使用异步方法的目的是使它尽可能少地处理线程问题。


Another issue. 另一个问题。 The messages I'm sending are broken up into a header array and a body array. 我发送的消息被分解为头数组和主体数组。 Can I can issue two BeginWrites in a row? 我可以连续发出两个BeginWrites吗?

Although I've never tried this, I don't know if it would work, and it really depends on the specific implementation of the NetworkStream class. 虽然我从未尝试过这个,但我不知道它是否会起作用,它实际上取决于NetworkStream类的具体实现。 It's possible the 2nd request would wait until the 1st request completes before attempting to execute, but I have a feeling it would either throw an exception or you would get corrupted data on the other end. 第二个请求可能会等到第一个请求完成后再尝试执行,但我觉得它会抛出一个异常,否则你会在另一端获得损坏的数据。 If you want to send 2 separate data streams at the same time, I think the best way to do this would be to use 2 separate NetworkStreams. 如果你想同时发送2个独立的数据流,我认为最好的方法是使用2个独立的NetworkStream。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM