A websocket's ReceiveAsync method does not await the entire message

梦想与她 提交于 2019-12-17 19:16:10

问题


I am receiving JSON through a websocket. At least: I am partially. Using an online websocket service I receive the full JSON response (all the HTML markup is ignored). When I look at the JSON that I receive in my console I can see the HTML markup (viewing it with the HTML viewer during debugging removes the HTML) but it ends abruptly (incomplete data).

My buffer has plenty of space and I am using async-await to (supposedly) wait for the entire response to come in before continuing.

private async Task Receive()
{
  var buffer = new byte[4096 * 20];

  while (_socket.State == WebSocketState.Open)
  {
      var response = await _socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

      if (response.MessageType == WebSocketMessageType.Close)
      {
          await
              _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Close response received",
                  CancellationToken.None);
      }
      else
      {
          var result = Encoding.UTF8.GetString(buffer);
          var a = buffer[1000];
          var b = buffer[10000];
          var c = buffer[50000];
          var d = buffer[81000];
          Console.WriteLine(result);
          var responseObject = JsonConvert.DeserializeObject<Response>(result, _requestParameters.ResponseDataType);

          OnSocketReceive.Invoke(this, new SocketEventArgs {Response = responseObject });
          buffer = new byte[4096 * 20];
      }
  }
}   

Things to note: The buffer is perfectly big enough and b, c and d are never filled. I should also note that this only happens for the 1-questions-newest-tag-java request, 155-questions-active works perfectly fine.

After doing some digging I have found that response.CloseStatus and response.CloseStatusDescription are always null, response.Count is always 1396 (copy-pasting the result in Word does show that there are always 1396 characters) and response.EndOfMessage is false.

Digging through some source code I have found that the DefaultReceiveBufferSize is 16 * 1024 (big enough) and the WebSocketGetDefaultKeepAliveInterval() refers to an external implementation (but the debugger shows 00:00:30).

It is not a matter of timeout since the debugger halts at the same moment the online service receives its response.

Why is my method continuing to execute when the socket has not yet received all data?


回答1:


I might be wrong, but I don't think you're always supposed to receive a complete WebSocket message at once. The server may be sending the message in chunks (that'd correspond to calling SendAsync with endOfMessage: false).

So, do await _socket.ReceiveAsync() in a loop and accumulate the received chunks, until WebSocketReceiveResult.EndOfMessage is true or an error has occured.

On a side note, you probably should be using WebSocket.CreateClientBuffer instead of new ArraySegment<byte>(buffer).




回答2:


Just to complete @Noseratio response, the code would be something like this:

ArraySegment<Byte> buffer = new ArraySegment<byte>(new Byte[8192]);

WebSocketReceiveResult result= null;

using (var ms = new MemoryStream())
{
     do
     {
         result = await socket.ReceiveAsync(buffer, CancellationToken.None);
         ms.Write(buffer.Array, buffer.Offset, result.Count);
     }
     while (!result.EndOfMessage);

     ms.Seek(0, SeekOrigin.Begin);

     if (result.MessageType == WebSocketMessageType.Text)
     {
          using (var reader = new StreamReader(ms, Encoding.UTF8))
          {
               // do stuff
          }
     }
}

Cheers.




回答3:


Following Noseratio's answer I have implemented a temporary buffer that will construct the data of the entire message.

var temporaryBuffer = new byte[BufferSize];
var buffer = new byte[BufferSize * 20];
int offset = 0;
WebSocketReceiveResult response;

while (true)
{
    response = await _socket.ReceiveAsync(
                         new ArraySegment<byte>(temporaryBuffer), 
                         CancellationToken.None);
    temporaryBuffer.CopyTo(buffer, offset);
    offset += response.Count;
    temporaryBuffer = new byte[BufferSize];
    if (response.EndOfMessage)
    {
        break;
    }
}

Full implementation here




回答4:


// Read the bytes from the web socket and accumulate all into a list.
var buffer = new ArraySegment<byte>(new byte[1024]);
WebSocketReceiveResult result = null;
var allBytes = new List<byte>();

do
{
    result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
    for (int i = 0; i < result.Count; i++)
    {
        allBytes.Add(buffer.Array[i]);
    }
}
while (!result.EndOfMessage);

// Optional step to convert to a string (UTF-8 encoding).
var text = Encoding.UTF8.GetString(allBytes.ToArray(), 0, allBytes.Count);



回答5:


Try this:

try
{
    WebSocketReceiveResult result;
    string receivedMessage = "";
    var message = new ArraySegment<byte>(new byte[4096]);
    do
    {
        result = await WebSocket.ReceiveAsync(message, DisconectToken);
        if (result.MessageType != WebSocketMessageType.Text)
            break;
        var messageBytes = message.Skip(message.Offset).Take(result.Count).ToArray();
        receivedMessage += Encoding.UTF8.GetString(messageBytes);                    
    }
    while (!result.EndOfMessage);
    if (receivedMessage != "{}" && !string.IsNullOrEmpty(receivedMessage))
    {
        ResolveWebSocketResponse.Invoke(receivedMessage, Connection);
        Console.WriteLine("Received: {0}", receivedMessage);
    }
}
catch (Exception ex)
{
    var mes = ex.Message;
}


来源:https://stackoverflow.com/questions/23773407/a-websockets-receiveasync-method-does-not-await-the-entire-message

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!