Websocket协议之php实现
前面学习了HTML5中websocket的握手协议、打开和关闭连接等基础内容,最近用php实现了与浏览器websocket的双向通信。在学习概念的时候觉得看懂了的内容,真正在实践过程中还是会遇到各种问题,网上也有一些关于php的websocket的实现,但是只有自己亲手写过之后才知道其中的感受。其中,google有一个开源的phpwebsocket类(https://code.google.com/p/phpwebsocket/),但是从其握手过程中可以明显看出,这还是最初的websocket协议,请求头中使用了两个KEY,并非version 13(现行版本)。下面是本人实践过程,同时封装好了一个现行版本的php实现的实用的websocket类。
一、握手
1、客户端发送请求
websocket协议提供给javascript的API就是特别简洁易用。
先看效果,客户端和服务器端握手的结果如下:
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
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协议的数据帧规范进行的。
效果如下
数据交互的过程非常的直接,其中“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刷新页面后会发现出错:
后来发现是重复关闭socket了,这个是因为在unwrap函数中遇到了控制帧直接关闭的原因。因此需要注意浏览器已经连接后进行刷新的操作。最后提供整个封装好的类,https://github.com/OshynSong/web/blob/master/websocket.class.php
Websocket协议之php实现的更多相关文章
- Websocket 协议解析
WebSocket protocol 是HTML5一种新的协议.它是实现了浏览器与服务器全双工通信(full-duplex). 现 很多网站为了实现即时通讯,所用的技术都是轮询(po ...
- WebSocket协议开发
一直以来,网络在很大程度上都是围绕着HTTP的请求/响应模式而构建的.客户端加载一个网页,然后直到用户点击下一页之前,什么都不会发生.在2005年左右,Ajax开始让网络变得更加动态了.但所有的HTT ...
- 初识WebSocket协议
1.什么是WebSocket协议 RFC6455文档的表述如下: The WebSocket Protocol enables two-way communication between a clie ...
- Websocket协议的学习、调研和实现
本文章同时发在 cpper.info. 1. websocket是什么 Websocket是html5提出的一个协议规范,参考rfc6455. websocket约定了一个通信的规范,通过一个握手的机 ...
- python测试基于websocket协议的即时通讯接口
随着html5的广泛应用,基于websocket协议的即时通讯有了越来越多的使用场景,本文使用python中的websocket-client模块来做相关的接口测试 import webclient ...
- Websocket协议数据帧传输和关闭连接
之前总结了关于Websocket协议的握手连接方式等其他细节,现在对socket连接建立后的数据帧传输和关闭细节总结. 一.数据帧格式 数据传输使用的是一系列数据帧,出于安全考虑和避免网络截获,客户端 ...
- Websocket协议之握手连接
Websocket协议是为了解决web即时应用中服务器与客户端浏览器全双工通信的问题而设计的,是完全意义上的Web应用端的双向通信技术,可以取代之前使用半双工HTTP协议而模拟全双工通信,同时克服了带 ...
- WebSocket协议
websocket 简介 (2013-04-09 15:39:28) 转载▼ 分类: websocket 一 WebSocket是html5新增加的一种通信协议,目前流行的浏览器都支持这个协议,例 ...
- Jmeter实现WebSocket协议的接口和性能测试方法
WebSocket protocol 是HTML5一种新的协议.它实现了浏览器与服务器全双工通信(full-duplex). 浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一 ...
随机推荐
- 小白日记22:kali渗透测试之提权(二)--抓包嗅探
抓包嗅探 通过抓包嗅探目标机器的流量,发现账号密码. Windows系统 1.Wirehshark 2.Omnipeek 3.commview 4.Sniffpass 只会抓取识别传输密码的明文协议, ...
- IIS7程序发布后 之 报图表处理程序配置 [c:\TempImageFiles\] 中的临时目录无效
把.net4.0的ASP.NET网站布置在IIS7上,原本开发时一切ok,图形都能够出来,但是一旦部署到iis上,再访问的话, 错误问题:图表处理程序配置 [c:\TempImageFiles\] 中 ...
- UVA442 Matrix Chain Multiplication 矩阵运算量计算(栈的简单应用)
栈的练习,如此水题竟然做了两个小时... 题意:给出矩阵大小和矩阵的运算顺序,判断能否相乘并求运算量. 我的算法很简单:比如(((((DE)F)G)H)I),遇到 (就cnt累计加一,字母入栈,遇到) ...
- Clover:让Windows下的资源管理器具有Chrome一样的标签页
这个小巧实用的插件第一次激发了我给人捐款的冲动. 不多说,上图看效果: 具有和Chrome一样的书签功能,以网页的形式保存本地位置,将常用目录放在书签上十分方便. 多标签相比多窗口的优势不需要我多说, ...
- CF A and B and Team Training (数学)
A and B and Team Training time limit per test 1 second memory limit per test 256 megabytes input sta ...
- [改善Java代码]预防线程死锁
线程死锁DeadLock是多线程编码中最头疼的问题,也是最难重现的问题,因为Java是单进程多线程语言.
- [设计模式]<<设计模式之禅>>关于依赖倒置原则
依赖倒置原则(Dependence Inversion Principle,DIP)这个名字看着有点别扭,“依赖”还“倒置”,这到底是什么意思?依赖倒置原则的原始定义是 High level modu ...
- MSP430常见问题之复位系统类
Q1:请问msp430 怎么手动复位啊?是不是连到RST/NMI 上?但是这个脚不是和JTAG 连吗?我看到一些资料上说复位的话还要上拉电阻或者复位电路.A1:JTAG 功能只在下载程序时候使用,正常 ...
- c#语法笔记
书写代码需要注意的地方: 1.代码中出现的所有标点都是英文半角 shift键快速切换中文半角和英文半角 shift+空格 切换全角/半角 2.在c#代码中,每行代码的结束,我们都以分号结束,注意:这个 ...
- 锋利的jQuery第2版学习笔记4、5章
第4章,jQuery中的事件和动画 注意:使用的jQuery版本为1.7.1 jQuery中的事件 JavaScript中通常使用window.onload方法,jQuery中使用$(document ...