一.概念
WebSocket定义
WebSocket是html5提供的一种在单个TCP连接上进行双向通信的协议,解决了客户端和服务端之间的实时通信问题。浏览器和服务器只需完成一次握手,两者之间就可以创建一个持久性的TCP连接,此后服务器和客户端通过此TCP连接进行双向实时通信。
WebSocket优点
很多网站为了实现数据推送,所用的技术都是ajax轮询。轮询是在特定的时间间隔,由浏览器主动发起请求,将服务器的数据拉回来。轮询需要不断的向服务器发送请求,会占用很多带宽和服务器资源。WebSocket建立TCP连接后,服务器可以主动给客户端传递数据,能够更好的节省服务器资源和带宽,实现更实时的数据通讯
WebSocke的属性

WebSocket的方法

WebSocket的事件
客户端支持WebSocket的浏览器中,在创建socket后,可以通过onopen、onmessage、onclose和onerror四个事件对socket进行响应。WebSocket的所有操作都是采用事件的方式触发的,这样不会阻塞UI,是的UI有更快的响应时间,有更好的用户体验,浏览器通过Javascript向服务器发出建立WebSocket连接的请求,连接建立后,客户端和服务器就可以通过TCP连接直接交换数据。当你获取WebSocket连接后,可以通多send()方法向服务器发送数据,可以通过onmessage事件接收服务器返回的数据.
js实现:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>WebSocket Test</title>
<script language="javascript" type="text/javascript">
var wsUri = "ws://localhost:5000/ws";
var output;
function init() {
output = document.getElementById("output");
document.getElementById('connect').onclick = function (e) {
testWebSocket();
};
}
function testWebSocket() {
websocket = new WebSocket(wsUri);
websocket.onopen = function (evt) {
onOpen(evt)
};
websocket.onclose = function (evt) {
onClose(evt)
};
websocket.onmessage = function (evt) {
onMessage(evt)
};
websocket.onerror = function (evt) {
onError(evt)
};
}
function onOpen(evt) {
writeToScreen("CONNECTED");
doSend("ping");
}
function onClose(evt) {
writeToScreen("DISCONNECTED");
}
function onMessage(evt) {
writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data + '</span>');
websocket.close();
}
function onError(evt) {
writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}
function doSend(message) {
writeToScreen("SENT: " + message);
websocket.send(message);
}
function writeToScreen(message) {
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
window.addEventListener("load", init, false);
</script>
</head>
<body>
<h2>WebSocket Test</h2>
<input type="button" id="connect" value="连接WebSocket"/>
<div id="output"></div>
</body>
</html>
<script>
</script>
后端C#实现:
public class WebsocketHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly IWebsocketCollection _websocketCollection;
public WebsocketHandlerMiddleware(
RequestDelegate next,
ILoggerFactory loggerFactory
)
{
_next = next;
_logger = loggerFactory.
CreateLogger<WebsocketHandlerMiddleware>();
_websocketCollection = WebsocketCollection.Instance;
}
public async Task Invoke(HttpContext context, IAppBasicAuthService appBasicAuth, IConfigService configService)
{
if (context.Request.Path == "/ws")
{
if (context.WebSockets.IsWebSocketRequest)
{
if (!await appBasicAuth.ValidAsync(context.Request))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("basic auth failed .");
return;
}
var appId = context.Request.Headers["appid"];
if (string.IsNullOrEmpty(appId))
{
var appIdSecret = appBasicAuth.GetAppIdSecret(context.Request);
appId = appIdSecret.Item1;
}
context.Request.Query.TryGetValue("client_name", out StringValues name);
if (!string.IsNullOrEmpty(name))
{
name = HttpUtility.UrlDecode(name);
}
else
{
_logger.LogInformation("Websocket client request No Name property ");
}
context.Request.Query.TryGetValue("client_tag", out StringValues tag);
if (!string.IsNullOrEmpty(tag))
{
tag = HttpUtility.UrlDecode(tag);
}
else
{
_logger.LogInformation("Websocket client request No TAG property ");
}
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
var clientIp = GetRemoteIp(context.Request);
var client = new WebsocketClient()
{
Client = webSocket,
Id = Guid.NewGuid().ToString(),
AppId = appId,
LastHeartbeatTime = DateTime.Now,
Name = name,
Tag = tag,
Ip = clientIp.ToString()
};
_websocketCollection.AddClient(client);
_logger.LogInformation("Websocket client {0} Added ", client.Id);
try
{
await Handle(context, client, configService);
}
catch (Exception ex)
{
_logger.LogError(ex, "Handle websocket client {0} err .", client.Id);
await _websocketCollection.RemoveClient(client, WebSocketCloseStatus.Empty, ex.Message);
await context.Response.WriteAsync("500 closed");
}
}
else
{
context.Response.StatusCode = 400;
}
}
else
{
await _next(context);
}
}
public IPAddress GetRemoteIp(HttpRequest httpRequest)
{
IPAddress ip;
var headers = httpRequest.Headers.ToList();
if (headers.Exists((kvp) => kvp.Key == "X-Forwarded-For"))
{
// when running behind a load balancer you can expect this header
var header = headers.First((kvp) => kvp.Key == "X-Forwarded-For").Value.ToString();
IPAddress.TryParse(header, out ip);
}
else
{
// this will always have a value (running locally in development won't have the header)
ip = httpRequest.HttpContext.Connection.RemoteIpAddress;
}
return ip;
}
private async Task Handle(HttpContext context, WebsocketClient socketClient, IConfigService configService)
{
var buffer = new byte[1024 * 2];
WebSocketReceiveResult result = null;
do
{
result = await socketClient.Client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
socketClient.LastHeartbeatTime = DateTime.Now;
var message = await ReadWebsocketMessage(result, buffer);
if (message == "ping")
{
//如果是ping,回复本地数据的md5版本
var appId = context.Request.Headers["appid"];
var md5 = await configService.AppPublishedConfigsMd5CacheWithInheritanced(appId);
await SendMessage(socketClient.Client, $"V:{md5}");
}
else
{
//如果不是心跳消息,回复0
await SendMessage(socketClient.Client, "0");
}
}
while (!result.CloseStatus.HasValue);
_logger.LogInformation($"Websocket close , closeStatus:{result.CloseStatus} closeDesc:{result.CloseStatusDescription}");
await _websocketCollection.RemoveClient(socketClient, result.CloseStatus, result.CloseStatusDescription);
}
private async Task SendMessage(WebSocket webSocket, string message)
{
var data = Encoding.UTF8.GetBytes(message);
await webSocket.SendAsync(new ArraySegment<byte>(data, 0, data.Length), WebSocketMessageType.Text, true, CancellationToken.None);
}
private async Task<string> ReadWebsocketMessage(WebSocketReceiveResult result, ArraySegment<Byte> buffer)
{
using (var ms = new MemoryStream())
{
ms.Write(buffer.Array, buffer.Offset, result.Count);
if (result.MessageType == WebSocketMessageType.Text)
{
ms.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(ms, Encoding.UTF8))
{
return await reader.ReadToEndAsync();
}
}
return "";
}
}
}
这里我用了.NetCore中自定义中间件进行拦截请求,判断请求的路径如果是ws,则做出响应的Response。
