文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

WebSocket的通信原理和使用

2023-09-13 12:33

关注

一、什么是WebSocket?

1.1 简介

WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信,即允许服务器主动发送信息给客户端。因此,在WebSocket中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输,客户端和服务器之间的数据交换变得更加简单。

1.2 WebSocket的优势

现在,很多网站为了实现推送技术,所用的技术都是Ajax轮询。轮询是在特定的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。

这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求。然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。HTML5定义的WebSocket协议优势如下:

小Header,互相沟通的Header非常小,只有2Bytes左右。
2、服务器不再被动接收到浏览器的请求之后才返回数据,而是在有新数据时就主动推送给浏览器。
3、WebSocket协议能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

1.3 WebSocket的原理 

▪ Websocket协议由RFC 6455定义,协议分为两个部分: 握手阶段和全双工通信阶段。

  客户端发送的header内容 

GET /nickname11 HTTP/1.1 Host: 127.0.0.1:9090Connection: UpgradeUpgrade: websocketSec-WebSocket-Extensions: permessage-deflate; client_max_window_bitsSec-WebSocket-Key: wJdg8v4EJiDsIZg5+s0hY8RUQ2A=Sec-WebSocket-Version: 13Origin: http://127.0.0.1

  服务端响应的header内容,这里的Sec-WebSocket-Accept要根据发送的Sec-WebSocket-Key来处理算出来,计算方法:base64_encode(sha1(websocket_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)) 。

HTTP/1.1 101 Switching ProtocolUpgrade: WebSocketSec-WebSocket-Version: 13Connection: UpgradeSec-WebSocket-Accept: wJdg8v4EJiDsIZg5+s0hY8RUQ2A=

▪ Websocket协议的握手阶段是使用的HTTP协议。

▪ Websocket协议的“全双工”消息通信是基于 TCP/IP 的协议集之上的,客户端和服务端可随时发送数据。协议连接是“ws”或者加密的“wss”。

▪  通信的数据是基于“帧(frame)”的,可以传输文本数据,也可以直接传输二进制数据,效率高。

 一条消息(message)可由一个或多个帧(Frame)组成,很多时候会将帧和消息混用,因为大部分时候一条消息只使用一个帧

二、使用PHP实现WebSocket通信

server.php(服务端) 

master = $this->createWebSocket();         //创建socket连接池        $this->sockets=array($this->master);    }public function start(){while (true) {$changes=$this->sockets;            $write=NULL;            $except=NULL;            //设置非阻塞,让多个连接能同时正常往下执行@socket_select($changes, $write, $except, NULL);foreach($changes as $socket){//判断是否新的socket连接if($socket == $this->master){$client=socket_accept($socket);$key=uniqid();$this->sockets[]=$client;$this->users[$key]=array(                        'client'=>$client,                        'is_shake'=>0                    );}else{$len=0;                    $buffer='';do{                        $l=socket_recv($socket,$buf,1024,0);                        $len+=$l;                        $buffer.=$buf;                    }while($l==1024);$key = $this->search($socket);// 如果接收的信息长度小于7,则该client的socket为断开连接if($len<7){                        unset($this->users[$key]);            socket_close($socket);                        continue;                    }                    //判断连接是否已握手                    if(!$this->users[$key]['is_shake']){                    $this->shake($key, $buffer);                    }else{                    //接收客户端发送消息                    $buffer = $this->getMsg($buffer);                        if($buffer === false){continue;                        }                        //发送消息                        $this->sendMsg($key,$buffer);                    }}}}}protected function intoRedis($data){$redis = new Redis();$redis->pconnect($this->redisIp, $this->redisPort, $this->redisLength);$redis->lpush("ws_".$this->getMd5Key($data['username']), json_encode($data));return true;}protected function search($socket){        foreach ($this->users as $key=>$val){            if($socket==$val['client'])            return $key;        }        return false;    }    protected function shake($key, $buf)    {    preg_match("/Sec-WebSocket-Key: (.*)\r\n/i",$buf,$match);    //用于服务端计算Sec_WebSocket_Accept的固定的字符串    $keyStr = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';        $res= "HTTP/1.1 101 Switching Protocol".PHP_EOL            ."Upgrade: WebSocket".PHP_EOL            ."Sec-WebSocket-Version: 13".PHP_EOL            ."Connection: Upgrade".PHP_EOL            ."Sec-WebSocket-Accept: " . base64_encode(sha1($match[1].$keyStr ,true)) .PHP_EOL.PHP_EOL;  // 注意需要两个换行        // 向客户端应答 Sec-WebSocket-Accept        socket_write($this->users[$key]['client'], $res, strlen($res));        //对已经握手的client做标志        $this->users[$key]['is_shake'] = 1;        return true;    }protected function sendMsg($key, $buffer){$index = strpos($buffer, ":");        $data = [        'username' => substr($buffer, 0, $index),        'msg' => substr($buffer, ($index+1)),        'time' => date("Y-m-d H:i:s", time()),        ];        foreach($this->users as $val){        $msg = $this->buildMsg(json_encode($data));            socket_write($val['client'], $msg, strlen($msg));        }        //通过redis记录消息        $this->intoRedis($data);        echo "
";        print_r($data);}// 编码服务端向客户端发送的内容protected 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);}// 解析客户端向服务端发送的内容protected 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;}//建立WebSocket链接protected function createWebSocket(){    $server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);    socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);//1代表接受所有的数据包    socket_bind($server, $this->ip, $this->port);    socket_listen($server);        echo 'Socket连接创建成功,时间: '.date('Y-m-d H:i:s').PHP_EOL;    return $server;}protected function getMd5Key($username){return md5($username."WebSocket");}}$server = new server();$act = isset($_POST['act']) ? $_POST['act'] : 'start';if($act == 'start'){$server->start();}else if($act == 'getAllMsg'){$server->getRedis();}

getredis.php(获取存在redis的历史消息) 

500, 'msg'=>'act参数不能为空', 'data'=>[]]);exit;}if($act != 'getAllMsg'){echo json_encode(['code'=>500, 'msg'=>'act传参错误', 'data'=>[]]);exit;}$redisIp = '127.0.0.1';$redisPort = 6379;$redisLength = 1024*600;$redis = new Redis();$redis->pconnect($redisIp, $redisPort, $redisLength);$keys = $redis->keys("ws_*");$data = [];if($keys){foreach($keys as $key){$res = $redis->lGetRange($key, 0, -1);if($res){foreach($res as &$val){$val = json_decode($val, JSON_UNESCAPED_UNICODE);$val['time_stamp'] = strtotime($val['time']);}$data = array_merge($res, $data);}}}if($data){$sort = array_column($data, 'time_stamp');array_multisort($sort, SORT_ASC, $data);}echo json_encode(['code'=>200, 'msg'=>'获取成功', 'data'=>$data]);exit;

chat.html(客户端)

   WebSocket聊天室

状态栏

当前用户: 在线情况:离线    


聊天记录栏

webSocket事件输出栏

来源地址:https://blog.csdn.net/m0_68949064/article/details/127729569

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯