WatsonTcp icon indicating copy to clipboard operation
WatsonTcp copied to clipboard

`WatsonTcpClient.Send()` with stream disconnects if `contentLength` is not equal to the stream's capacity.

Open JVemon opened this issue 2 years ago • 1 comments

Given a byte[] buffer, I'd like to send a portion of it - moreover, I'd like to use my own Stream instances which may be pooled and recycled.

The WatsonTcpClient.Send (long contentLength, Stream stream) is the closest overload to my purposes as I can provide my own Stream and coincidentally my buffer's data always starts at the zeroth index (although it'd be nice to also be able to define the starting position).

To my understanding, contentLength defines how much of my Stream should be read and sent.

However, if I choose a contentLength that is not equal to the buffer's capacity, what happens is that the data is sent/received correctly as expected (it respects the contentLength that I chose), but the client will be disconnected afterwards.

The only instance in which the client doesn't get disconnected is if contentLength is set equal to the buffer capacity, but then of course that'd defeat the point for my purposes.

The workaround for me so far is to copy the desired data into a new buffer of the exact size required, or create a new MemoryStream and write() to it what I need, but minimizing copies/allocations is exactly what I'm trying to do.

Below is the program I use to reproduce it. I've been using .NET 6.0 and tested this on Windows 10 and macOS Monterey.


using WatsonTcp;

internal class Sandbox
{

    public static void Main(string[] args)
    {
        if (args[0].ToLower().Equals("server"))
            StartServer();
        else
            StartClient();

        Console.ReadLine();
    }

    static void StartServer ()
    {
        WatsonTcpServer server = new WatsonTcpServer("127.0.0.1", 9001);

        server.Events.ClientConnected += (sender, args) =>
            Console.WriteLine("Server: a client has connected.");

        server.Events.ClientDisconnected += (sender, args) =>
            Console.WriteLine("Server: a client has disconnected: " + args.Reason);

        server.Events.MessageReceived += (sender, args) =>
            Console.WriteLine($"Server: received {args.Data.Length} bytes from client");

        server.Start();

        Console.WriteLine("Server has started.");
    }

    static void StartClient ()
    {
        WatsonTcpClient client = new WatsonTcpClient("127.0.0.1", 9001);

        client.Events.ServerConnected += (sender, args) => {
            Console.WriteLine("Client: connected to server. Will send a message...");

            //                       VVVVVVVVV Only want to send the first 5 bytes
            var buffer = new byte[] {1,2,3,4,5,6,7,8,9,10};

            using (var stream = new MemoryStream(buffer))
                client.Send(contentLength:5, stream); //    <-- will disconnect unless you use 10

        };

        client.Events.ServerDisconnected += (sender, args) =>
            Console.WriteLine("Client: disconnected from server.");

        client.Events.MessageReceived += (sender,args) =>
            Console.WriteLine($"Client: received {args.Data.Length} bytes from server.");

        Console.WriteLine("Client attempting connection...");
        client.Connect();
    }
}

JVemon avatar Jun 27 '22 02:06 JVemon

I noticed that my server was not using StreamReceived as it should in this scenario. However the problem does persist anyway even if you replace MessageReceived for that.

JVemon avatar Jul 11 '22 23:07 JVemon

Copied and reproduced it on my end. Seems to derive from WatsonMessage.cs, this section specifically:

                    if ((int)endCheck[3] == 0
                       && (int)endCheck[2] == 0
                       && (int)endCheck[1] == 0
                       && (int)endCheck[0] == 0)
                    {
                        _Logger?.Invoke(Severity.Debug, _Header + "null header data, peer disconnect detected");
                        return false;
                    }

Haven't been able to figure out why this is, yet, though. Content length must at least be the buffer length provided, or else header reading fails.

private async Task DataReceiver() (WatsonTcpClient.cs) would then exit out its while loop here:

                    bool buildSuccess = await msg.BuildFromStream(_Token).ConfigureAwait(false);
                    if (!buildSuccess)

.. with a default disconnect reason of Normal.

eatyouroats avatar Aug 15 '22 16:08 eatyouroats

Hi, sorry for the delay on this. I'm looking into it now.

jchristn avatar Aug 15 '22 20:08 jchristn

I believe I have a fix ready for this. Example using your test code (I added it as project Test.PartialStream):

image

The client isn't disconnected, and the server receives the appropriate number of bytes.

jchristn avatar Aug 15 '22 20:08 jchristn

Could you give this a try?

NuGet: https://www.nuget.org/packages/WatsonTcp/4.8.14.12 Commit: https://github.com/jchristn/WatsonTcp/commit/022eab13360c786b5f9e3c3d7798f44c509ef686

Also updating deps, there will be a .13 version shortly.

jchristn avatar Aug 15 '22 20:08 jchristn

Closing just to clean up my inbox, please re-open if this does not solve the issue!

jchristn avatar Aug 15 '22 21:08 jchristn

Works on my end. =)

eatyouroats avatar Aug 16 '22 08:08 eatyouroats

Thanks for letting me know @eatyouroats !!

jchristn avatar Aug 16 '22 13:08 jchristn