websocket 無需經過輪詢服務器的方式以得到響應 同步在線用戶數 上線下線 抓包 3-way-handshake web-linux-shell 開發

 

https://code.google.com/archive/p/phpwebsocket/source/default/sourcephp

The WebSocket API (WebSockets) - Web APIs | MDN
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_APIcss

WebSockets - Web API 接口參考 | MDN
https://developer.mozilla.org/zh-CN/docs/Web/API/WebSockets_APIhtml

WebSockets 是一種先進的技術。它能夠在用戶的瀏覽器和服務器之間打開交互式通訊會話。使用此API,您能夠向服務器發送消息並接收事件驅動的響應,而無需經過輪詢服務器的方式以得到響應。node

接口

WebSocket
用於鏈接WebSocket服務器的主要接口,以後能夠在這個鏈接上發送 和接受數據。
CloseEvent
鏈接關閉時WebSocket對象發送的事件。
MessageEvent
當從服務器獲取到消息的時候WebSocket對象觸發的事件。

工具

  • HumbleNet: 一個在瀏覽器中工做的跨平臺網絡庫。它由一個圍繞websocket和WebRTC的C包裝器組成,抽象了跨瀏覽器的差別,方便了爲遊戲和其它應用程序建立多用戶網絡功能。
  • µWebSockets:由C++11Node.js 實現的高度可擴展的WebSocket服務器和客戶端.。
  • ClusterWS:  輕量級、快速和強大的框架,用於在Node.js.中構建可伸縮的WebSocket應用程序。
  • Socket.IO: 一個基於長輪詢/WebSocket的Node.js第三方傳輸協議。
  • SocketCluster: 一個用於Node.js的pub/sub專一於可伸縮 WebSocket框架。
  • WebSocket-Node: 一個用 Node.js實現WebSocket服務器API。
  • Total.js:一個用Node.js 實現的的Web應用程序框架(例如:WebSocket聊天)。
  • Faye: 一個 Node.jsWebSocket (雙向鏈接)和 EventSource (單向鏈接)的服務器和客戶端。
  • SignalR: SignalR在可用時將隱藏使用WebSockets,在不可用時將優雅地使用其餘技術和技術,而應用程序代碼保持不變。
  • Caddy: 可以將任意命令(stdin/stdout)代理爲websocket的web服務器。
  • ws: 一個流行的WebSocket客戶端和服務器 Node.js庫。
  • jsonrpc-bidirectional: 易於使用異步RPC庫,經過單個WebSocket或RTCDataChannel (WebRTC)鏈接支持雙向調用。TCP / SCTP /等。客戶端和服務器能夠各自承載本身的JSONRPC和服務器端點。
  • rpc-websockets: JSON-RPC 2.0在websocket上實現Node.js和JavaScript。

 

 

同步ws鏈接數來同步在線用戶數python

https://websockets.readthedocs.io/en/stable/intro.html#synchronization-examplegit

Synchronization example

A WebSocket server can receive events from clients, process them to update the application state, and synchronize the resulting state across clients.github

Here’s an example where any client can increment or decrement a counter. Updates are propagated to all connected clients.web

The concurrency model of asyncio guarantees that updates are serialized.shell

Run this script in a console:npm

 

#!/usr/bin/env python

# WS server example that synchronizes state across clients

import asyncio
import json
import logging
import websockets

logging.basicConfig()

STATE = {"value": 0}

USERS = set()


def state_event():
    return json.dumps({"type": "state", **STATE})


def users_event():
    return json.dumps({"type": "users", "count": len(USERS)})


async def notify_state():
    if USERS:  # asyncio.wait doesn't accept an empty list
        message = state_event()
        await asyncio.wait([user.send(message) for user in USERS])


async def notify_users():
    if USERS:  # asyncio.wait doesn't accept an empty list
        message = users_event()
        await asyncio.wait([user.send(message) for user in USERS])


async def register(websocket):
    USERS.add(websocket)
    await notify_users()


async def unregister(websocket):
    USERS.remove(websocket)
    await notify_users()


async def counter(websocket, path):
    # register(websocket) sends user_event() to websocket
    await register(websocket)
    try:
        await websocket.send(state_event())
        async for message in websocket:
            data = json.loads(message)
            if data["action"] == "minus":
                STATE["value"] -= 1
                await notify_state()
            elif data["action"] == "plus":
                STATE["value"] += 1
                await notify_state()
            else:
                logging.error("unsupported event: {}", data)
    finally:
        await unregister(websocket)


start_server = websockets.serve(counter, "localhost", 6789)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

  

Then open this HTML file in several browsers.

 

<!DOCTYPE html>
<html>
    <head>
        <title>WebSocket demo</title>
        <style type="text/css">
            body {
                font-family: "Courier New", sans-serif;
                text-align: center;
            }
            .buttons {
                font-size: 4em;
                display: flex;
                justify-content: center;
            }
            .button, .value {
                line-height: 1;
                padding: 2rem;
                margin: 2rem;
                border: medium solid;
                min-height: 1em;
                min-width: 1em;
            }
            .button {
                cursor: pointer;
                user-select: none;
            }
            .minus {
                color: red;
            }
            .plus {
                color: green;
            }
            .value {
                min-width: 2em;
            }
            .state {
                font-size: 2em;
            }
        </style>
    </head>
    <body>
        <div class="buttons">
            <div class="minus button">-</div>
            <div class="value">?</div>
            <div class="plus button">+</div>
        </div>
        <div class="state">
            <span class="users">?</span> online
        </div>
        <script>
            var minus = document.querySelector('.minus'),
                plus = document.querySelector('.plus'),
                value = document.querySelector('.value'),
                users = document.querySelector('.users'),
                websocket = new WebSocket("ws://127.0.0.1:6789/");
            minus.onclick = function (event) {
                websocket.send(JSON.stringify({action: 'minus'}));
            }
            plus.onclick = function (event) {
                websocket.send(JSON.stringify({action: 'plus'}));
            }
            websocket.onmessage = function (event) {
                data = JSON.parse(event.data);
                switch (data.type) {
                    case 'state':
                        value.textContent = data.value;
                        break;
                    case 'users':
                        users.textContent = (
                            data.count.toString() + " user" +
                            (data.count == 1 ? "" : "s"));
                        break;
                    default:
                        console.error(
                            "unsupported event", data);
                }
            };
        </script>
    </body>
</html>

  

WebSocket是一種網絡傳輸協議,可在單個TCP鏈接上進行全雙工通訊,位於OSI模型應用層。WebSocket協議在2011年由IETF標準化爲RFC 6455,後由RFC 7936補充規範。Web IDL中的WebSocket API由W3C標準化。

WebSocket使得客戶端和服務器之間的數據交換變得更加簡單,容許服務端主動向客戶端推送數據。在WebSocket API中,瀏覽器和服務器只須要完成一次握手,二者之間就能夠建立持久性的鏈接,並進行雙向數據傳輸。

 

背景

如今,不少網站爲了實現推送技術,所用的技術都是輪詢。輪詢是在特定的的時間間隔(如每秒),由瀏覽器對服務器發出HTTP請求,而後由服務器返回最新的數據給客戶端的瀏覽器。這種傳統的模式帶來很明顯的缺點,即瀏覽器須要不斷的向服務器發出請求,然而HTTP請求可能包含較長的頭部,其中真正有效的數據可能只是很小的一部分,顯然這樣會消耗不少的帶寬資源。

比較新的輪詢技術是Comet。這種技術雖然能夠實現雙向通訊,但仍然須要反覆發出請求。並且在Comet中廣泛採用的HTTP長鏈接也會消耗服務器資源。

在這種狀況下,HTML5定義了WebSocket協議,能更好的節省服務器資源和帶寬,而且可以更實時地進行通信。

Websocket使用wswss統一資源標誌符,相似於HTTPS。其中wss表示使用了TLS的Websocket。如:

ws://example.com/wsapi
wss://secure.example.com/wsapi

Websocket與HTTP和HTTPS使用相同的TCP端口,能夠繞過大多數防火牆的限制。默認狀況下,Websocket協議使用80端口;運行在TLS之上時,默認使用443端口。

 

優勢

  • 較少的控制開銷。在鏈接建立後,服務器和客戶端之間交換數據時,用於協議控制的數據包頭部相對較小。在不包含擴展的狀況下,對於服務器到客戶端的內容,此頭部大小隻有2至10字節(和數據包長度有關);對於客戶端到服務器的內容,此頭部還須要加上額外的4字節的掩碼。相對於HTTP請求每次都要攜帶完整的頭部,此項開銷顯著減小了。
  • 更強的實時性。因爲協議是全雙工的,因此服務器能夠隨時主動給客戶端下發數據。相對於HTTP請求須要等待客戶端發起請求服務端才能響應,延遲明顯更少;即便是和Comet等相似的長輪詢比較,其也能在短期內更屢次地傳遞數據。
  • 保持鏈接狀態。與HTTP不一樣的是,Websocket須要先建立鏈接,這就使得其成爲一種有狀態的協議,以後通訊時能夠省略部分狀態信息。而HTTP請求可能須要在每一個請求都攜帶狀態信息(如身份認證等)。
  • 更好的二進制支持。Websocket定義了二進制幀,相對HTTP,能夠更輕鬆地處理二進制內容。
  • 能夠支持擴展。Websocket定義了擴展,用戶能夠擴展協議、實現部分自定義的子協議。如部分瀏覽器支持壓縮等。
  • 更好的壓縮效果。相對於HTTP壓縮,Websocket在適當的擴展支持下,能夠沿用以前內容的上下文,在傳遞相似的數據時,能夠顯著地提升壓縮率。[14]

握手協議

WebSocket 是獨立的、建立在 TCP 上的協議。

Websocket 經過 HTTP/1.1 協議的101狀態碼進行握手。

爲了建立Websocket鏈接,須要經過瀏覽器發出請求,以後服務器進行迴應,這個過程一般稱爲「握手」(handshaking)。

 

 

一個典型的Websocket握手請求以下:

客戶端請求

GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13

服務器迴應

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/

字段說明

  • Connection必須設置Upgrade,表示客戶端但願鏈接升級。
  • Upgrade字段必須設置Websocket,表示但願升級到Websocket協議。
  • Sec-WebSocket-Key是隨機的字符串,服務器端會用這些數據來構造出一個SHA-1的信息摘要。把「Sec-WebSocket-Key」加上一個特殊字符串「258EAFA5-E914-47DA-95CA-C5AB0DC85B11」,而後計算SHA-1摘要,以後進行BASE-64編碼,將結果作爲「Sec-WebSocket-Accept」頭的值,返回給客戶端。如此操做,能夠儘可能避免普通HTTP請求被誤認爲Websocket協議。
  • Sec-WebSocket-Version 表示支持的Websocket版本。RFC6455要求使用的版本是13,以前草案的版本均應當棄用。
  • Origin字段是可選的,一般用來表示在瀏覽器中發起此Websocket鏈接所在的頁面,相似於Referer。可是,與Referer不一樣的是,Origin只包含了協議和主機名稱。
  • 其餘一些定義在HTTP協議中的字段,如Cookie等,也能夠在Websocket中使用。

 

抓包驗證

 

101 Switching Protocols 切換協議。服務器根據客戶端的請求切換協議。只能切換到更高級的協議,例如,切換到HTTP的新版本協議

 

 zh.wikipedia.org/wiki/傳輸控制協議

 

 

 

 

 

https://help.aliyun.com/document_detail/66031.html

一.概述

移動端APP大多數功能都能經過客戶端向服務器端發送請求,服務器應答來完成。好比:用戶註冊,獲取商品列表等能力。

但有一些場景須要服務器向客戶端推送應用內通知,如:用戶之間的即時通訊等功能。這種時候就須要創建一個通訊通道,讓服務器可以給指定的客戶端發送下行通知請求。也就是客戶端和服務器端之間具有雙向通訊的能力。

具有雙向通行能力的架構對於移動APP屬於剛性需求。

使用須知

API網關已經在全部Region開放雙向通訊能力,雙向通訊能力構建於WebSocket協議之上,目前Android,Objective-C,JAVA三種SDK均支持雙向通訊。

實現方式

API網關目前已經在全部Region提供雙向通訊的能力,用戶只須要在API網關上設置三個API,而後下載自動生成的SDK到客戶端,簡單嵌入到客戶端就能完美實現客戶端和服務器端之間的雙向通訊的功能。

下面是利用API網關實現雙向通訊的能力的業務流程簡圖:

流程描述

(1) 客戶端在啓動的時候和API網關創建了WebSocket鏈接,而且將本身的設備ID告知API網關;

(2) 客戶端在WebSocket通道上發起註冊信令;

(3) API網關將註冊信令轉換成HTTP協議發送給用戶後端服務,而且在註冊信令上加上設備ID參數;

(4) 用戶後端服務驗證註冊信令,若是驗證經過,記住用戶設備ID,返回200應答;

(5) 用戶後端服務經過HTTP/HTTPS/WebSocket三種協議中的任意一種向API網關發送下行通知信令,請求中攜帶接收請求的設備ID;

(6) API網關解析下行通知信令,找到指定設備ID的鏈接,將下行通知信令經過WebSocket鏈接發送給指定客戶端;

(7) 客戶端在不想收到用戶後端服務通知的時候,經過WebSocket鏈接發送註銷信令給API網關,請求中不攜帶設備ID;

(8) API網關將註銷信令轉換成HTTP協議發送給用戶後端服務,而且在註冊信令上加上設備ID參數;

(9) 用戶後端服務刪除設備ID,返回200應答。

二.雙向通訊三種管理信令

要使用API網關的雙向通訊能力,首先要了解API網關雙向通訊相關的三種信令,須要注意的是,這三個信令其實就是API網關上的三個API,須要用戶去API網關建立後才能使用。

1.註冊信令

註冊信令是客戶端發送給用戶後端服務的信令,起到兩個做用:

(1)將客戶端的設備ID發送給用戶後端服務,用戶後端服務須要記住這個設備ID。用戶不須要定義設備ID字段,設備ID字段由API網關的SDK自動生成;

(2)用戶能夠將此信令定義爲攜帶用戶名和密碼的API,用戶後端服務在收到註冊信令的驗證客戶端的合法性。用戶後端服務在返回註冊信令應答的時候,返回非200時,API網關會視此狀況爲註冊失敗。

客戶端要想收到用戶後端服務發送過來的通知,須要先發送註冊信令給API網關,收到用戶後端服務的200應答後正式註冊成功。

2.下行通知信令

用戶後端服務,在收到客戶端發送的註冊信令後,記住註冊信令中的設備ID字段,而後就能夠向API網關發送接收方爲這個設備的下行通知信令了。只要這個設備在線,API網關就能夠將此下行通知發送到端。

3.註銷信令

客戶端在不想收到用戶後端服務的通知時發送註銷信令發送給API網關,收到用戶後端服務的200應答後註銷成功,再也不接受用戶後端服務推送的下行消息。

 

 

#!/usr/bin/env python

# WS server example that synchronizes state across clients
# https://pypi.org/project/websockets/

import asyncio
import json
import logging
import websockets

logging.basicConfig()

STATE = {"value": 0}
CMD = {'cmdStr': '', 'cmdRet': ''}
TIME = {'now': ''}
USERS = set()


def state_event():
    return json.dumps({"type": "state", **STATE, **CMD, **TIME})


def users_event():
    return json.dumps({"type": "users", "count": len(USERS)})


def cmd_event():
    return json.dumps({'type': 'cmd', "cmdStr": CMD['cmdStr'], 'cmdRet': CMD['cmdRet']})


async def notify_state():
    if USERS:  # asyncio.wait doesn't accept an empty list
        message = state_event()
        await asyncio.wait([user.send(message) for user in USERS])


async def notify_users():
    if USERS:  # asyncio.wait doesn't accept an empty list
        message = users_event()
        await asyncio.wait([user.send(message) for user in USERS])


async def run_cmd():
    cmd = CMD['cmdStr']
    proc = await asyncio.create_subprocess_shell(cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
    stdout, stderr = await proc.communicate()
    output = ''
    if stdout:
        output += f'[stdout]\n{stdout.decode()}'
    if stderr:
        output += f'[stderr]\n{stderr.decode()}'
    CMD['cmdRet'] = output
    message = cmd_event()
    print(message)
    await asyncio.wait([user.send(message) for user in USERS])


async def register(websocket):
    USERS.add(websocket)
    await notify_users()


async def unregister(websocket):
    USERS.remove(websocket)
    await notify_users()


async def counter(websocket, path):
    # register(websocket) sends user_event() to websocket
    await register(websocket)
    try:
        await websocket.send(state_event())
        async for message in websocket:
            data = json.loads(message)
            if data["action"] == "minus":
                STATE["value"] -= 1
                await notify_state()
            elif data["action"] == "plus":
                STATE["value"] += 1
                await notify_state()
            elif data["action"] == "cmd":
                CMD['cmdStr'] = data['cmdStr']
                await run_cmd()
            else:
                logging.error("unsupported event: {}", data)
    finally:
        await unregister(websocket)


host, port = '0.0.0.0', 6789

start_server = websockets.serve(counter, host, port)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

  

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>WebSocket demo-Shell</title>
    <style type="text/css">
        body {
            font-family: "Courier New", sans-serif;
            text-align: center;
        }

        .buttons {
            font-size: 4em;
            display: flex;
            justify-content: center;
        }

        .button, .value {
            line-height: 1;
            padding: 2rem;
            margin: 2rem;
            border: medium solid;
            min-height: 1em;
            min-width: 1em;
        }

        .button {
            cursor: pointer;
            user-select: none;
        }

        .minus {
            color: red;
        }

        .plus {
            color: green;
        }

        .value {
            min-width: 2em;
        }

        .state {
            font-size: 2em;
        }
    </style>
</head>
<body>
<div class="buttons">
    <div class="minus button">-</div>
    <div class="value">?</div>
    <div class="plus button">+</div>
</div>
<div class="state">
    <span class="users">?</span> online
</div>
<input id="cmdStr" style="margin-top: 4em;width: 50em;height: 10em;" type="text" id="input" name="name" required
       placeholder="請輸入shell命令">
<button id="cmdBtn" style="color: red">cmdBtn--TODO監聽鍵盤</button>
<pre id="cmdRet">
3
</pre>
<script>
var minus = document.querySelector('.minus'),
    plus = document.querySelector('.plus'),
    value = document.querySelector('.value'),
    users = document.querySelector('.users'),
    cmdStr = document.getElementById('cmdStr'),
    cmdBtn = document.getElementById('cmdBtn'),
    cmdRet = document.getElementById('cmdRet'),
    // websocket = new WebSocket("ws://192.168.11.228:6789/");
    websocket = new WebSocket("ws://192.168.11.215:6789/");
minus.onclick = function (event) {
    websocket.send(JSON.stringify({action: 'minus'}));
}

plus.onclick = function (event) {
    websocket.send(JSON.stringify({action: 'plus'}));
}

cmdBtn.onclick = function (event) {
    websocket.send(JSON.stringify({action: 'cmd', cmdStr: cmdStr.value}));
}
websocket.onmessage = function (event) {
    data = JSON.parse(event.data);
    switch (data.type) {
        case 'state':
            value.textContent = data.value;
            break;
        case 'users':
            users.textContent = (
                data.count.toString() + " user" +
                (data.count == 1 ? "" : "s"));
            break;
        case 'cmd':
            cmdRet.textContent = data.cmdRet;
            break;
        default:
            console.error(
                "unsupported event", data);
    }
};
</script>
</body>
</html>
相關文章
相關標籤/搜索