javascript中WebSocket用法


一.概念

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。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM