websocket 无需通过轮询服务器的方式以获得响应 同步在线用户数 上线下线 抓包 3-way-handshake web-linux-shell 开发
https://code.google.com/archive/p/phpwebsocket/source/default/source
The WebSocket API (WebSockets) - Web APIs | MDN
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
WebSockets - Web API 接口参考 | MDN
https://developer.mozilla.org/zh-CN/docs/Web/API/WebSockets_API
WebSockets 是一种先进的技术。它可以在用户的浏览器和服务器之间打开交互式通信会话。使用此API,您可以向服务器发送消息并接收事件驱动的响应,而无需通过轮询服务器的方式以获得响应。
接口
WebSocket
- 用于连接WebSocket服务器的主要接口,之后可以在这个连接上发送 和接受数据。
CloseEvent
- 连接关闭时WebSocket对象发送的事件。
MessageEvent
- 当从服务器获取到消息的时候WebSocket对象触发的事件。
工具
- HumbleNet: 一个在浏览器中工作的跨平台网络库。它由一个围绕websocket和WebRTC的C包装器组成,抽象了跨浏览器的差异,方便了为游戏和其它应用程序创建多用户网络功能。
- µWebSockets:由C++11和Node.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.js的WebSocket (双向连接)和 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连接数来同步在线用户数
https://websockets.readthedocs.io/en/stable/intro.html#synchronization-example
Synchronization example
A WebSocket server can receive events from clients, process them to update the application state, and synchronize the resulting state across clients.
Here’s an example where any client can increment or decrement a counter. Updates are propagated to all connected clients.
The concurrency model of asyncio
guarantees that updates are serialized.
Run this script in a console:
#!/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端口。
优点
- 较少的控制开销。在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有2至10字节(和数据包长度有关);对于客户端到服务器的内容,此头部还需要加上额外的4字节的掩码。相对于HTTP请求每次都要携带完整的头部,此项开销显著减少了。
- 更强的实时性。由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少;即使是和Comet等类似的长轮询比较,其也能在短时间内更多次地传递数据。
- 保持连接状态。与HTTP不同的是,Websocket需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。
- 更好的二进制支持。Websocket定义了二进制帧,相对HTTP,可以更轻松地处理二进制内容。
- 可以支持扩展。Websocket定义了扩展,用户可以扩展协议、实现部分自定义的子协议。如部分浏览器支持压缩等。
握手协议
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
- API 网关 >
- 用户指南(开放 API) >
- 双向通信使用指南
一.概述
移动端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>
websocket 无需通过轮询服务器的方式以获得响应 同步在线用户数 上线下线 抓包 3-way-handshake web-linux-shell 开发的更多相关文章
- 浅谈Websocket、Ajax轮询和长轮询(long polling)
浅谈Websocket.Ajax轮询和长轮询(long p0ll) 最近看到了一些介绍Websocket的文章,觉得挺有用,所以在这里将自己的对其三者的理解记录一下. 1.什么是Websocket W ...
- Android学习系列(7)--App轮询服务器消息
这篇文章是android开发人员的必备知识. 1.轮询服务器 一般的应用,定时通知消息可以采用轮询的方法从服务器拿取消息,当然实时消息通知的话,建议采用推送服务. 其中需要注意轮询的频率 ...
- 浅谈Websocket、Ajax轮询和长连接(long pull)
最近看到了一些介绍Websocket的文章,觉得挺有用,所以在这里将自己的对其三者的理解记录一下. 1.什么是Websocket Websocket是HTML5中提出的新的协议,注意,这里是协议,可以 ...
- WebSocket知识、轮询、长轮询、长连接
一.WebSocket理论知识 1.什么是websocket WebSocket是HTML5新增的协议,它的目的是在浏览器和服务器之间建立一个不受限的双向通信的通道,比如说,服务器可以在任意时刻发送消 ...
- DMA为什么比轮询、中断方式性能要卓越非常多?(你不懂)
本文原创为freas_1990,转载请标明出处:http://blog.csdn.net/freas_1990/article/details/35735397 假设是计算机专业出身的同学,都听过一个 ...
- 客户端与服务器持续同步解析(轮询,comet,WebSocket)
在B/S模型的Web应用中,客户端常常需要保持和服务器的持续更新.这种对及时性要求比较高的应用比如:股票价格的查询,实时的商品价格,自动更新的twitter timeline以及基于浏览器的聊天系统( ...
- tornado 10 长轮询和 websocket
tornado 10 长轮询和 websocket 一.长轮询 #在网页,我们经常扫码登录,那么问题来了,前端是如何知道用户在手机上扫码登录的呢 这里就需要用到长轮询 #长轮询 #客户端能够不断地向服 ...
- javascript轮询请求服务器
抛出问题:web浏览器如何与服务保持通信? 方法一:Ajax轮询 方法二:EventSource轮询 方法三:websocket保持长连接 下面的解决方案是,Ajax轮询与EventSource轮询的 ...
- 网页实时聊天之js和jQuery实现ajax长轮询
众所周知,HTTP协议是无状态的,所以一次的请求都是一个单独的事件,和前后都没有联系.所以我们在解决网页实时聊天时就遇到一个问题,如何保证与服务器的长时间联系,从而源源不段地获取信息. 一直以来的方式 ...
随机推荐
- SCUT - 482 - 生成树上的点 - Prufer
https://scut.online/p/482 没听说过这个东西. 洛谷也有这个,所以还是要去接触一些奇奇怪怪的知识才行. https://www.luogu.org/problem/P2290 ...
- mongodb连接警告修复
问题 Node.js中mongoose模块连接MongoDB数据库时提示(node:12580) DeprecationWarning: current URL string parser is de ...
- thrift的php-v0.12版本类自动加载失败
参考网上教程,使用$loader->registerDefinition('Sample', $GEN_DIR); 但是会报PHP Fatal error: Uncaught Error: C ...
- GUI学习之二十八—QMessageBox
今天来学习下QMessageBox. QMessageBox主要用来通知用户或者请求用户提问和接收应答一个模态对话框. 一.对话框的构成 图标是有标准图标的,可以直接调用. 我们声明的消息框,初始状态 ...
- bzoj5020 & loj2289 [THUWC 2017]在美妙的数学王国中畅游 LCT + 泰勒展开
题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=5020 https://loj.ac/problem/2289 题解 这个 appear 和 d ...
- Django【第18篇】:Django之缓存
Django 之缓存 一.缓存 由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将一个某个views的返回值保存 ...
- 连续处理函数reduce
>>> def operat(x,y): return x*y >>> print reduce(operat,(1,2,3,4,5,6,7,8,9,10))362 ...
- 详解Spring MVC 集成EHCache缓存_java - JAVA
文章来源:嗨学网 敏而好学论坛www.piaodoo.com 欢迎大家相互学习 废话少说,直接上代码: ehcache.xml 文件 <?xml version="1.0" ...
- Quick BI的SQL传参建模可以用在什么场景
Quick B的SQL传参建模功能提供基于SQL的数据加工处理能力,减轻了IT支撑人员的工作量.在即席查询SQL中,我们用物理字段显示别名来表示参数的占位符,配置完占位符后,就可以在查询控件中进行参数 ...
- React Native 之FlatList
1.新建项目 2.因为要用到导航跳转, 所以添加依赖,,这里拷贝这个: "dependencies": { "@types/react": "^16. ...