您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

Websocket协议之php实现

2025/2/11 10:47:06发布23次查看
前面学习了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就是特别简洁易用。
view code
先看效果,客户端和服务器端握手的结果如下:
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) )11 $this->port = $p;12 else13 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 ) 9 $encodedata = chr(0x81) . chr($len) . $msg;10 else if (126 )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 = 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 )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) 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
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product