通过原生php实现websocket
- 新建静态页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
// 创建一个Socket实例
var socket = new WebSocket('ws://172.16.130.74: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("test");
}
</script>
</head>
<body>
<button onclick="send()">发送消息</button>
</body>
</html>
- php的实现
<?php
$server=socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
socket_bind($server,'0.0.0.0',9090) or die("error");
socket_listen($server,5);
$allsockets=[$server];
while(true)
{
$copySockets=$allsockets;
if(socket_select($copySockets,$write ,$except,0)===false) exit("error");
if(in_array($server,$copySockets)){
$client=socket_accept($server);
$buf=socket_read($client,8024);
print_r($buf).PHP_EOL;// 客户端请求头
if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/i",$buf,$match)) {
$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 ;//注意这里,需要两个换行
socket_write($client, $res, strlen($res));//握手成功
socket_write($client,buildMsg("hello websocket"));
$allsockets[]=$client;//把 websocket客户端的socket保存起来
}
$key = array_search($server, $copySockets);
unset($copySockets[$key]);
}
foreach($copySockets as $s)
{
$buf=socket_read($s,8024);
if(strlen($buf)<9) //代表客户端主动关闭
{
$key = array_search($s, $allsockets);
unset($allsockets[$key]);//在数组中删除 该socket
socket_close($s);//服务端一定要主动关掉
continue;
}
echo getMsg($buf);//获取客户端发送消息,并转码
echo PHP_EOL;
}
//socket_close($client);//关掉 客户端socket
}
socket_close($server);//关机
//解析客户端发送给服务端的消息
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;
}
//编码发送给客户端的数据
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);
}
- 请求头
GET / HTTP/1.1
Host: 172.16.130.74:9090
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: file://
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.90 Safari/537.36 2345Explorer/9.3.2.17331
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Sec-WebSocket-Key: UuvGbo3XuryZ4r167PmL3w==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
必须是有效的http request 格式;
HTTP request method 必须是GET,协议应不小于1.1 如: Get / HTTP/1.1;
必须包括Upgrade头域,并且其值为”websocket”;
必须包括”Connection” 头域,并且其值为”Upgrade”;
必须包括”Sec-WebSocket-Key”头域,其值采用base64编码的随机16字节长的字符序列;
如果请求来自浏览器客户端,还必须包括Origin头域 。 该头域用于防止未授权的跨域脚本攻击,服务器可以从Origin决定是否接受该WebSocket连接;
必须包括”Sec-webSocket-Version” 头域,当前值必须是13;
可能包括”Sec-WebSocket-Protocol”,表示client(应用程序)支持的协议列表,server选择一个或者没有可接受的协议响应之;
可能包括”Sec-WebSocket-Extensions”, 协议扩展, 某类协议可能支持多个扩展,通过它可以实现协议增强;
可能包括任意其他域,如cookie.
- 响应头
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: Y+Te7S7wQJC0FwXumEdGbv9/Mek=
必须包括Upgrade头域,并且其值为”websocket”;
必须包括Connection头域,并且其值为”Upgrade”;
必须包括Sec-WebSocket-Accept头域,其值是将请求包“Sec-WebSocket-Key”的值,与”258EAFA5- E914-47DA-95CA-C5AB0DC85B11″这个字符串进行拼接,然后对拼接后的字符串进行sha-1运算,再进行base64编码,就是 “Sec-WebSocket-Accept”的值;
应答包中冒号后面有一个空格;
最后需要两个空行作为应答包结束。
请注意:258EAFA5- E914-47DA-95CA-C5AB0DC85B11 这一串加密的字串是固定的,不可更改,否则会握手失败。客户端的请求头实际上是一个http请求,我们只要从头部匹配出 Sec-WebSocket-Key 并且按照固定加密返回即可。
![](https://img.haomeiwen.com/i1414809/c4c67d36ac982ce6.jpg)
![](https://img.haomeiwen.com/i1414809/b800aebd91943f4c.png)
- 端口占用查看命令
- lsof -i:port
- netstat -anpt | grep port
网友评论