Search code examples
sockets.net-coresocketasynceventargs

Is there any difference between TCP wrappers' GetStream().Read/Write and Socket's Receive/Send


If client Socket is defined like this:

args = new SocketAsyncEventArgs();
args.AcceptSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
await args.AcceptSocket.ConnectAsync(host, port);

and server lets it get connected in this way:

server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
server.Bind(new IPEndPoint(0, port));
server.Listen(0);
var arg = new SocketAsyncEventArgs();
arg.AcceptSocket = await server.AcceptAsync();

Are the byte[] used in Send/SendAsync and Receive/ReceiveAsync for transmission between server and client exact equivalent to NetworkStream, which we get by calling tcpClient.GetStream() for reading/writing, of TcpListener and TcpClient?

I'm not sure BUT I think they are because SocketType for both client and server is set to Stream and there shouldn't be any data loss in send/receive between those Streaming protocols!


Solution

  • The difference is one of API design.

    The .NET Socket class is an OOP API for Win32's Winsock, which itself is derived from BSD Sockets.

    The Socket API is built around its own functions for sending and receiving data, with functions like send, recv. On many platforms you can use the OS-provided filesystem API for reading and writing to sockets the same way you can read and write to local files.

    In .NET, the Stream class exists as an abstraction of any source or sink for binary data which can be read in a blocking or non-blocking (async) manner regardless of where it came from (a file on a local disk, a file on a network share, a buffer in-memory, a TCP connection, a UDP connection, and so on). Read more about Stream and its use as an abstraction here: https://learn.microsoft.com/en-us/dotnet/standard/io/

    The point is that if you write a program or library that processes data - then rather than having to repeat your code over-and-over for different types of IO (files, in-memory buffers, TCP connections, etc) you only need to write your code once using Stream and then magically your code can be used in many new places without much work.

    But this comes with a downside of leaky-abstractions. We've since learned over the past 20-30 years of software-engineering that a single interface will not be perfect in every role - for example, a MemoryStream is always non-blocking and doesn't need flushing - but a NetworkStream (a Stream API for Socket) behaves very differently despite sharing the same API (remember: interfaces do not describe behavior!), such as how it buffers data internally (e.g. Nagle's algorithm). This is why .NET is now moving away from Stream and towards the new Pipeline API model.

    So, in short:

    • Network connections are always using a Socket internally.
    • The TcpClient object adapts the Socket API to the Stream API (as NetworkStream).
      • So a TcpClient cannot exist without a Socket.
      • And a NetworkStream is simply an adapter for Socket for the Stream API.
    • If your program does not need to abstract-away IO using the Stream API then you should only use the Socket API and not use NetworkStream or TcpClient.
    • If you do need to pass network data around using a Stream model, then use TcpClient and NetworkStream - but be-aware of how NetworkStream behaves and you should always use non-blocking (async, aka "overlapped IO") to avoid bottlenecks and program freezes.