1

I have a C# .net server & client (TCP). The server listens for multiple clients in an asynchronous manner. The clients can send data ranging from screenshots, to messages, to a videostream (really just multiple screenshots sent extremely fast which never works...), to system information. The problem that I'm facing is that when all of these are possibilites, the server can only receive so little. I can't seem to download a file from a client & receive a screenshot at the same time. Or when receiving multiple screenshots really fast to emulate screensharing, I can't download a file (this usually crashes the data receiving process.)

I don't even think my server is correctly receiving data for clients, as data often is corrupt and throws exceptions. I'll start with my server listening code itself:

    public void Listen()
    {
        _socket.Listen(100);
        _socket.BeginAccept(new AsyncCallback(AcceptCallback), null);
        CSCLog("Server started || Listening...");

        void AcceptCallback(IAsyncResult AR)
        {
            Socket socket = _socket.EndAccept(AR);

            clienthandler client = new clienthandler(socket, _dash);
            _socketClientList.Add(client);

            // Repeat the listening process...
            _socket.BeginAccept(new AsyncCallback(AcceptCallback), null);
        }
    }

The server holds a custom class, clienthandler, that requires a socket and provides individual socket "control." Here is the basic run down of that class:

public class clienthandler
{
    public Socket _clientSocket;
    public clienthandler(Socket paramSocket)
    {
        _clientSocket = paramSocket;
        receiveAll();
    }

    public void receiveAll()
    {
        int BUFF_SIZE = 4024 * 2;
        byte[] _lengthBuffer = new byte[BUFF_SIZE];

        _clientSocket.BeginReceive(_lengthBuffer, 0, _lengthBuffer.Length, 0, new AsyncCallback(LengthCallBack), _clientSocket);

        void LengthCallBack(IAsyncResult lengthAR)
        {
            try
            {
                Socket buffSocket = (Socket)lengthAR.AsyncState;
                int lengthreceived = _clientSocket.EndReceive(lengthAR);
                byte[] lengthdata = new byte[lengthreceived];
                Array.Copy(_lengthBuffer, lengthdata, lengthreceived);

                // Handle the received incoming data length
                DataInformation datai = (DataInformation)ObjectHandler.Deserialize(_lengthBuffer);
                int datalength = datai.datalength;

                Array.Clear(_lengthBuffer, 0, _lengthBuffer.Length);

                PrepCol(datai, buffSocket);

                // Repeat the data length listening process...
                _clientSocket.BeginReceive(_lengthBuffer, 0, _lengthBuffer.Length, 0, new AsyncCallback(LengthCallBack), _clientSocket);

            }
            catch (Exception ex) { // handle exception... }
        }
    }
}

To further explain, the receiveAll() function tries to asynchronously listen for data. I've created a custom class (these are all stored in a .DLL that the client and server share) called DataInformation that acts as a header for data being sent through the stream. It only includes an enum of an objectType & int datalength that signifies the amount of incoming data my client sends right afterwards (as any data sent from the client has its own DataInformation sent first, then immediately after is the actual data. Both are serialized.)

The PropCol() methods takes in the deserialized DataInformation class and socket object. It then collects data using the datainformation variables provided (the Enum & int datalength.):

    public void PrepCol(DataInformation paramDatainformation, Socket paramBuffSocket)
    {
        int datalength = paramDatainformation.datalength;

        object streamobj;
        MemoryStream ms = new MemoryStream();

        if(paramDatainformation.objectType == ObjectType.Universal)
        {
            // Prepare & collect the incoming data
            while (datalength > 0)
            {
                byte[] buffer;
                if (datalength < paramBuffSocket.ReceiveBufferSize)
                {
                    buffer = new byte[datalength];
                }
                else
                    buffer = new byte[paramBuffSocket.ReceiveBufferSize];

                int rec = paramBuffSocket.Receive(buffer, 0, buffer.Length, 0);

                datalength -= rec;
                ms.Write(buffer, 0, rec);
            }

            // Handle the collected data
            ms.Close();
            byte[] data = ms.ToArray(); ms.Dispose();
            streamobj = ObjectHandler.Deserialize(data);
            Array.Clear(data, 0, data.Length);

            // Check the data that is received & determine type
            CheckData(streamobj);
        }

        if(paramDatainformation.objectType == ObjectType.Screenshot)
        { // Receive data certain way }

        if(paramDatainformation.objectType == ObjectType.TransferFile)
        { // Receive data certain way }
    }

It utilizes a while loop to synchronously receive data until it has received the amount that the DataInformation says it needs. It takes the data object and passes it into the CheckData() method that requires an object. It takes the object and checks if it is any object it can handle (such as a screenshot, or message, ect...), if so then it does.

The problem I find the most is that when receiving large data, or data really fast, my DataInformation deseralization in the receiveAll() method returns corrupt/ invalid data which isn't good at all because I'm losing something that I could be needing.

My question really boils down to what am I doing wrong? Or how should I take the approach to receive multiple data simultaneously?

Any more information I could provide is that the Serialization & Desserialization methods are in the .DLL. That's really all. I apologize for dumping large amounts of code out, but felt like those were the most relevent things. Thank you for your time.

5
  • 1
    In LengthCallBack(), you've incorrectly assumed that because you've received something on your socket, you have in fact received one entire DataInformation object (and then you proceed to attempt to deserialize it). You might have only received part of it, or possibly an entire object plus whatever was sent after it (the data stream that comes afterwards). I assume that object has a fixed size? If so, you need to store the bytes in some kind of storage until you've received the right amount and then deserialize. Hope that helps. Commented Nov 28, 2019 at 6:29
  • 1
    When you send data over a TCP socket, the bytes might get separated along the way and be received on the other end in multiple bursts. Or the opposite might happen; multiple sends might be received on the other end in one shot. The bytes are always guaranteed to be in the same order as they were sent (when viewed as one long stream), just not necessarily in the same "grouping" that they were sent. This is inherent in the way TCP works. Commented Nov 28, 2019 at 6:33
  • 1
    Huh...all of the things I said above, were in the accepted answer from your previous question months ago... Commented Nov 28, 2019 at 7:10
  • @Idle_Mind Thank you, I'm trying to impliment this right now. There is a fixed size actually and it happens to be 249 bytes it seems. I'll have to make it so that the server waits until that amount & then continues. Commented Nov 28, 2019 at 7:14
  • @Idle_Mind Sorry yes I did ask something very similar to this months ago, I actually did do exactly as I accepted the answer as, buuut I used it in the wrong place. I did it for the PrepCol instead. When I should have been doing it for both. It's a mess up on my part, sorry for making you have to re-explain it to me again. Commented Nov 28, 2019 at 7:16

1 Answer 1

1

First of all, as mentioned in comments, you should consider your TCP socket as continous stream of data. Eventually, you'll end up in situation where you read uncomplete set of bytes, and being unable to deserialize it.

Said all the above, when you receive next portion of data from the socket, you need to know two things:

  • how much data you received so far;
  • how much data you need to begin the deserialization process.

Another point is that you are using old-fashioned callback-style asynchronous operations. You can rewrite your code to use async/await-friendly methods, like Socket.ReceiveAsync().

And finally, don't use Socket in your logic. Use Stream instead.

Here is the approach I'd choose to receive and parse binary data according to some protocol. I assume that your protocol consists of some fixed-size header followed by the data (file/screenshot/video etc.). The size and type of data is stored in the header.

using System;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace Test1
{
    public interface IData
    {
    }

    public interface IHeader
    {
        int DataSize { get; }
    }

    public interface IHeaderParser
    {
        int HeaderSize { get; }
        IHeader Parse(byte[] buffer, int offset);
    }

    public interface IDataParser
    {
        IData Parse(byte[] buffer, int offset);
    }

    public interface IDataParserSelector
    {
        IDataParser GetParser(IHeader header);
    }

    public class StreamReceiver : IDisposable
    {
        public StreamReceiver(
            Stream stream,
            IHeaderParser headerParser,
            IDataParserSelector dataParserSelector)
        {
            _stream = stream;
            _headerParser = headerParser;
            _dataParserSelector = dataParserSelector;
        }

        public virtual void Dispose()
        {
            _stream.Dispose();
        }

        public async Task<IData> ReceiveAsync(CancellationToken token)
        {
            const int headerOffset = 0;
            await ReadAsync(headerOffset, _headerParser.HeaderSize, token).ConfigureAwait(false);
            var header = _headerParser.Parse(_buffer, headerOffset);

            var dataOffset = headerOffset + _headerParser.HeaderSize;
            await ReadAsync(dataOffset, header.DataSize, token).ConfigureAwait(false);
            var dataParser = _dataParserSelector.GetParser(header);
            var data = dataParser.Parse(_buffer, dataOffset);

            return data;
        }

        private async Task ReadAsync(int offset, int count, CancellationToken token)
        {
            if (_buffer.Length < offset + count)
            {
                var oldBuffer = _buffer;
                _buffer = new byte[offset + count];
                Array.Copy(oldBuffer, _buffer, oldBuffer.Length);
            }

            var nread = 0;

            while (nread < count)
            {
                nread += await _stream.ReadAsync(
                    _buffer, offset + nread, count - nread, token)
                    .ConfigureAwait(false);
            }
        }

        private readonly Stream _stream;
        private readonly IHeaderParser _headerParser;
        private readonly IDataParserSelector _dataParserSelector;
        private byte[] _buffer = new byte[0];
    }

    public class TcpReceiver : StreamReceiver
    {
        public TcpReceiver(
            TcpClient tcpClient,
            IHeaderParser headerParser,
            IDataParserSelector dataParserSelector) 
            : base(tcpClient.GetStream(), headerParser, dataParserSelector)
        {
            _tcpClient = tcpClient;
        }

        public override void Dispose()
        {
            base.Dispose();
            _tcpClient.Dispose();
        }

        private readonly TcpClient _tcpClient;
    }
}

This is just a stub, I leave interface implementations up to you, if you ever consider using this approach.

Also, I didn't cover the connection process here, so it is up to you too :).

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! You're explanation really helps me better understand TCP networking. I definitely have to retouch on some coding to understand the sample you provided. It's much appreciated, I'll be sure to tell you how things go :) !

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.