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
CloseEvent
MessageEvent
同步ws鏈接數來同步在線用戶數python
https://websockets.readthedocs.io/en/stable/intro.html#synchronization-examplegit
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使用ws
或wss
的統一資源標誌符,相似於HTTPS。其中wss
表示使用了TLS的Websocket。如:
ws://example.com/wsapi
wss://secure.example.com/wsapi
Websocket與HTTP和HTTPS使用相同的TCP端口,能夠繞過大多數防火牆的限制。默認狀況下,Websocket協議使用80端口;運行在TLS之上時,默認使用443端口。
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/
抓包驗證
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)將客戶端的設備ID發送給用戶後端服務,用戶後端服務須要記住這個設備ID。用戶不須要定義設備ID字段,設備ID字段由API網關的SDK自動生成;
(2)用戶能夠將此信令定義爲攜帶用戶名和密碼的API,用戶後端服務在收到註冊信令的驗證客戶端的合法性。用戶後端服務在返回註冊信令應答的時候,返回非200時,API網關會視此狀況爲註冊失敗。
客戶端要想收到用戶後端服務發送過來的通知,須要先發送註冊信令給API網關,收到用戶後端服務的200應答後正式註冊成功。
用戶後端服務,在收到客戶端發送的註冊信令後,記住註冊信令中的設備ID字段,而後就能夠向API網關發送接收方爲這個設備的下行通知信令了。只要這個設備在線,API網關就能夠將此下行通知發送到端。
客戶端在不想收到用戶後端服務的通知時發送註銷信令發送給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>