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++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连接数来同步在线用户数

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使用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>

  

 

 

 

 

 

posted @ 2017-05-06 20:08  papering  阅读(464)  评论(0编辑  收藏  举报