>웹 프론트엔드 >JS 튜토리얼 >WebSocket 통신 사용량 분석

WebSocket 통신 사용량 분석

php中世界最好的语言
php中世界最好的语言원래의
2018-05-14 14:32:421776검색

이번에는 WebSocket 통신 사용 분석을 가져왔습니다. WebSocket 통신 사용 분석의 주의사항은 무엇인가요?

WebSocket이란 무엇인가요?

WebSocket은 클라이언트와 서버 간의 양방향 데이터 전송을 위한 표준 프로토콜입니다. 그러나 이는 HTTP와는 아무런 관련이 없습니다. TCP 기반의 독립적인 구현입니다.

과거에는 클라이언트가 서버의 처리 진행 상황을 알고 싶다면 지속적으로 Ajax를 사용하여 폴링해야 했고, 브라우저가 몇 초마다 서버에 요청을 보내도록 해야 했기 때문에 클라이언트에 많은 부담이 가해졌습니다. 섬기는 사람. 또 다른 종류의 폴링은 전화 통화와 유사한 긴 폴링을 사용하는 것입니다. 즉, 클라이언트가 연결을 시작한 후 메시지가 없으면 응답이 끊어지지 않습니다. 클라이언트에 반환되지 않으면 연결 단계가 항상 차단됩니다.

그리고 WebSocket은 이러한 HTTP 문제를 해결합니다. 서버가 프로토콜 업그레이드(HTTP -> WebSocket)를 완료한 후 서버는 클라이언트에 정보를 적극적으로 푸시하여 폴링으로 인해 발생하는 동기화 지연 문제를 해결할 수 있습니다. WebSocket에는 단 한 번의 HTTP 핸드셰이크만 필요하므로 연결이 닫힐 때까지 서버는 클라이언트와 계속 통신할 수 있습니다. 이렇게 하면 서버가 HTTP 프로토콜을 반복적으로 구문 분석할 필요가 없어져 리소스 오버헤드가 줄어듭니다.

새로운 표준의 발전으로 WebSocket은 더욱 성숙해졌으며 다양한 주류 브라우저는 WebSocket에 대한 더 나은 지원을 제공합니다(IE 하위 버전, IE 10 이하와 호환되지 않음). 시간.

WebSocket을 사용할 때, js는 ws 프로토콜을 지원하는데, 이는 가볍게 캡슐화된 Socket 프로토콜과 비슷하지만, 예전에는 Socket 연결을 직접 유지해야 했습니다. 이제 좀 더 표준적인 방식으로 이를 수행할 수 있습니다.

위 그림을 바탕으로 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 메시지와의 차이점:

Upgrade: websocket
Connection: Upgrade

이 두 줄은 WebSocket 프로토콜이 시작되었음을 나타냅니다.

Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13

Sec-WebSocket-Key는 브라우저에 의해 무작위로 생성되며 악의적이거나 의도하지 않은 연결로부터 기본적인 보호 기능을 제공합니다.

Sec-WebSocket-Version은 WebSocket의 버전을 나타냅니다. 처음에는 WebSocket 프로토콜이 너무 많았고 제조업체마다 자체 프로토콜 버전이 있었지만 이제는 해결되었습니다. 서버가 이 버전을 지원하지 않는 경우 서버에서 지원하는 버전 번호가 포함된 Sec-WebSocket-Version 헤더를 반환해야 합니다.

WebSocket 개체 만들기:

var ws = new websocket("ws://127.0.0.1:8001");

ws는 WebSocket 프로토콜을 사용하고 그 뒤에 주소와 포트가 오는 것을 의미합니다.

전체 클라이언트 코드:

<script type="text/javascript">
 var ws;
 var box = document.getElementById('box');
 function startWS() {
 ws = new WebSocket('ws://127.0.0.1:8001');
 ws.onopen = function (msg) {
 console.log('WebSocket opened!');
 };
 ws.onmessage = function (message) {
 console.log('receive message: ' + message.data);
 box.insertAdjacentHTML('beforeend', '<p>' + message.data + '</p>');
 };
 ws.onerror = function (error) {
 console.log('Error: ' + error.name + error.number);
 };
 ws.onclose = function () {
 console.log('WebSocket closed!');
 };
 }
 function sendMessage() {
 console.log('Sending a message...');
 var text = document.getElementById('text');
 ws.send(text.value);
 }
 window.onbeforeunload = function () {
 ws.onclose = function () {}; // 首先关闭 WebSocket
 ws.close()
 };
</script>

서버 응답 메시지 헤더

먼저, 다음을 살펴보겠습니다. 서버 응답 메시지:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

한 줄씩 설명하겠습니다

  • 우선 101 상태 코드는 서버가 클라이언트의 요청을 이해했으며 클라이언트에게 다른 프로토콜을 사용하여 요청을 완료하도록 알릴 것임을 나타냅니다. 업그레이드 메시지 헤더

  • 그런 다음 Sec-WebSocket-Accept는 서버에서 확인되고 암호화된 Sec-WebSocket-Key입니다.

  • 마지막으로 Sec-WebSocket-Protocol은 사용된 최종 프로토콜을 나타냅니다.

Sec-WebSocket-Accept 계산 방법:

  • Sec-WebSocket-Key를 258EAFA5-E914-47DA-95CA-C5AB0DC85B11로 연결

  • SHA1을 통해 다이제스트를 계산하고 base64 문자열로 변환 .

注意:Sec-WebSocket-Key/Sec-WebSocket-Accept 的换算,只能带来基本的保障,但连接是否安全、数据是否安全、客户端 / 服务端是否合法的 ws 客户端、ws 服务端,其实并没有实际性的保证。

创建主线程,用于实现接受 WebSocket 建立请求:

def create_socket():
 # 启动 Socket 并监听连接
 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 try:
 sock.bind(('127.0.0.1', 8001))
 # 操作系统会在服务器 Socket 被关闭或服务器进程终止后马上释放该服务器的端口,否则操作系统会保留几分钟该端口。
 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 sock.listen(5)
 except Exception as e:
 logging.error(e)
 return
 else:
 logging.info('Server running...')
 # 等待访问
 while True:
 conn, addr = sock.accept() # 此时会进入 waiting 状态
 data = str(conn.recv(1024))
 logging.debug(data)
 header_dict = {}
 header, _ = data.split(r'\r\n\r\n', 1)
 for line in header.split(r'\r\n')[1:]:
 key, val = line.split(': ', 1)
 header_dict[key] = val
 if 'Sec-WebSocket-Key' not in header_dict:
 logging.error('This socket is not websocket, client close.')
 conn.close()
 return
 magic_key = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
 sec_key = header_dict['Sec-WebSocket-Key'] + magic_key
 key = base64.b64encode(hashlib.sha1(bytes(sec_key, encoding='utf-8')).digest())
 key_str = str(key)[2:30]
 logging.debug(key_str)
 response = 'HTTP/1.1 101 Switching Protocols\r\n' \
  'Connection: Upgrade\r\n' \
  'Upgrade: websocket\r\n' \
  'Sec-WebSocket-Accept: {0}\r\n' \
  'WebSocket-Protocol: chat\r\n\r\n'.format(key_str)
 conn.send(bytes(response, encoding='utf-8'))
 logging.debug('Send the handshake data')
 WebSocketThread(conn).start()

进行通信

服务端解析 WebSocket 报文

Server 端接收到 Client 发来的报文需要进行解析

Client 包格式

FIN: 占 1bit

0:不是消息的最后一个分片
1:是消息的最后一个分片

RSV1, RSV2, RSV3:各占 1bit

一般情况下全为 0。当客户端、服务端协商采用 WebSocket 扩展时,这三个标志位可以非
0,且值的含义由扩展进行定义。如果出现非零的值,且并没有采用 WebSocket 扩展,连接出错。

Opcode: 4bit

%x0:表示一个延续帧。当 Opcode 为 0 时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片;
%x1:表示这是一个文本帧(text frame);
%x2:表示这是一个二进制帧(binary frame);
%x3-7:保留的操作代码,用于后续定义的非控制帧;
%x8:表示连接断开;
%x9:表示这是一个心跳请求(ping);
%xA:表示这是一个心跳响应(pong);
%xB-F:保留的操作代码,用于后续定义的控制帧。

Mask: 1bit

表示是否要对数据载荷进行掩码异或操作。
0:否
1:是

Payload length: 7bit or (7 + 16)bit or (7 + 64)bit

表示数据载荷的长度
0~126:数据的长度等于该值;
126:后续 2 个字节代表一个 16 位的无符号整数,该无符号整数的值为数据的长度;
127:后续 8 个字节代表一个 64 位的无符号整数(最高位为 0),该无符号整数的值为数据的长度。

Masking-key: 0 or 4bytes

当 Mask 为 1,则携带了 4 字节的 Masking-key;
当 Mask 为 0,则没有 Masking-key。
掩码算法:按位做循环异或运算,先对该位的索引取模来获得 Masking-key 中对应的值 x,然后对该位与 x 做异或,从而得到真实的 byte 数据。
注意:掩码的作用并不是为了防止数据泄密,而是为了防止早期版本的协议中存在的代理缓存污染攻击(proxy cache poisoning attacks)等问题。

Payload Data: 载荷数据

解析 WebSocket 报文代码如下:

def read_msg(data):
 logging.debug(data)
 msg_len = data[1] & 127 # 数据载荷的长度
 if msg_len == 126:
 mask = data[4:8] # Mask 掩码
 content = data[8:] # 消息内容
 elif msg_len == 127:
 mask = data[10:14]
 content = data[14:]
 else:
 mask = data[2:6]
 content = data[6:]
 raw_str = '' # 解码后的内容
 for i, d in enumerate(content):
 raw_str += chr(d ^ mask[i % 4])
 return raw_str

服务端发送 WebSocket 报文

返回时不携带掩码,所以 Mask 位为 0,再按载荷数据的大小写入长度,最后写入载荷数据。

struct 模块解析

struct.pack(fmt, v1, v2, ...)

按照给定的格式 fmt,把数据封装成字符串 ( 实际上是类似于 C 结构体的字节流 )

struct 中支持的格式如下表:

Format C Type Python type Standard size
x pad byte no value
c char bytes of length 1 1
b signed char integer 1
B unsigned char integer 1
? _Bool bool 1
h short integer 2
H unsigned short integer 2
i int integer 4
I unsigned int integer 4
l long integer 4
L unsigned long integer 4
q long long integer 8
Q unsigned long long integer 8
n ssize_t integer
N size_t integer
e -7 float 2
f float float 4
d double float 8
s char[] bytes
p char[] bytes
P void * integer

为了同 C 语言中的结构体交换数据,还要考虑有的 C 或 C++ 编译器使用了字节对齐,通常是以 4 个字节为单位的 32 位系统,故而 struct 根据本地机器字节顺序转换。可以用格式中的第一个字符来改变对齐方式,定义如下:

Character Byte order Size Alignment
@ native native native
= native standard none
<little-endianstandardnone
> big-endian standard none
! network (= big-endian) standard none

发送 WebSocket 报文代码如下:

def write_msg(message):
 data = struct.pack('B', 129) # 写入第一个字节,10000001
 # 写入包长度
 msg_len = len(message)
 if msg_len <= 125:
  data += struct.pack('B', msg_len)
 elif msg_len <= (2 ** 16 - 1):
  data += struct.pack('!BH', 126, msg_len)
 elif msg_len <= (2 ** 64 - 1):
  data += struct.pack('!BQ', 127, msg_len)
 else:
  logging.error('Message is too long!')
  return
 data += bytes(message, encoding='utf-8') # 写入消息内容
 logging.debug(data)
 return data

总结

没有其他能像 WebSocket 一样实现全双工传输的技术了,迄今为止,大部分开发者还是使用 Ajax 轮询来实现,但这是个不太优雅的解决办法,WebSocket 虽然用的人不多,可能是因为协议刚出来的时候有安全性的问题以及兼容的浏览器比较少,但现在都有解决。如果你有这些需求可以考虑使用 WebSocket:

  • 多个用户之间进行交互;

  • 需要频繁地向服务端请求更新数据。

比如弹幕、消息订阅、多玩家游戏、协同编辑、股票基金实时报价、视频会议、在线教育等需要高实时的场景。

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

ES6实现全屏滚动插件步骤详解

Vue中watch使用方法总结

위 내용은 WebSocket 통신 사용량 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.