0

0

Websocket协议之php实现

php中文网

php中文网

发布时间:2016-06-06 19:40:55

|

2333人浏览过

|

来源于php中文网

原创

前面学习了HTML5中websocket的握手 协议 、打开和关闭连接等基础内容,最近用php 实现 了与浏览器websocket的双向通信。在学习概念的时候觉得看懂了的内容,真正在实践过程中还是会遇到各种问题,网上也有一些关于php的websocket的 实现 ,但是只有自己亲手

前面学习了html5中websocket的握手协议、打开和关闭连接等基础内容,最近用php实现了与浏览器websocket的双向通信。在学习概念的时候觉得看懂了的内容,真正在实践过程中还是会遇到各种问题,网上也有一些关于php的websocket的实现,但是只有自己亲手写过之后才知道其中的感受。其中,google有一个开源的phpwebsocket类(https://code.google.com/p/phpwebsocket/),但是从其握手过程中可以明显看出,这还是最初的websocket协议,请求头中使用了两个key,并非version 13(现行版本)。下面是本人实践过程,同时封装好了一个现行版本的php实现的实用的websocket类。

一、握手

1、客户端发送请求

websocket协议提供给javascript的API就是特别简洁易用。

Websocket协议之php实现View Code

 

先看效果,客户端和服务器端握手的结果如下:

立即学习PHP免费学习笔记(深入)”;

Websocket协议之php实现

Websocket协议之php实现

2、服务器端

封装的类为WebSocket,address和port为类的属性。

(1)建立socket并监听

 1     function createSocket()
 2     {
 3         $this->master=socket_create(AF_INET, SOCK_STREAM, SOL_TCP)
 4             or die("socket_create() failed:".socket_strerror(socket_last_error()));
 5             
 6         socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1)
 7             or die("socket_option() failed".socket_strerror(socket_last_error()));
 8             
 9         socket_bind($this->master, $this->address, $this->port)
10             or die("socket_bind() failed".socket_strerror(socket_last_error()));
11             
12         socket_listen($this->master,20)
13             or die("socket_listen() failed".socket_strerror(socket_last_error()));
14         
15         $this->say("Server Started : ".date('Y-m-d H:i:s'));
16         $this->say("Master socket  : ".$this->master);
17         $this->say("Listening on   : ".$this->address." port ".$this->port."\n");
18         
19     }

 

然后启动监听,同时要维护连接到服务器的用户的一个数组(连接池),每连接一个用户,就要push进一个,同时关闭连接后要删除相应的用户的连接。

 1     public function __construct($a, $p)
 2     {
 3         if ($a == 'localhost')
 4             $this->address = $a;
 5         else if (preg_match('/^[\d\.]*$/is', $a))
 6             $this->address = long2ip(ip2long($a));
 7         else
 8             $this->address = $p;
 9         
10         if (is_numeric($p) && intval($p) > 1024 && intval($p) < 65536)
11             $this->port = $p;
12         else
13             die ("Not valid port:" . $p);
14         
15         $this->createSocket();
16         array_push($this->sockets, $this->master);
17     }

(2)建立连接

维护用户的连接池

1     public function connect($clientSocket)
2     {
3         $user = new User();
4         $user->id = uniqid();
5         $user->socket = $clientSocket;
6         array_push($this->users,$user);
7         array_push($this->sockets,$clientSocket);
8         $this->log($user->socket . " CONNECTED!" . date("Y-m-d H-i-s"));
9     }

(3)回复响应头

首先要获取请求头,从中取出Sec-Websocket-Key,同时还应该取出Host、请求方式、Origin等,可以进行安全检查,防止未知的连接。

 1     public function getHeaders($req)
 2     {
 3         $r = $h = $o = null;
 4         if(preg_match("/GET (.*) HTTP/"   , $req, $match))
 5             $r = $match[1];
 6         if(preg_match("/Host: (.*)\r\n/"  , $req, $match))
 7             $h = $match[1];
 8         if(preg_match("/Origin: (.*)\r\n/", $req, $match))
 9             $o = $match[1];
10         if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $req, $match))
11             $key = $match[1];
12             
13         return array($r, $h, $o, $key);
14     }

之后是得到key然后进行websocket协议规定的加密算法进行计算,返回响应头,这样浏览器验证正确后就握手成功了。这里涉及的详细解析信息过程参见另一篇博文http://blog.csdn.net/u010487568/article/details/20569027

Simple Groupware0.745
Simple Groupware0.745

Simple Groupware 是一个完整的协同工作套件包。它采用PHP,XML,SQL,HTML,CSS和sgsML开发。Simple Groupware与其它同类型系统不同之处在于使用了新的编程语言sgsML。该语言能够实现快速开发Web应用系统。支持MySQL,Oracle和PostgreSQL。

下载

 1     protected function wrap($msg="", $opcode = 0x1)
 2     {
 3         //默认控制帧为0x1(文本数据)
 4         $firstByte = 0x80 | $opcode;
 5         $encodedata = null;
 6         $len = strlen($msg);
 7         
 8         if (0 <= $len && $len <= 125)
 9             $encodedata = chr(0x81) . chr($len) . $msg;
10         else if (126 <= $len && $len <= 0xFFFF)
11         {
12             $low = $len & 0x00FF;
13             $high = ($len & 0xFF00) >> 8;
14             $encodedata = chr($firstByte) . chr(0x7E) . chr($high) . chr($low) . $msg;
15         }
16         
17         return $encodedata;            
18     }

其中我只实现了发送数据长度在2的16次方以下个字符的情况,至于长度为8个字节的超大数据暂未考虑。

 1      private function doHandShake($user, $buffer)
 2      {
 3         $this->log("\nRequesting handshake...");
 4         $this->log($buffer);
 5         list($resource, $host, $origin, $key) = $this->getHeaders($buffer);
 6         
 7         //websocket version 13
 8         $acceptKey = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
 9         
10         $this->log("Handshaking...");
11         $upgrade  = "HTTP/1.1 101 Switching Protocol\r\n" .
12                     "Upgrade: websocket\r\n" .
13                     "Connection: Upgrade\r\n" .
14                     "Sec-WebSocket-Accept: " . $acceptKey . "\r\n\r\n";  //必须以两个回车结尾
15         $this->log($upgrade);
16         $sent = socket_write($user->socket, $upgrade, strlen($upgrade));
17         $user->handshake=true;
18         $this->log("Done handshaking...");
19         return true;
20     }

二、数据传输

1、客户端

客户端websocket的API非常容易,直接使用websocket对象的send方法即可。

1 ws.send(message);

2、服务器端

客户端发送的数据是经过浏览器支持的websocket进行了mask处理的,而根据规定服务器端返回的数据不能进行掩码处理,但是需要按照协议的数据帧规定进行封装后发送。因此服务器需要接收数据必须将接收到的字节流进行解码。

 1     protected function unwrap($clientSocket, $msg="")
 2     { 
 3         $opcode = ord(substr($msg, 0, 1)) & 0x0F;
 4         $payloadlen = ord(substr($msg, 1, 1)) & 0x7F;
 5         $ismask = (ord(substr($msg, 1, 1)) & 0x80) >> 7;
 6         $maskkey = null;
 7         $oridata = null;
 8         $decodedata = null;
 9         
10         //关闭连接
11         if ($ismask != 1 || $opcode == 0x8)
12         {
13             $this->disconnect($clientSocket);
14             return null;
15         }
16         
17         //获取掩码密钥和原始数据
18         if ($payloadlen <= 125 && $payloadlen >= 0)
19         {
20             $maskkey = substr($msg, 2, 4);
21             $oridata = substr($msg, 6);
22         }
23         else if ($payloadlen == 126)
24         {
25             $maskkey = substr($msg, 4, 4);
26             $oridata = substr($msg, 8);
27         }
28         else if ($payloadlen == 127)
29         {
30             $maskkey = substr($msg, 10, 4);
31             $oridata = substr($msg, 14);
32         }
33         $len = strlen($oridata);
34         for($i = 0; $i < $len; $i++)
35         {
36             $decodedata .= $oridata[$i] ^ $maskkey[$i % 4];
37         }        
38         return $decodedata; 
39     }

其中得到掩码和控制帧后需要进行验证,如果掩码不为1直接关闭,如果控制帧为8也直接关闭。后面的原始数据和掩码获取是通过websocket协议的数据帧规范进行的。

效果如下

Websocket协议之php实现
Websocket协议之php实现
数据交互的过程非常的直接,其中“u”是服务器发送给客户端的,然后客户端发送一段随机字符串给服务器。

三、连接关闭

1、客户端

1 ws.close();

2、服务器端

需要将维护的用户连接池移除相应的连接用户。

 1     public function disconnect($clientSocket)
 2     {
 3         $found = null;
 4         $n = count($this->users);
 5         for($i = 0; $i<$n; $i++)
 6         {
 7             if($this->users[$i]->socket == $clientSocket)
 8             { 
 9                 $found = $i;
10                 break;
11             }
12         }
13         $index = array_search($clientSocket,$this->sockets);
14         
15         if(!is_null($found))
16         { 
17             array_splice($this->users, $found, 1);
18             array_splice($this->sockets, $index, 1); 
19             
20             socket_close($clientSocket);
21             $this->say($clientSocket." DISCONNECTED!");
22         }
23     }

其中遇到的一个问题就是,如果将上述函数中的socket_close语句提出到if语句外面的时候,当浏览器连接到服务器后,F5刷新页面后会发现出错:

Websocket协议之php实现

后来发现是重复关闭socket了,这个是因为在unwrap函数中遇到了控制帧直接关闭的原因。因此需要注意浏览器已经连接后进行刷新的操作。最后提供整个封装好的类,https://github.com/OshynSong/web/blob/master/websocket.class.php

 

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

7

2025.12.31

php网站源码教程大全
php网站源码教程大全

本专题整合了php网站源码相关教程,阅读专题下面的文章了解更多详细内容。

4

2025.12.31

视频文件格式
视频文件格式

本专题整合了视频文件格式相关内容,阅读专题下面的文章了解更多详细内容。

7

2025.12.31

不受国内限制的浏览器大全
不受国内限制的浏览器大全

想找真正自由、无限制的上网体验?本合集精选2025年最开放、隐私强、访问无阻的浏览器App,涵盖Tor、Brave、Via、X浏览器、Mullvad等高自由度工具。支持自定义搜索引擎、广告拦截、隐身模式及全球网站无障碍访问,部分更具备防追踪、去谷歌化、双内核切换等高级功能。无论日常浏览、隐私保护还是突破地域限制,总有一款适合你!

7

2025.12.31

出现404解决方法大全
出现404解决方法大全

本专题整合了404错误解决方法大全,阅读专题下面的文章了解更多详细内容。

42

2025.12.31

html5怎么播放视频
html5怎么播放视频

想让网页流畅播放视频?本合集详解HTML5视频播放核心方法!涵盖<video>标签基础用法、多格式兼容(MP4/WebM/OGV)、自定义播放控件、响应式适配及常见浏览器兼容问题解决方案。无需插件,纯前端实现高清视频嵌入,助你快速打造现代化网页视频体验。

4

2025.12.31

关闭win10系统自动更新教程大全
关闭win10系统自动更新教程大全

本专题整合了关闭win10系统自动更新教程大全,阅读专题下面的文章了解更多详细内容。

3

2025.12.31

阻止电脑自动安装软件教程
阻止电脑自动安装软件教程

本专题整合了阻止电脑自动安装软件教程,阅读专题下面的文章了解更多详细教程。

3

2025.12.31

html5怎么使用
html5怎么使用

想快速上手HTML5开发?本合集为你整理最实用的HTML5使用指南!涵盖HTML5基础语法、主流框架(如Bootstrap、Vue、React)集成方法,以及无需安装、直接在线编辑运行的平台推荐(如CodePen、JSFiddle)。无论你是新手还是进阶开发者,都能轻松掌握HTML5网页制作、响应式布局与交互功能开发,零配置开启高效前端编程之旅!

2

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
swoole入门物联网开发与实战
swoole入门物联网开发与实战

共15课时 | 1.2万人学习

swoole项目实战(第二季)
swoole项目实战(第二季)

共15课时 | 1.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号