Heim  >  Artikel  >  Web-Frontend  >  So kommunizieren Sie mit WebSocket

So kommunizieren Sie mit WebSocket

php中世界最好的语言
php中世界最好的语言Original
2018-06-01 14:35:441784Durchsuche

Dieses Mal zeige ich Ihnen, wie Sie die WebSocket-Kommunikation verwenden und welche Vorsichtsmaßnahmen bei der Verwendung der WebSocket-Kommunikation gelten. Hier sind praktische Fälle, werfen wir einen Blick darauf.

Was ist WebSocket?

WebSocket ist ein Standardprotokoll für die bidirektionale Datenübertragung zwischen Client und Server. Aber es hat nichts mit HTTP zu tun, es ist eine unabhängige Implementierung, die auf TCP basiert.

Wenn der Client in der Vergangenheit den Verarbeitungsfortschritt des Servers wissen wollte, musste er ständig Ajax zum Abfragen verwenden, sodass der Browser alle paar Sekunden eine Anfrage an den Server sendete, was zu einem Problem führte großer Druck auf den Server. Eine andere Art der Abfrage ist die Verwendung einer langen Abfrage, die dem Tätigen eines Telefonanrufs ähnelt. Es wird nicht aufgelegt, bis eine Nachricht empfangen wird. Mit anderen Worten: Wenn keine Nachricht vorliegt, wird die Verbindung unterbrochen nicht an den Client zurückgegeben werden, ist die Verbindungsphase immer blockiert.

WebSocket löst diese Probleme von HTTP. Nachdem der Server das Protokoll-Upgrade abgeschlossen hat (HTTP -> WebSocket), kann der Server aktiv Informationen an den Client übertragen und so das durch Abfragen verursachte Problem der Synchronisierungsverzögerung lösen. Da WebSocket nur einen HTTP-Handshake erfordert, kann der Server so lange mit dem Client kommunizieren, bis die Verbindung geschlossen wird. Dadurch entfällt die Notwendigkeit, dass der Server das HTTP-Protokoll wiederholt analysiert, und der Ressourcenaufwand wird reduziert.

Mit der Weiterentwicklung neuer Standards ist WebSocket ausgereifter geworden und verschiedene Mainstream-Browser unterstützen WebSocket besser (nicht kompatibel mit niedrigeren Versionen von IE, IE unter 10). , Sie können einen Blick darauf werfen, wenn Sie Zeit haben.

Bei der Verwendung von WebSocket ist die Front-End-Nutzung relativ standardisiert. js unterstützt das WS-Protokoll. Es fühlt sich ähnlich an wie ein leicht gekapseltes Socket-Protokoll, aber Sie müssen das beibehalten Die bisherigen Verbindungen können nun auf eine standardisiertere Art und Weise hergestellt werden.

Lassen Sie uns anhand des obigen Bildes ausführlich über den Kommunikationsprozess von WebSocket sprechen.

Verbindung herstellen

Kopfzeile der Client-Anfragenachricht

Client-Anfragenachricht:

GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13

Unterschiede zum herkömmlichen HTTP-Nachrichten:

Upgrade: websocket
Connection: Upgrade

Diese beiden Zeilen zeigen an, dass das WebSocket-Protokoll initiiert wurde.

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

Sec-WebSocket-Key wird zufällig vom Browser generiert und bietet grundlegenden Schutz vor böswilligen oder unbeabsichtigten Verbindungen.

Sec-WebSocket-Version gibt die Version von WebSocket an. Anfangs gab es zu viele WebSocket-Protokolle und verschiedene Hersteller hatten ihre eigenen Protokollversionen, aber jetzt ist dies geklärt. Wenn der Server diese Version nicht unterstützt, muss ein Sec-WebSocket-Versionheader zurückgegeben werden, der die vom Server unterstützte Versionsnummer enthält.

Erstellen Sie ein WebSocket-Objekt:

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

ws bedeutet die Verwendung des WebSocket-Protokolls, gefolgt von der Adresse und dem Port

Vollständiger Client-Code:

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

Header der Server-Antwortnachricht

Werfen wir zunächst einen Blick auf die Server-Antwortnachricht:

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

Erläutern wir es Zeile für Zeile

  • Zunächst zeigt 101 Statuscode an, dass der Server die Anfrage des Clients verstanden hat und den Client über den Upgrade-Nachrichtenheader darüber informiert, dass er ein anderes Protokoll verwenden soll, um die Anfrage abzuschließen >

  • Dann ist Sec-WebSocket-Accept der Sec-WebSocket-Key, der vom Server bestätigt und verschlüsselt wurde

  • Schließlich ist Sec- Das WebSocket-Protokoll ist das Protokoll, das die Endverwendung darstellt.

Berechnungsmethode von Sec-WebSocket-Accept:

  • Spleißen Sie Sec-WebSocket-Key mit 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 ;

  • Berechnen Sie den Digest über SHA1 und konvertieren Sie ihn in einen Base64-String.

注意: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中文网其它相关文章!

推荐阅读:

如何制作并使用Vue波纹按钮组件

怎样解决ajax的data参数错误导致页面崩溃

Das obige ist der detaillierte Inhalt vonSo kommunizieren Sie mit WebSocket. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn