简体   繁体   English

如何使用C#套接字同时有效地接收和发送数据

[英]How can I efficiently receive & send data simultaneously using c# sockets

I have a C# .net server & client (TCP). 我有一个C#.net服务器和客户端(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." 服务器拥有一个自定义类clienthandler,该类需要一个套接字并提供单独的套接字“控件”。 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. 为了进一步说明, receiveAll()函数尝试异步侦听数据。 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. 我创建了一个名为DataInformation的自定义类(这些都存储在客户端和服务器共享的.DLL中), DataInformation充当通过流发送的数据的头。 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.) 它仅包含一个objectTypeint datalength的枚举,该枚举表示我的客户端随后立即发送的传入数据量(因为从客户端发送的任何数据都先发送自己的DataInformation,然后紧接着发送实际的数据。两者均已序列化。 )

The PropCol() methods takes in the deserialized DataInformation class and socket object. PropCol()方法接受反序列化的DataInformation类和套接字对象。 It then collects data using the datainformation variables provided (the Enum & int datalength.): 然后使用提供的datainformation变量(Enum和int数据长度)收集数据:

    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. 它利用while循环来同步接收数据,直到接收到DataInformation所说的所需数量为止。 It takes the data object and passes it into the CheckData() method that requires an object. 它获取数据对象,并将其传递到需要对象的CheckData()方法中。 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. 我最发现的问题是,当接收大数据或数据速度非常快时, receiveAll()方法中的receiveAll()返回损坏/无效的数据,这根本不好,因为我丢失了可能需要的东西。

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. 我可以提供的更多信息是序列化和反序列化方法位于.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. 感谢您的时间。

First of all, as mentioned in comments , you should consider your TCP socket as continous stream of data. 首先,如注释中所述 ,您应该将TCP套接字视为连续的数据流。 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() . 您可以重写代码以使用异步/等待友好的方法,例如Socket.ReceiveAsync()

And finally, don't use Socket in your logic. 最后,不要在您的逻辑中使用Socket Use Stream instead. 请改用Stream

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 :). 另外,我在这里没有介绍连接过程,所以也取决于您:)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM