NetworkStream.Write vs. Socket.Send
Asked Answered
H

3

21

I have a c# application that I use a custom FTP library for. Right now Im using Socket.Send to send the data but I was wondering if it would be better to initiate a NetworkStream with the socket and use NetworkStream.Write instead.

Are there any advantages to using one over the other?

Hogen answered 2/7, 2011 at 3:54 Comment(0)
K
29

The advantage of a NetworkStream derives primarily from the fact that it is a Stream. The disadvantage of a Socket is that common code that reads and writes from abstract I/O sources like a Stream cannot handle a Socket.

The main use case for a NetworkStream is that you have some code elsewhere that reads or writes from a Stream, and you wish you could use it with a Socket. You would know if were in this situation and then NetworkStream would be a big help!

Say for example you had a communications library and you supported serializing messages from files, named pipes and TCP/IP. The ideal choice for the I/O class would be Stream. Then your serialization methods could accept a FileStream, a PipeStream, or a NetworkStream. It would even accept a MemoryStream. This is the benefit of abstraction because after we've created the stream, a method can interact with it without knowing what kind of stream it is.

In this sense, a NetworkStream uses the adapter design pattern. It adapts the Socket API to the Stream API so that clients that are expecting a Stream can use it.

So finally, the question, if NetworkStream is a Stream adapter for a Socket, which one should we use? Well, if you need a Stream, then NetworkStream is your only choice. If you don't need a Stream, then you can use whichever API you are most comfortable with. If you are already using Socket successfully, there is no pressing reason to switch to NetworkStream.

Kreutzer answered 2/7, 2011 at 5:46 Comment(3)
Can I just add to this that NetworkStream also has much better async supportScarcely
@LukeMcGregor That isn't true. NetworkStream does not implement Task-based async support directly, instead it relies on Stream's own APM-to-Task functionality which isn't as ideal as "native" Task-based async (it uses TaskFactory.FromAsync). Furthermore, the BeginRead/BeginWrite (APM) methods simply wrap Socket.BeginReceive/Socket.BeginSend which aren't as high-performance as the (confusingly named, non-Task-based) Socket.SendAsync and Socket.ReceiveAsync methods.Astrogate
further, neither TPL nor APM async models are "high-performance" compared to "native" IOCP and overlapped I/O, but this is a sockets programming concept beyond most developers these days and a lost art for most people claiming to be "Windows programmers." IOCP also remains platform specific technology, but will send/receive data faster than any "user mode" solution invented in the depths of the BCL. If you want "the fastest tx/rx performance possible under windows" you want raw sockets, static buffers, and IOCP w/Overlapped I/O (or an equivalent native solution on a non-Windows platform.)Kristine
F
3

You can potentially separate creation of NetworkStream and to work with that as with abstract Stream - so you'll be able to change your transport or simply to create Stream stubs for testing.

As a question of method itself - NetworkStream.Write inside has the only operation (except state checks) streamSocket.Send(buffer, offset, size, SocketFlags.None); - so it's mostly the same as to call that on socket.

Featly answered 2/7, 2011 at 5:41 Comment(0)
K
3

One disadvantage of using the .NET Framework implementation of NetworkStream::Write is that if the underlying network (OSI layers 1-4) is unable to receive the entire data buffer you are not made aware (except, perhaps, by inspecting .NET Networking performance counters.)

Unreliable.

Something like this appears in NetworkStream::Write of Microsoft's .NET Framework System.dll:

try
{
    socket.Send(buffer, offset, size, SocketFlags.None);
}
/* ... catch{throw;} */

Note how the return value, which represents the count of bytes written by Send(), is discarded. This suggests NetworkStream::Write is unreliable for sub-par networks (roaming/wireless), or networks that may become IO bound (exceeding available bandwidth.)

More than one implementation.

You can find other implementations which write all bytes until all of buffer has been written (or other failure occurs.) It is a standard method of sending data, and will always exhibit the following behavior:

var count = 0;
while (count < size)
{
    count += socket.Send(buffer, offset + count, size - count, ...);
}

What does this mean?

You can write proper and reliable send() code by calling Socket::Send directly, you cannot write proper and reliable send() code calling NetworkStream::Write.

As an aside, other variations of NetworkStream do not have the same problem the BCL disassembly shows (see mono,cosi2), and still others show similar problems despite their best efforts (see flourinefx).

References

  1. Mono Repository on Github
  2. FlourineFxSL Source Code
  3. cosi2 Repository on BitBucket (via searchcode.com)
Kristine answered 15/9, 2015 at 21:54 Comment(3)
According to the documentation for socket.Send: "In nonblocking mode, Send may complete successfully even if it sends less than the number of bytes you request. It is your application's responsibility to keep track of the number of bytes sent and to retry the operation until the application sends the requested number of bytes." - so this bug does not apply in cases that don't use nonblocking sockets, and nonblocking is the default, so most users should be okay.Astrogate
Also, NetworkStream's constructor will throw an IOException if a nonblocking Socket (if( !socket.IsBlocking ) { instance is passed in to its constructor.Astrogate
i upvoted your comment because it was accurate, not because i agreed with your interpretation of the problem. it IS possible to use a non-blocking socket despite the ctor check. i have personally dealt with this problem in the past and it is worth people understanding that it is there. this COULD be fixed within netfx by adding a loop around the Socket::Send call as is done in other implementations (corefx, mono, others) but "the docs say" continues to be the defense for allowing this bug to exist in the first place.Kristine

© 2022 - 2024 — McMap. All rights reserved.