使用 OWIN 的网络套接字
Websockets using OWIN
到目前为止,我在 Web-api 上看到的所有使用 Microsoft WebSockets 的示例都使用 IIS,实现在 get 方法上,HTTP 连接升级为 websocket 和 websocket 的实例处理程序传递给 HTTPContext
public HttpResponseMessage Get() {
if (HttpContext.Current.IsWebSocketRequest) {
var noteHandler = new NoteSocketHandler();
HttpContext.Current.AcceptWebSocketRequest(noteHandler);
}
return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
}
我想要实现的是在 OWIN 管道上做同样的事情。我面临的问题是正在升级连接以使用 Websockets,但它没有使用我的 websocket 处理程序。我哪里错了?请提出建议。
Controller utilizing OwinContext (Followed the example WebSockets in Nancy using OWIN),
public HttpResponseMessage Get() {
IOwinContext owinContext = Request.GetOwinContext();
WebSocketAccept acceptToken = owinContext.Get<WebSocketAccept>("websocket.Accept");
if (acceptToken != null) {
var requestHeaders = GetValue<IDictionary<string, string[]>>(owinContext.Environment, "owin.RequestHeaders");
Dictionary<string, object> acceptOptions = null;
string[] subProtocols;
if (requestHeaders.TryGetValue("Sec-WebSocket-Protocol", out subProtocols) && subProtocols.Length > 0) {
acceptOptions = new Dictionary<string, object>();
// Select the first one from the client
acceptOptions.Add("websocket.SubProtocol", subProtocols[0].Split(',').First().Trim());
}
acceptToken(acceptOptions, async wsEnv => {
var wsSendAsync = (WebSocketSendAsync)wsEnv["websocket.SendAsync"];
var wsRecieveAsync = (WebSocketReceiveAsync)wsEnv["websocket.ReceiveAsync"];
var wsCloseAsync = (WebSocketCloseAsync)wsEnv["websocket.CloseAsync"];
var wsCallCancelled = (CancellationToken)wsEnv["websocket.CallCancelled"];
//should I pass the handler to an event?
var handler = new NoteSocketHAndler();
});
} else {
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
}
处理程序代码:
using System;
using Socket = Microsoft.Web.WebSockets;
using Newtonsoft.Json;
public class NoteSocketHandler : Socket.WebSocketHandler {
private static Socket.WebSocketCollection connections = new Socket.WebSocketCollection();
public NoteSocketHandler() {
}
public override void OnOpen() {
connections.Add(this);
}
public override void OnClose() {
connections.Remove(this);
}
public override void OnMessage(string message) {
ChatMessage chatMessage = JsonConvert.DeserializeObject<ChatMessage>(message);
foreach (var connection in connections) {
connection.Send(message);
}
}
}
我终于想出了解决问题的方法。您可以在下面找到代码,我还编写了一个使用 websockets on OWIN.
的基本应用程序
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Microsoft.Owin;
namespace NoteApp.WebService.Controller {
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NoteApp.WebService.Handler;
using WebSocketAccept = System.Action<
System.Collections.Generic.IDictionary<string, object>, // WebSocket Accept parameters
System.Func< // WebSocketFunc callback
System.Collections.Generic.IDictionary<string, object>, // WebSocket environment
System.Threading.Tasks.Task>>;
using WebSocketCloseAsync = System.Func<
int, // closeStatus
string, // closeDescription
System.Threading.CancellationToken, // cancel
System.Threading.Tasks.Task>;
using WebSocketReceiveAsync = System.Func<
System.ArraySegment<byte>, // data
System.Threading.CancellationToken, // cancel
System.Threading.Tasks.Task<
System.Tuple< // WebSocketReceiveTuple
int, // messageType
bool, // endOfMessage
int>>>; // count
// closeStatusDescription
using WebSocketReceiveResult = System.Tuple<int, bool, int>;
using WebSocketSendAsync = System.Func<
System.ArraySegment<byte>, // data
int, // message type
bool, // end of message
System.Threading.CancellationToken, // cancel
System.Threading.Tasks.Task>;
public class NoteController : ApiController {
public HttpResponseMessage Get() {
IOwinContext owinContext = Request.GetOwinContext();
WebSocketAccept acceptToken = owinContext.Get<WebSocketAccept>("websocket.Accept");
if (acceptToken != null) {
var requestHeaders = GetValue<IDictionary<string, string[]>>(owinContext.Environment, "owin.RequestHeaders");
Dictionary<string, object> acceptOptions = null;
string[] subProtocols;
if (requestHeaders.TryGetValue("Sec-WebSocket-Protocol", out subProtocols) && subProtocols.Length > 0) {
acceptOptions = new Dictionary<string, object>();
// Select the first one from the client
acceptOptions.Add("websocket.SubProtocol", subProtocols[0].Split(',').First().Trim());
}
acceptToken(acceptOptions, ProcessSocketConnection);
} else {
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
}
private async Task ProcessSocketConnection(IDictionary<string, object> wsEnv) {
var wsSendAsync = (WebSocketSendAsync)wsEnv["websocket.SendAsync"];
var wsCloseAsync = (WebSocketCloseAsync)wsEnv["websocket.CloseAsync"];
var wsCallCancelled = (CancellationToken)wsEnv["websocket.CallCancelled"];
var wsRecieveAsync = (WebSocketReceiveAsync)wsEnv["websocket.ReceiveAsync"];
//pass the sendasync tuple and the cancellation token to the handler. The handler uses the sendasync method to send message. Each connected client has access to this
var handler = new NoteSocketHandler(wsSendAsync, CancellationToken.None);
handler.OnOpen();
var buffer = new ArraySegment<byte>(new byte[100]);
try {
object status;
while (!wsEnv.TryGetValue("websocket.ClientCloseStatus", out status) || (int)status == 0) {
WebSocketReceiveResult webSocketResultTuple = await wsRecieveAsync(buffer, CancellationToken.None);
int count = webSocketResultTuple.Item3;
handler.OnMessage(Encoding.UTF8.GetString(buffer.Array, 0, count));
}
} catch (Exception ex) {
Console.WriteLine(ex.Message);
throw ex;
}
handler.OnClose();
await wsCloseAsync((int)WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
}
T GetValue<T>(IDictionary<string, object> env, string key) {
object value;
return env.TryGetValue(key, out value) && value is T ? (T)value : default(T);
}
}
}
到目前为止,我在 Web-api 上看到的所有使用 Microsoft WebSockets 的示例都使用 IIS,实现在 get 方法上,HTTP 连接升级为 websocket 和 websocket 的实例处理程序传递给 HTTPContext
public HttpResponseMessage Get() {
if (HttpContext.Current.IsWebSocketRequest) {
var noteHandler = new NoteSocketHandler();
HttpContext.Current.AcceptWebSocketRequest(noteHandler);
}
return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
}
我想要实现的是在 OWIN 管道上做同样的事情。我面临的问题是正在升级连接以使用 Websockets,但它没有使用我的 websocket 处理程序。我哪里错了?请提出建议。
Controller utilizing OwinContext (Followed the example WebSockets in Nancy using OWIN),
public HttpResponseMessage Get() {
IOwinContext owinContext = Request.GetOwinContext();
WebSocketAccept acceptToken = owinContext.Get<WebSocketAccept>("websocket.Accept");
if (acceptToken != null) {
var requestHeaders = GetValue<IDictionary<string, string[]>>(owinContext.Environment, "owin.RequestHeaders");
Dictionary<string, object> acceptOptions = null;
string[] subProtocols;
if (requestHeaders.TryGetValue("Sec-WebSocket-Protocol", out subProtocols) && subProtocols.Length > 0) {
acceptOptions = new Dictionary<string, object>();
// Select the first one from the client
acceptOptions.Add("websocket.SubProtocol", subProtocols[0].Split(',').First().Trim());
}
acceptToken(acceptOptions, async wsEnv => {
var wsSendAsync = (WebSocketSendAsync)wsEnv["websocket.SendAsync"];
var wsRecieveAsync = (WebSocketReceiveAsync)wsEnv["websocket.ReceiveAsync"];
var wsCloseAsync = (WebSocketCloseAsync)wsEnv["websocket.CloseAsync"];
var wsCallCancelled = (CancellationToken)wsEnv["websocket.CallCancelled"];
//should I pass the handler to an event?
var handler = new NoteSocketHAndler();
});
} else {
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
}
处理程序代码:
using System;
using Socket = Microsoft.Web.WebSockets;
using Newtonsoft.Json;
public class NoteSocketHandler : Socket.WebSocketHandler {
private static Socket.WebSocketCollection connections = new Socket.WebSocketCollection();
public NoteSocketHandler() {
}
public override void OnOpen() {
connections.Add(this);
}
public override void OnClose() {
connections.Remove(this);
}
public override void OnMessage(string message) {
ChatMessage chatMessage = JsonConvert.DeserializeObject<ChatMessage>(message);
foreach (var connection in connections) {
connection.Send(message);
}
}
}
我终于想出了解决问题的方法。您可以在下面找到代码,我还编写了一个使用 websockets on OWIN.
的基本应用程序using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Microsoft.Owin;
namespace NoteApp.WebService.Controller {
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NoteApp.WebService.Handler;
using WebSocketAccept = System.Action<
System.Collections.Generic.IDictionary<string, object>, // WebSocket Accept parameters
System.Func< // WebSocketFunc callback
System.Collections.Generic.IDictionary<string, object>, // WebSocket environment
System.Threading.Tasks.Task>>;
using WebSocketCloseAsync = System.Func<
int, // closeStatus
string, // closeDescription
System.Threading.CancellationToken, // cancel
System.Threading.Tasks.Task>;
using WebSocketReceiveAsync = System.Func<
System.ArraySegment<byte>, // data
System.Threading.CancellationToken, // cancel
System.Threading.Tasks.Task<
System.Tuple< // WebSocketReceiveTuple
int, // messageType
bool, // endOfMessage
int>>>; // count
// closeStatusDescription
using WebSocketReceiveResult = System.Tuple<int, bool, int>;
using WebSocketSendAsync = System.Func<
System.ArraySegment<byte>, // data
int, // message type
bool, // end of message
System.Threading.CancellationToken, // cancel
System.Threading.Tasks.Task>;
public class NoteController : ApiController {
public HttpResponseMessage Get() {
IOwinContext owinContext = Request.GetOwinContext();
WebSocketAccept acceptToken = owinContext.Get<WebSocketAccept>("websocket.Accept");
if (acceptToken != null) {
var requestHeaders = GetValue<IDictionary<string, string[]>>(owinContext.Environment, "owin.RequestHeaders");
Dictionary<string, object> acceptOptions = null;
string[] subProtocols;
if (requestHeaders.TryGetValue("Sec-WebSocket-Protocol", out subProtocols) && subProtocols.Length > 0) {
acceptOptions = new Dictionary<string, object>();
// Select the first one from the client
acceptOptions.Add("websocket.SubProtocol", subProtocols[0].Split(',').First().Trim());
}
acceptToken(acceptOptions, ProcessSocketConnection);
} else {
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
}
private async Task ProcessSocketConnection(IDictionary<string, object> wsEnv) {
var wsSendAsync = (WebSocketSendAsync)wsEnv["websocket.SendAsync"];
var wsCloseAsync = (WebSocketCloseAsync)wsEnv["websocket.CloseAsync"];
var wsCallCancelled = (CancellationToken)wsEnv["websocket.CallCancelled"];
var wsRecieveAsync = (WebSocketReceiveAsync)wsEnv["websocket.ReceiveAsync"];
//pass the sendasync tuple and the cancellation token to the handler. The handler uses the sendasync method to send message. Each connected client has access to this
var handler = new NoteSocketHandler(wsSendAsync, CancellationToken.None);
handler.OnOpen();
var buffer = new ArraySegment<byte>(new byte[100]);
try {
object status;
while (!wsEnv.TryGetValue("websocket.ClientCloseStatus", out status) || (int)status == 0) {
WebSocketReceiveResult webSocketResultTuple = await wsRecieveAsync(buffer, CancellationToken.None);
int count = webSocketResultTuple.Item3;
handler.OnMessage(Encoding.UTF8.GetString(buffer.Array, 0, count));
}
} catch (Exception ex) {
Console.WriteLine(ex.Message);
throw ex;
}
handler.OnClose();
await wsCloseAsync((int)WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
}
T GetValue<T>(IDictionary<string, object> env, string key) {
object value;
return env.TryGetValue(key, out value) && value is T ? (T)value : default(T);
}
}
}