PHP webSocket实现网页聊天室

一.简介

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));

三.数据幀构造和解析

  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代表一个文本帧

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个位

解析文本帧的算法

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就很简单了

// 创建一个 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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏张戈的专栏

【ES私房菜】Filebeat安装部署及配置详解

Filebeat是Beat成员之一,基于Go语言,无任何依赖,并且比logstash更加轻量,非常适合安装在生产机器上,不会带来过高的资源占用,轻量意味着简单。

10.9K20
来自专栏Huramkin的归档库

ps命令常见用法

Linux中的ps命令用来列出系统中当前运行的那些进程,查看它们的运行状态,占用的资源

18510
来自专栏枕边书

初探PHP多进程

准备 我们都知道PHP是单进程执行的,PHP处理多并发主要是依赖服务器或PHP-FPM的多进程及它们进程的复用,但PHP实现多进程也意义重大,尤其是在后台Cli...

37680
来自专栏搜云库

Spring Boot 中使用 公共配置

常用应用属性 可以在application.properties / application.yml file中指定各种属性,也可以在命令行开关中指定。本节提供...

604100
来自专栏达摩兵的技术空间

跨页面通讯的几种方式

你经常会遇到需要跨标签共享信息的情况,那么本文就跟大家一起回顾下web端有哪些方式可以实现这样的需求。

21250
来自专栏腾讯云Elasticsearch Service

logstash input插件开发

logstash作为一个数据管道中间件,支持对各种类型数据的采集与转换,并将数据发送到各种类型的存储库,比如实现消费kafka数据并且写入到Elasticsea...

75040
来自专栏公众号_薛勤的博客

eclipse不能运行Struts2项目

刚接触Struts2项目,本想写个HelloWorld上手,谁知道光eclipse配置tomcat就鼓捣一晚上,查阅各种资料。

9610
来自专栏蓝天

Linux系统命令Top/free的使用及参数详解

top [-] [d delay] [q] [c] [S] [s] [i] [n]

11620
来自专栏向治洪

cocos2d 在windows环境下搭建

详细搭建步骤如下: 1、Android 开发环境搭建 Android开发环境搭建不是重点,相信看此文章的很多人都已经搭建成功,这里随便概述性的说说。 1、下载 ...

23470
来自专栏FreeBuf

GDB调试CVE-2018-5711 PHP-GD拒绝服务漏洞

下载、编译PHP源码 从github的PHP-src克隆下含有漏洞的版本,最好采取7.0以上版本,编译时候会比较简单,本次选用PHP7.1.9。编译环境为 阿里...

24650

扫码关注云+社区

领取腾讯云代金券