Web套接字在以非常快的速度发送数据时行为不端

时间:2017-03-23 07:45:25

标签: c# sockets networking websocket

我是WebSockets的新手,所以我很抱歉,如果这是由于我的错,但是当我在一个while循环(仅测试)中将数据从WebSocket(在chrome中)发送到服务器(C#,TCPListener)时没有正确到达服务器。它的第一个字节通常消失,有时键也会在两个消息之间混合。这导致我的服务器崩溃。当数据不规则地发送或者不是一个接一个地超快速发送时,这种情况永远不会发生。

这是服务器接收器代码:

#region Receiver

    public class Receiver
    {
        private const int DEFAULT_DATA_LIMIT = 256; // Bytes

        private const int HEADER_LENGTH = 2;
        private const int KEYS_LENGTH = 4;

        private const int SHORT_BYTES = 2;
        private const int LONG_BYTES = 8;

        private const int SHORT_DATA = 126;
        private const int LONG_DATA = 127;

        private readonly Socket TargetSocket;

        private readonly object DataLock = new object();
        private readonly object amountOfDataReceivedLock = new object();
        private readonly object RecevingLock = new object();
        private readonly object CompletingRecieveLock = new object();

        private bool receiving;

        private int _amoutOfDataReceived;
        private byte[] Data;

        private int amountOfDataReceived
        {
            get
            {
                lock(amountOfDataReceivedLock)
                    return _amoutOfDataReceived;
            }
            set
            {
                lock(amountOfDataReceivedLock)
                    _amoutOfDataReceived = value;
            }
        }

        public int Capacity
        {
            get
            {
                return Data.Length;
            }
        }

        public int FreeSpace
        {
            get
            {
                return Capacity - amountOfDataReceived;
            }
        }

        public bool ReceivingData
        {
            get
            {
                return receiving;
            }
        }

        public bool ChunkReady
        {
            get
            {
                int wholeChunkLength = WholeChunkLength();

                if(wholeChunkLength == -1)
                    return false;

                return WholeChunkLength() <= amountOfDataReceived;
            }
        }

        public Receiver(Socket socket, int dataLimit)
        {
            this.TargetSocket = socket;
            Data = new byte[dataLimit];
        }

        public Receiver(Socket socket) : this(socket, DEFAULT_DATA_LIMIT) { }

        private int ChunkDataLength()
        {
            lock(DataLock)
            {
                lock(amountOfDataReceivedLock)
                {
                    if(amountOfDataReceived < 1)
                        return -1;

                    int lengthInfoIndex = HEADER_LENGTH - 1;

                    if(amountOfDataReceived < lengthInfoIndex + 1)
                        return -1;

                    int rawLength = Data[lengthInfoIndex] -128;

                    int bytesRequiredToGetLength = 0;

                    if(rawLength == SHORT_DATA)
                        bytesRequiredToGetLength = SHORT_BYTES;
                    else if(rawLength == LONG_DATA)
                        bytesRequiredToGetLength = LONG_BYTES;
                    else
                        return rawLength;

                    if(amountOfDataReceived < lengthInfoIndex + 1 + bytesRequiredToGetLength)
                        return -1;

                    return Utilities.ToInt32(Data, lengthInfoIndex + 1, bytesRequiredToGetLength);
                }
            }
        }

        private int WholeChunkLength()
        {
            int dataLength = ChunkDataLength();
            int lengthInfoLength = dataLength < SHORT_DATA ? 0 : dataLength < short.MaxValue ? SHORT_BYTES : LONG_BYTES;

            if(dataLength == -1)
                return -1;

            return HEADER_LENGTH + lengthInfoLength + KEYS_LENGTH + dataLength;
        }

        private void ReceiveDataInternal(int dataToReceiveLength)
        {
            if(dataToReceiveLength == 0)
                return;

            lock(RecevingLock)
            {
                if(receiving)
                    return;

                receiving = true;
            }

            if(dataToReceiveLength > FreeSpace)
                dataToReceiveLength = FreeSpace;

            TargetSocket.BeginReceive(Data, amountOfDataReceived, dataToReceiveLength, SocketFlags.None, result =>
            {
                OnRecevingComplete(result, dataToReceiveLength);
            }, null);

        }

        private void OnRecevingComplete(System.IAsyncResult result, int receivedDataLength)
        {
            lock(CompletingRecieveLock) // This is not needed really
            {
                TargetSocket.EndReceive(result);
                this.amountOfDataReceived += receivedDataLength;
                receiving = false;
            }
        }

        public void ReceiveData()
        {
            ReceiveDataInternal(TargetSocket.Available);
        }

        public byte[] GetChunk()
        {
            lock(DataLock)
            {
                lock(amountOfDataReceivedLock)
                {
                    int chunkLength = WholeChunkLength();

                    if(chunkLength == -1 || chunkLength > amountOfDataReceived)
                        return null;
                     //                            throw new System.InvalidOperationException("Chunk is yet not ready!");

                    byte[] chunk = new byte[chunkLength];

                    for(int i = 0; i < chunkLength; i++)
                        chunk[i] = Data[i];

                    ArrayUtilities<byte>.ShiftArrayLeft(Data, chunkLength, amountOfDataReceived);
                    amountOfDataReceived -= chunkLength;

                    return chunk;
                }
            }
        }

    }

    #endregion

这是调用Receiver类的函数

private void Update()
    {
        if(!running)
            return;

        for(int i = 0; i < clients.TotalClients; i++)
        {
            if(!clients[i].IsReady)
                continue;

            var recievier = clients[i].GetReciever();

            if(recievier.recievier.FreeSpace > 0 && clients[i].DataAvaliable && !recievier.Receiving)
                recievier.ReceiveData();

            if(recievier.ChunkReady)
            {
                var data = recievier.GetChunk();

                Utilities.WSFormatter.DecodeMessage(data);

                int SI = Utilities.WSFormatter.MessageStartIndex(data);

                System.Console.WriteLine(System.Text.Encoding.UTF8.GetString(data, SI, data.Length - SI));
            }

        }
    }

函数本身是通过类似的东西调用的(在新线程上,而不是默认线程):

while (true)
{
    Update();
    System.Threading.Thread.Sleep(1);
}

这是WebSocket代码:

        Engine.Loader.loadEngine(function() {

        var client = new Engine.Client("ws:192.168.1.105:8080");
        var connected = false;

        client.addConnectListener(function()
        {
            connected = true;
            console.log("Connection successful!");              
        });

        client.addRecieveListener(function(data)
        {
            console.log(data);
        });

        var gl = new Engine.GameLoop(new Engine.Renderer(), new Engine.Input(), client);
        gl.start();

        var i = 0;

        gl.addEventListener("UPDATE", function()
        {
            /*The code that works*/

            if(connected)
                client.sendString("" + i++);

            /*The code that causes problems*/

            var j = 10;

            while(connected && j-- > 0)
                client.sendString("" + i++);

        });

    });

编辑:UPDATE事件由window.requestAnimationFrame

调用 编辑:我之前发布的答案也失败了。事实证明它只有在错误发生之前数据[]被填充时才有效。因此,如果我增加数据容量[],我还需要增加接收数据之间的时间。

编辑:完成整个工作。它有不止一个问题。事实证明,Socket.BeginReceive()方法在内部执行的操作以及我同时调用的DataReceiver.GetChunk()修改了数据。 所以现在我首先在临时缓冲区中接收数据,然后在触发完成事件时将其写入主缓冲区,同时锁定它,这样其他线程就不会乱用它(据我所知,完整的方法没有调用调用BeginRecieve函数的线程。)

同样正如@vtortola所说,当完成事件被触发时,所有数据可能都不在缓冲区中,所以我也考虑过了。

这是实际可行的脚本。它是重写的,但有些方法是从旧脚本中复制的(是的,我很懒):

public class DataReceiver
{
private const int DEFAULT_DIRECT_BUFFER_LIMIT = 256;
private const int DEFAULT_DATA_LIMIT = 256; // bytes

private const int HEADER_LENGTH = 2;
private const int KEYS_LENGTH = 4;

private const int SHORT_DATA = 126;
private const int SHORT_BYTES = 2;

private const int LONG_DATA = 127;
private const int LONG_BYTES = 8;

private readonly Socket TargetSocket;

private readonly object dataUpdatingLock = new object();

private bool receivingData;

private byte[] directRecieveBuffer;

private int dataReceived;
private byte[] data;

public int Capacity
{
    get
    {
        return data.Length;
    }
}

public int AmountOfDataReceived
{
    get
    {
        return dataReceived;
    }
}

public int FreeSpace
{
    get
    {
        return Capacity - AmountOfDataReceived;
    }
}

public bool ReceivingData
{
    get
    {
        return receivingData;
    }
}

public bool ChunkReady
{
    get
    {
        int chunkLength = WholeChunkLength();

        if(chunkLength < 1)
            return false;

        return chunkLength <= dataReceived;
    }
}

private DataReceiver(Socket socket, int bufferLength, int directBufferLength)
{
    this.TargetSocket = socket;
    this.data = new byte[bufferLength];
    this.directRecieveBuffer = new byte[directBufferLength];
}

public DataReceiver(Socket socket, int bufferLength) : this(socket, bufferLength, DEFAULT_DIRECT_BUFFER_LIMIT) { }

public DataReceiver(Socket socket) : this(socket, DEFAULT_DATA_LIMIT, DEFAULT_DIRECT_BUFFER_LIMIT) { }

private void ReceiveDataInternally()
{
    receivingData = true;

    int expectedDataLength = TargetSocket.Available;

    if(expectedDataLength > FreeSpace)
        expectedDataLength = FreeSpace;

    if(expectedDataLength > directRecieveBuffer.Length)
        expectedDataLength = directRecieveBuffer.Length;

    TargetSocket.BeginReceive(directRecieveBuffer, 0, expectedDataLength, SocketFlags.None, result =>
    {
        int receivedDataLength = TargetSocket.EndReceive(result);

        lock(dataUpdatingLock)
        {
            for(int i = 0; i < receivedDataLength; i++)
            {
                data[dataReceived++] = directRecieveBuffer[i];
                directRecieveBuffer[i] = 0;
            }
        }

        receivingData = false;

    }, null);
}

public byte[] GetChunk()
{
    int chunkLength = WholeChunkLength();

    if(chunkLength == -1 || chunkLength > dataReceived)
        return null;

    byte[] chunk = new byte[chunkLength];

    for(int i = 0; i < chunkLength; i++)
        chunk[i] = data[i];

    lock(dataUpdatingLock)
    {
        ArrayUtilities<byte>.ShiftArrayLeft(data, chunkLength, dataReceived);
        dataReceived -= chunkLength;
    }

    return chunk;
}

private int ChunkDataLength()
{
    if(dataReceived < 1)
        return -1;

    int lengthInfoIndex = HEADER_LENGTH - 1;

    if(dataReceived < HEADER_LENGTH)
        return -1;

    int rawLength = data[lengthInfoIndex] & 127;

    int bytesRequiredToGetLength = 0;

    if(rawLength == SHORT_DATA)
        bytesRequiredToGetLength = SHORT_BYTES;
    else if(rawLength == LONG_DATA)
        bytesRequiredToGetLength = LONG_BYTES;
    else
        return rawLength;

    if(dataReceived < HEADER_LENGTH + bytesRequiredToGetLength)
        return -1;

    return Utilities.ToInt32(data, lengthInfoIndex + 1, bytesRequiredToGetLength);
}

private int WholeChunkLength()
{
    int dataLength = ChunkDataLength();
    int lengthInfoLength = dataLength < SHORT_DATA ? 0 : dataLength < short.MaxValue ? SHORT_BYTES : LONG_BYTES;

    if(dataLength == -1)
        return -1;

    return HEADER_LENGTH + lengthInfoLength + KEYS_LENGTH + dataLength;
}

public void StartReceving()
{
    if(!receivingData)
        ReceiveDataInternally();
}
}

1 个答案:

答案 0 :(得分:0)

当您致电recievier.ReceiveData();时,您认为在方法完成时已经读取了数据,但这是不对的。对该方法的调用仅启动读取,该读取将在调用OnRecevingComplete的匿名委托上异步终止。当应用程序有更多负载只是一个简单的调用时,这将更加明显。如果您使用TcpListener并使用async/await而非Socket以及BeginXXXEndXXX方法,则会更容易。

您似乎也假设一旦读取完成,您的所有数据都在缓冲区中,但可能没有。例如,对于读取6个字节,您可能需要读取6次,或3次,或者可能只是1.您需要在某处继续读取数据while,直到您完成所有预期的有效负载。< / p>

请参阅此处的示例:How can I read from a socket repeatedly?

  public void ContinuousReceive(){
    byte[] buffer = new byte[1024];
    bool terminationCodeReceived = false;
    while(!terminationCodeReceived){
      try{
          if(server.Receive(buffer)>0){
             // We got something
             // Parse the received data and check if the termination code
             // is received or not
          }
      }catch (SocketException e){
          Console.WriteLine("Oops! Something bad happened:" + e.Message);
      }
    }
  }

作为旁注:

return Utilities.ToInt32(Data, lengthInfoIndex + 1, bytesRequiredToGetLength);

Int32有4个字节,因此如果将8个字节解析为Int32,它可能会溢出。另外,请记住长度为ushortuintulong(即:无符号整数),具体取决于标头是否指示&lt; = 125,126或127。

您无法使用多个线程读取,也无法在套接字上使用多个线程进行写入。您可以同时从独立的读写,但不能同时读取相同的操作。这将导致不可预测的错误。你不应该有任何锁,并认为该类不是线程安全的(例如它是NetworkStream)。多线程不会在网络编程中带来更好的性能,只是没有。相反,专注于使您的代码异步。

Good old book that I loved