前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PHP webSocket实现网页聊天室

PHP webSocket实现网页聊天室

作者头像
用户3094376
发布2018-09-12 11:04:38
7K0
发布2018-09-12 11:04:38
举报
文章被收录于专栏:gaoqin31

一.简介

http请求只能由客户端主动发起,服务器响应的模式, 服务器无法主动向客户端推数据,websocket的出现完美的解决了这一问题。 websocket和http处于同一层,都是基于TCP协议的,客户端和服务器使用websocket通讯的时候需要握手和传输数据两步, 握手借助http状态码101 switch protocol从http协议转换到websocket协议,之后便和http协议无关了。

二.握手

websocket首先由浏览器主动发起一个http请求,主要请求头内容如下: Connection: 告知服务器当前请求连接是升级的 Upgrade: websocket Upgrade 告诉服务器这个http链接是升级的websocket链接 Sec-WebSocket-Version: 13 协议版本 Sec-WebSocket-Key: IYiYjdXLDgHybP4teMOnsQ== 验证key

服务器响应头如下 HTTP/1.1 101 Switching Protocols 表示变换协议 Upgrade: websocket Connection:Upgrade 服务器返回的告知客户端同意使用升级并使用websocket协议,用来完善HTTP升级响应 Sec-WebSocket-Accept:Ev/nT3aIpWH9deAfyYMPbBwkQWo= 客户端 Sec-WebSocket-Key经过加密后的字符串算法 base64_encode(sha1(Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11));

三.数据幀构造和解析

代码语言:javascript
复制
  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-------+-+-------------+-------------------------------+
 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 | |1|2|3|       |K|             |                               |
 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 |     Extended payload length continued, if payload len == 127  |
 + - - - - - - - - - - - - - - - +-------------------------------+
 |                               |Masking-key, if MASK set to 1  |
 +-------------------------------+-------------------------------+
 | Masking-key (continued)       |          Payload Data         |
 +-------------------------------- - - - - - - - - - - - - - - - +
 :                     Payload Data continued ...                :
 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 |                     Payload Data continued ...                |
 +---------------------------------------------------------------+

构造协议文本幀的算法(PHP)

其中的opcode为1代表一个文本帧

代码语言:javascript
复制
private function encode($data){
    $len = strlen($data);
    $encode = '';
    if($len < 126){
        $encode = chr(0x81) . chr($len) . $data;
    }else if($len  >= 126 && $len <= 65535){
        $low = $len & 0x00FF;
        $high = ($len & 0xFF00) >> 8;
        $encode = chr(0x81) . chr(0x7F) . chr($high) . chr($low) . $data; 
    }else{
        encode = chr(0x81) . 
    }
    return $encode;
}

如果playload len < 126,则playloadlen 是数据的真实长度 如果playload len = 126,数据的长度等于playload len后面2个字节对应的无符号整数就是数据的真实长度 如果playload len = 127,数据的长度等于playload len后面8个字节对应的无符号整数就是数据的真实长度

之前对位运算并不熟悉,这里也写下构建数据帧详细的步骤 php使用chr将数据转换为标准ascii所指定的单个字符 长度 < 126 FIN + RSV1 + RSV2 + RSV3 + opcode = 0x81 = 10000001, 再加上数据长度和数据 长度 >=126 <= 65535 FIN + RSV1 + RSV2 + RSV3 + opcode = 0x81 = 10000001 加上 Payload len = 0x7E = 126 由于ASCII范围为 0-127即1个字节,所以必须将2个字节拆分成单个字节即高位$high和低位$low来表示 $low = $len & (11111111 = 0x00FF)这样就取得了$len第二个字节的值。 因为$len是两个字节 取第一个字节的值需要 $len & (1111111100000000 = 0xFF00) 然后向右移8个位

解析文本帧的算法

代码语言:javascript
复制
private function decode($data){
    if(!$data) return array();
    //第一个字节和00001111按位与运算取得的后4位数据就是opcode
    $opcode = ord(substr($data, 0, 1)) & 0x0f;
    //第二个字节和10000000按位与运算,保留第一位的值,然后右移7位取得的就是ismask
    $ismask = (ord(substr($data, 1, 1)) & 0x80) >> 7;
    //第二个字节和01000000按位与运算取得后7位的值就是playloadlen
    $playloadlen = ord(substr($data, 1, 1)) & 0x7f;
    $cdata = $maskkey = $decode = '';
    if($playloadlen < 126){
        $maskkey = substr($data, 2, 4);
        $cdata = substr($data, 6);
    }else if($playloadlen == 126){
        $maskkey = substr($data, 4, 4);
        $cdata = substr($data, 8);
    }else if($playloadlen == 127){
        $maskkey = substr($data, 10, 4);
        $cdata = substr($data, 14);
    }
    if($cdata && $maskkey){
        for($i = 0; $i < strlen($cdata); $i++){
            $decode{$i} = $cdata{$i} ^ $maskkey[$i % 4];
        }
        $decode = join('', $decode);
    }
    return array($opcode, $ismask, $decode);
}

websocket规定客户端发送给服务端的数据必须经过掩码处理,服务器端发送给客户端的数据无需掩码处理, 解码算法: 将playload的原始数据的每个字符下标与4取模,然后将这个原始字符与前面取模后相应位置的掩码字符进行异或运算即可 data[i] = source[i] ^ maskkey[i / 4];

四.PHP服务端

之前对于socket的select方法也不是很了解,

function socket_select (array &$read, array &$write, array &$except, $tv_sec, [, int $tv_usec = 0 ])

1.新连接到来时,被监听的端口是活跃的,如果是新数据到来或者客户端关闭链接时,活跃的是对应的客户端socket而不是服务器上被监听的端口 2.如果客户端发来数据没有被读走,则socket_select将会始终显示客户端是活跃状态并将其保存在read数组中 3.如果客户端先关闭了,则必须手动关闭服务器上相对应的客户端socket,否则socket_select也始终显示该客户端活跃(这个道理跟"有新连接到来然后没有用socket_access把它读出来,导致监听的端口一直活跃"是一样的)

$read是一个引用变量,每次执行的时候传入我们需要监听的socket资源,执行过后,返回活跃的socket资源,核心伪代码如下

$socket = socketcreate(); $socket_select = array($socket); while(true){   $read = $socket_select;   socket_select($read, $write, $except, null);   foreach($read as $sock){     if($sock == $socket){//新连接到来时     }else{//客户端发送数据或者客户端关闭的时候     }    } }

五.客户端

客户端websocket api就很简单了

代码语言:javascript
复制
// 创建一个 websocket 连接
var ws = new WebSocket("ws:XXXXX");

// websocket 创建成功事件
ws.onopen = function () {
};

// websocket 接收到消息事件
ws.onmessage = function (e) {
    var msg = JSON.parse(e.data);
}

// websocket 错误事件
ws.onerror = function () {
};

//websocket 关闭事件
ws.close = function () {
};

完整代码在我的github

参考文章

http://www.cnblogs.com/zhenbianshu/p/6111257.html

http://blog.csdn.net/u010487568/article/details/20908427?utm_source=tuicool&utm_medium=referral

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017-08-10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 参考文章
相关产品与服务
云服务器
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档