我有一个WebSocket服务器,它接受来自客户端的二进制数据流,并在每读取4MB时用另一个文本数据流进行响应。服务器使用IIS 8和asp.net web api。
服务器
public class WebSocketController : ApiController
{
public HttpResponseMessage Get()
{
if (!HttpContext.Current.IsWebSocketRequest)
{
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
HttpContext.Current.AcceptWebSocketRequest(async (context) =>
{
try
{
WebSocket socket = context.WebSocket;
byte[] requestBuffer = new byte[4194304];
int offset = 0;
while (socket.State == WebSocketState.Open)
{
var requestSegment = new ArraySegment<byte>(requestBuffer, offset, requestBuffer.Length - offset);
WebSocketReceiveResult result = await socket.ReceiveAsync(requestSegment, CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
// Send one last response before closing
var response = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Server got " + offset + " bytes\n"));
await socket.SendAsync(response, WebSocketMessageType.Text, true, CancellationToken.None);
// Close
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
break;
}
offset += result.Count;
if (offset == requestBuffer.Length)
{
// Regular response
var response = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Server got 4194304 bytes\n"));
await socket.SendAsync(response, WebSocketMessageType.Text, true, CancellationToken.None);
offset = 0;
}
}
}
catch (Exception ex)
{
// Log and continue
}
});
return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
}
}
字符串
C#客户端使用ClientWebSocket类连接到服务器并发送请求。它创建了一个任务,用于接收来自服务器的响应,该任务与请求发送并行运行。发送完请求后,它在套接字上调用CloseAsync
,然后等待Receive
任务完成。
客户
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace WebSocketClient
{
class Program
{
static void Main(string[] args)
{
try
{
CallWebSocketServer().Wait();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
static async Task CallWebSocketServer()
{
using (ClientWebSocket socket = new ClientWebSocket())
{
await socket.ConnectAsync(new Uri("ws://localhost/RestWebController"), CancellationToken.None);
byte[] buffer = new byte[128 * 1024];
Task receiveTask = Receive(socket);
for (int i = 0; i < 1024; ++i)
{
await socket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Binary, true, CancellationToken.None);
}
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
receiveTask.Wait();
Console.WriteLine("All done");
}
}
static async Task Receive(ClientWebSocket socket)
{
try
{
byte[] recvBuffer = new byte[64 * 1024];
while (socket.State == WebSocketState.Open)
{
var result = await socket.ReceiveAsync(new ArraySegment<byte>(recvBuffer), CancellationToken.None);
Console.WriteLine("Client got {0} bytes", result.Count);
Console.WriteLine(Encoding.UTF8.GetString(recvBuffer, 0, result.Count));
if (result.MessageType == WebSocketMessageType.Close)
{
Console.WriteLine("Close loop complete");
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine("Exception in receive - {0}", ex.Message);
}
}
}
}
型
问题是客户端在CloseAsync
调用时阻塞。
在这种情况下,正常关闭WebSocket的正确方法是什么?
3条答案
按热度按时间cfh9epnr1#
想明白了。
服务器
基本上,我必须调用
ClientWebSocket.CloseOutputAsync
(而不是CloseAsync
)方法来告诉框架不再从客户端发送输出。字符串
客户
然后在
Receive
函数中,我必须允许套接字状态WebSocketState.CloseSent
从服务器接收Close
响应型
huwehgph2#
我使用以下代码的各种组合做了一些研究。该场景是客户端发送数据,直到服务器关闭WebSocket。
服务器
字符串
客户
型
我观察到以下情况:
CloseAsync
成对工作,这意味着第一端调用CloseAsync
并等待,直到另一端也调用它。CloseAsync
和CancelationToken.None
将无限期地等待,人们更喜欢使用CloseOutputAsync
,似乎不需要等待对方的回应。ReceiveAsync
调用返回的结果中可以观察到WebSocket关闭事件,但WebSocket.State
和WebSocket.CloseStatus
仅在ReceiveAsync
之后更新。CloseAsync
和CloseOutputAsync
可以以任意组合配对,以响应WebSocket关闭事件。由于在关闭WebSocket时不需要等待响应,也不需要知道另一端是否处理了close事件,因此CloseOutputAsync
应该更好地使用。CloseAsync
响应服务器的请求,WebSocket.State
将更改为Closed
,随后的SendAsync
和ReceiveAsync
将抛出异常。SendAsync
会抛出IOException,并显示以下消息:“无法将数据写入传输连接:已建立的连接已被主机中的软件中止。”ReceiveAsync
将抛出一个WebSocketException
消息:“远程方在未完成关闭握手的情况下关闭了WebSocket连接。”CloseAsync
等待另一端,因此可以使用它来确定是否已成功接收所有发送的数据。如果双方同时发送和关闭,则不适用。**注意:**我使用.NET 6进行测试。上述观察可能不适用于以其他语言编写的客户端/服务器。
hrirmatl3#
我建议你看看这些链接:
异步服务器:https://msdn.microsoft.com/en-us/library/fx6588te%28v=vs.110%29.aspx
异步客户端:https://msdn.microsoft.com/en-us/library/bew39x2a(v=vs.110).aspx
最近我实现了类似的东西与这些链接作为一个例子.方法“BeginReceive”(用于服务器)和“BeginConnect”(用于客户端)启动每个新线程。这样就不会有任何东西