Best practice reading from `Socket` / `NetworkStream`?

I've got a C# application in which I want to read data from a Unix domain socket (on the sending side I have a Java application which sends buffered data). I want to read all data into a MemoryStream, as I subsequently want to parse that using CborReader (unfortunately I didn't find any way getting the CborReader itself to read the data directly from the socket). Now, to my question: I can't figure out what the best practice should be for fully reading the data from the Unix domain socket into a MemoryStream. My tests indicate that I can't rely solely on NetworkStream.DataAvailable but also need to call Socket.Poll(), so I've come up with the following code:
private static MemoryStream ReadFully(Socket socket, NetworkStream stream)
{
var memoryStream = new MemoryStream();
var buffer = new byte[8192];
while (socket.Poll(TimeSpan.FromMilliseconds(100), SelectMode.SelectRead))
{
while (stream.DataAvailable)
{
var bytesRead = stream.Read(buffer, 0, buffer.Length);
memoryStream.Write(buffer, 0, bytesRead);
}
}
memoryStream.Position = 0;
return memoryStream;
}
private static MemoryStream ReadFully(Socket socket, NetworkStream stream)
{
var memoryStream = new MemoryStream();
var buffer = new byte[8192];
while (socket.Poll(TimeSpan.FromMilliseconds(100), SelectMode.SelectRead))
{
while (stream.DataAvailable)
{
var bytesRead = stream.Read(buffer, 0, buffer.Length);
memoryStream.Write(buffer, 0, bytesRead);
}
}
memoryStream.Position = 0;
return memoryStream;
}
Does this make sense or is there a better / more idiomatic way?
4 Replies
reflectronic
reflectronic7mo ago
you are trying to read until the socket is closed? in general, when stream.Read returns 0, it means that there is no more data so, the way to do this loop is while (stream.Read(...) is var bytesRead and not 0). you don't need Poll or DataAvailable. (by the way, you can access the Socket from stream.Socket, you don't need to pass it around) however. what you really want here is
private static MemoryStream ReadFully(NetworkStream stream)
{
var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
memoryStream.Position = 0;
return memoryStream;
}
private static MemoryStream ReadFully(NetworkStream stream)
{
var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
memoryStream.Position = 0;
return memoryStream;
}
knut.wannheden
knut.wannhedenOP7mo ago
I've tried this, but I think this requires the sending application to somehow close the socket, because whenever I try this, the operation is blocking with the following stack trace:
Interop.Sys.ReceiveMessage() at /Users/knut/Library/Application Support/JetBrains/Rider2024.1/resharper-host/DecompilerCache/decompiler/ac2d207955094b78acaf0bd37a9cfa67a5000/0d/bf21a485/Interop.cs:line 932
SocketPal.SysReceive()
SocketPal.TryCompleteReceiveFrom()
SocketAsyncContext.ReceiveFrom()
Socket.Receive()
NetworkStream.Read()
Stream.CopyTo()
Program.ReadFully()
Interop.Sys.ReceiveMessage() at /Users/knut/Library/Application Support/JetBrains/Rider2024.1/resharper-host/DecompilerCache/decompiler/ac2d207955094b78acaf0bd37a9cfa67a5000/0d/bf21a485/Interop.cs:line 932
SocketPal.SysReceive()
SocketPal.TryCompleteReceiveFrom()
SocketAsyncContext.ReceiveFrom()
Socket.Receive()
NetworkStream.Read()
Stream.CopyTo()
Program.ReadFully()
I was able to get the sending application to shutdown the connection for writing without closing the socket (as I still want to send a response on the C# side). That way your approach with CopyTo() then works as expected. Thanks!
reflectronic
reflectronic7mo ago
ahh, i see... you were using the timeout on Poll so that you didn't have to close the socket that's a bit of a cheap hack, i do prefer what you have now though, if you need any more flexibility than that, i would use a basic length-prefixed protocol just, like, read one int, and then read that many bytes from the socket
knut.wannheden
knut.wannhedenOP7mo ago
Indeed. I just couldn't figure out before why the CopyTo() call was always blocking. Ideally, I would prefer if the CborReader could read directly from the stream.
Want results from more Discord servers?
Add your server