如何在.Net中优雅地关闭双向WebSocket

我有一个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(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(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(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在套接字上调用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(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(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调用时CloseAsync

在这种情况下,正确关闭WebSocket的正确方法是什么?

想出这个。

服务器

基本上,我不得不调用ClientWebSocket.CloseOutputAsync (而不是CloseAsync )方法告诉框架不再从客户端发送输出。

 await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); 

客户

然后在Receive函数中,我不得不允许套接字状态WebSocketState.CloseSent从服务器接收Close响应

 static async Task Receive(ClientWebSocket socket) { try { byte[] recvBuffer = new byte[64 * 1024]; while (socket.State == WebSocketState.Open || socket.State == WebSocketState.CloseSent) { var result = await socket.ReceiveAsync(new ArraySegment(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); } } 

我建议你看看这些链接:

异步服务器: 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”(用于客户端)启动每个新线程。 所以不会有任何阻碍