PHP实现WebSocket实例详解
WebSocket 协议是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。
WebSocket 通信协议于2011年被 IETF 定为标准 RFC 6455,并被 RFC7936 所补充规范。
—— 百度百科
WebSocket 是一个持久化的协议,这是相对于 http 非持久化来说的。
举个简单的例子,http1.0 的生命周期是以 request 作为界定的,也就是一个 request,一个 response,对于 http 来说,本次 client 与 server 的会话到此结束;而在 http1.1 中,稍微有所改进,即添加了 keep-alive,也就是在一个 http 连接中可以进行多个 request 请求和多个 response 接受操作。然而在实时通信中,并没有多大的作用,http 只能由 client 发起请求,server 才能返回信息,即 server 不能主动向 client 推送信息,无法满足实时通信的要求。而 WebSocket 可以进行持久化连接,即 client 只需进行一次握手,成功后即可持续进行数据通信,值得关注的是 WebSocket 实现 client 与 server 之间全双工通信,即 server 端有数据更新时可以主动推送给 client 端。
上图是一个演示client和server之间建立WebSocket连接时握手部分
client 建立 WebSocket 时向服务器端请求的信息
1
2
3
4
5
6
7
8
|
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket //告诉服务器现在发送的是WebSocket协议 Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== //是一个Base64 encode的值,这个是浏览器随机生成的,用于验证服务器端返回数据是否是WebSocket助理 Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com |
服务器获取到 client 请求的信息后,根据 WebSocket 协议对数据进行处理并返回,其中要对 Sec-WebSocket-Key 进行加密等操作
1
2
3
4
5
|
HTTP/1.1 101 Switching Protocols Upgrade: websocket //依然是固定的,告诉客户端即将升级的是Websocket协议,而不是mozillasocket,lurnarsocket或者shitsocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= //这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key,也就是client要求建立WebSocket验证的凭证 Sec-WebSocket-Protocol: chat |
PHP 服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
<?php if (( $socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP)) < 0) { echo "socket_create() 失败的原因是:" .socket_strerror( $sock ). "\n" ; } if (( $ret = socket_bind( $socket , '127.0.0.1' , '9090' )) < 0) { echo "socket_bind() 失败的原因是:" .socket_strerror( $ret ). "\n" ; } if (( $ret = socket_listen( $socket ,3)) < 0) { echo "socket_listen() 失败的原因是:" .socket_strerror( $ret ). "\n" ; } $all_sockets = [ $socket ]; // socket 集合 do { $copy_sockets = $all_sockets ; // 单独拷贝一份 // 因为客户端是长连接,如果客户端非正常断开,服务端会在 socket_accept 阻塞,现在使用 select 非阻塞模式 socket if (socket_select( $copy_sockets , $write , $except , 0) === false) exit ( 'sosket_select error!' ); // 接收第一次 socket 连入,连入后移除服务端 socket if (in_array( $socket , $copy_sockets )) { $client = socket_accept( $socket ); if ( $client ) { $buf = socket_read( $client , 1024); echo $buf ; // 匹配 Sec-Websocket-Key 标识 if (preg_match( "/Sec-WebSocket-Key: (.*)\r\n/i" , $buf , $match )) { // 需要将 Sec-WebSocket-Key 值累加字符串,并依次进行 SHA-1 加密和 base64 加密 $key = base64_encode (sha1( $match [1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' ,true)); // 拼凑响应内容 $res = "HTTP/1.1 101 Switching Protocol" .PHP_EOL . "Upgrade: WebSocket" .PHP_EOL . "Connection: Upgrade" .PHP_EOL . "WebSocket-Location: ws://127.0.0.1:9090" .PHP_EOL . "Sec-WebSocket-Accept: " . $key .PHP_EOL.PHP_EOL; // 注意这里,需要两个换行 // 向客户端应答 Sec-WebSocket-Accept socket_write( $client , $res , strlen ( $res )); // 向客户端发送消息 socket_write( $client , buildMsg( 'socket ok' ), 1024); // 加入客户端 socket $all_sockets [] = $client ; } // 移除服务端 socket $key = array_search ( $socket , $copy_sockets ); unset( $copy_sockets [ $key ]); // socket_close($client); } } // 循环所有客户端 sockets foreach ( $copy_sockets as $s ) { // 获取客户端发给服务端的内容 $buf = socket_read( $s , 8024); echo strlen ( $buf ). '---' .PHP_EOL; // 代表客户端主动关闭 if ( strlen ( $buf ) < 9) { $key = array_search ( $s , $all_sockets ); unset( $all_sockets [ $key ]); socket_close( $s ); continue ; } // 输出 echo getMsg( $buf ).PHP_EOL; } } while (true); socket_close( $socket ); // 编码服务端向客户端发送的内容 function buildMsg( $msg ) { $frame = []; $frame [0] = '81' ; $len = strlen ( $msg ); if ( $len < 126) { $frame [1] = $len < 16 ? '0' . dechex ( $len ) : dechex ( $len ); } else if ( $len < 65025) { $s = dechex ( $len ); $frame [1] = '7e' . str_repeat ( '0' , 4 - strlen ( $s )) . $s ; } else { $s = dechex ( $len ); $frame [1] = '7f' . str_repeat ( '0' , 16 - strlen ( $s )) . $s ; } $data = '' ; $l = strlen ( $msg ); for ( $i = 0; $i < $l ; $i ++) { $data .= dechex (ord( $msg { $i })); } $frame [2] = $data ; $data = implode( '' , $frame ); return pack( "H*" , $data ); } // 解析客户端向服务端发送的内容 function getMsg( $buffer ) { $res = '' ; $len = ord( $buffer [1]) & 127; if ( $len === 126) { $masks = substr ( $buffer , 4, 4); $data = substr ( $buffer , 8); } else if ( $len === 127) { $masks = substr ( $buffer , 10, 4); $data = substr ( $buffer , 14); } else { $masks = substr ( $buffer , 2, 4); $data = substr ( $buffer , 6); } for ( $index = 0; $index < strlen ( $data ); $index ++) { $res .= $data [ $index ] ^ $masks [ $index % 4]; } return $res ; } |
客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
<!DOCTYPE html> < html lang = "en" > < head > < meta charset = "UTF-8" > < title >Title</ title > < script > // 创建一个Socket实例 var socket = new WebSocket('ws://localhost:9090'); // 打开Socket socket.onopen = function(event) { // 发送一个初始化消息 socket.send("init msg"); }; socket.onmessage = function(event) { console.log('收到消息',event); }; // 监听Socket的关闭 socket.onclose = function(event) { console.log('关闭监听',event); }; function send() { socket.send("client msg"); } </ script > </ head > < body > < button onclick = "send()" >发送消息</ button > </ body > </ html > |
运行测试:
Client
Server
到此这篇关于PHP实现WebSocket实例详解的文章就介绍到这了