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>