强调一下:websocket 是一个应用层协议,协议标识符为ws 即websocket的缩写,wss 为 websocket security的缩写
来自客户端的握手,在服务端看来,就是客户端发过来一段HTTP报头
GET / HTTP/1.1
Host: 101.200.142.148:8880
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
这时,服务端需要响应如下信息给客户端,这样握手就成功了。
客户端:
Document
这是一个websocket的demo
服务端:
大体思路:
使用IO多路复用 socket_select
如果当前活跃的是 套接字 就建立连接 ,并初始化用户列表
如果当前活跃的是 连接 ,就ws握手,如果已经握手,就解码客户端
传来的数据帧,然后调用send方法,将数据帧推送给在线的每个用户。
Ws.php
socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, true);
socket_bind($this->socket, $allow_ip, $port);
socket_listen($this->socket);
$this->sockets[] = $this->socket;
while (true) {
$tmp_sockets = $this->sockets;
socket_select($tmp_sockets, $this->write, $this->except, null);
foreach ($tmp_sockets as $sock) {
if ($sock == $this->socket) {
$connSock = socket_accept($this->socket);
$this->sockets[] = $connSock;
$this->users[] = [
'socket' => $connSock,
'hand_shake' => false,
];
} else {
$index = $this->getUserIndex($sock);
$request = socket_read($sock, 1024);
if(strlen($request) == 8){
$this->logout($index);
}else{
if ($this->users[$index]['hand_shake'] == false) {
$response = $this->handShake($request);
//响应客户端的握手请求
socket_write($sock, $response, strlen($response));
$this->users[$index]['hand_shake'] = true;
} else {
//解码数据帧
$msg = $this->decode($request);
$this->send($msg, $index);
}
}
}
}
}
}
private function logout($user_index){
socket_close($this->users[$user_index]['socket']);
unset($this->users[$user_index]);
$this->sockets = null;
$this->sockets[] = $this->socket;
foreach($this->users as $user){
$this->sockets[] = $user['socket'];
}
}
public function send($msg, $index)
{
$arr = explode('===', $msg);
switch ($arr[0]) {
case 'login':
$this->users[$index]['name'] = $arr[1];
$res['msg'] = $arr[1] . ':登录成功';
$res['type'] = 'login';
//获取所有用户名
$names['userlist'] = $this->getUserNames();
$names['type'] = 'user';
$names = $this->frame(json_encode($names));
//向每个用户推送
foreach ($this->users as $user) {
socket_write($user['socket'], $names, strlen($names));
}
break;
case 'content':
$res['content'] = $arr[1];
$res['name'] = $this->users[$index]['name'];
$res['time'] = date('Y-m-d H:i:s',time());
$res['type'] = 'content';
break;
}
$res = $this->frame(json_encode($res));
//向每个用户推送
foreach ($this->users as $user) {
socket_write($user['socket'], $res, strlen($res));
}
}
private function getUserNames()
{
foreach ($this->users as $user) {
$names[] = $user['name'];
}
return $names;
}
public function getUserIndex($sock)
{
foreach ($this->users as $key => $user) {
if ($user['socket'] == $sock) {
return $key;
}
}
}
public function handShake($request)
{
preg_match("/Sec-WebSocket-Key:(.*)\r\n/", $request, $match);
$key = trim($match[1]);
var_dump($match);
$new_key = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
$response = "HTTP/1.1 101 Switching Protocols\r\n";
$response .= "Upgrade: websocket\r\n";
$response .= "Connection: Upgrade\r\n";
$response .= "Sec-WebSocket-Accept: $new_key\r\n";
$response .= "Sec-WebSocket-Protocol: chat\r\n\r\n";
return $response;
}
//解析客户端发来的数据帧
public function decode($buffer)
{
$len = $masks = $data = $decoded = null;
$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++) {
$decoded .= $data[$index] ^ $masks[$index % 4];
}
return $decoded;
}
//编码数据帧
public function frame($s)
{
$a = str_split($s, 125);
if (count($a) == 1) {
return "\x81" . chr(strlen($a[0])) . $a[0];
}
$ns = "";
foreach ($a as $o) {
$ns .= "\x81" . chr(strlen($o)) . $o;
}
return $ns;
}
}
server.php
index.html
西电221聊骚室
西电221聊骚室
消息框
用户列表