php的socket通信
2015-07-01 09:57
627 查看
客户端
客户端非常简单,利用现代浏览器的WebSocket API,这里介绍的很详细:http://msdn.microsoft.com/zh-cn/library/ie/hh673567核心代码:
JAVASCRIPT
1 2 3 4 5 | var wsServer = 'ws://127.0.0.1:8080'; var ws = new WebSocket(wsServer); ws.onmessage = function (evt) { do sth }; |
服务器
思路
难点是服务器,没有了apache和nginx这些http服务器在前面顶着,只用php该怎么写?这里有个教程讲的很深入/article/1697916.html
写之前捋一捋思路:
1 监听:首先要挂起一个进程来监听来自客户端的请求
2 握手:对于第一次合法的请求,发送合法的header回去
3 保持连接:有新消息到了就广播出去。直到客户端断开
4 接受另一个请求,重复2和3
关键代码如下:
PHP
1 2 3 4 56 | public function start_server() { $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); //允许使用本地地址 socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, TRUE); socket_bind($this->socket, $this->host, $this->port); //最多10个人连接,超过的客户端连接会返回WSAECONNREFUSED错误 socket_listen($this->socket, $this->maxuser); while(TRUE) { $this->cycle = $this->accept; $this->cycle[] = $this->socket; //阻塞用,有新连接时才会结束 socket_select($this->cycle, $write, $except, null); foreach ($this->cycle as $k => $v) { if($v === $this->socket) { if (($accept = socket_accept($v)) < 0) { continue; } //如果请求来自监听端口那个套接字,则创建一个新的套接字用于通信 $this->add_accept($accept); continue; } $index = array_search($v, $this->accept); if ($index === NULL) { continue; } if (!@socket_recv($v, $data, 1024, 0) || !$data) {//没消息的socket就跳过 $this->close($v); continue; } if (!$this->isHand[$index]) { $this->upgrade($v, $data, $index); if(!empty($this->function['add'])) { call_user_func_array($this->function['add'], array($this)); } continue; } $data = $this->decode($data); if(!empty($this->function['send'])) { call_user_func_array($this->function['send'], array($data, $index, $this)); } } sleep(1); } } //增加一个初次连接的用户 private function add_accept($accept) { $this->accept[] = $accept; $index = array_keys($this->accept); $index = end($index); $this->isHand[$index] = FALSE; } //关闭一个连接 private function close($accept) { $index = array_search($accept, $this->accept); socket_close($accept); unset($this->accept[$index]); unset($this->isHand[$index]); if(!empty($this->function['close'])) { call_user_func_array($this->function['close'], array($this)); } } //响应升级协议 private function upgrade($accept, $data, $index) { if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/",$data,$match)) { $key = base64_encode(sha1($match[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true)); $upgrade = "HTTP/1.1 101 Switching Protocol\r\n" . "Upgrade: websocket\r\n" . "Connection: Upgrade\r\n" . "Sec-WebSocket-Accept: " . $key . "\r\n\r\n"; //必须以两个回车结尾 socket_write($accept, $upgrade, strlen($upgrade)); $this->isHand[$index] = TRUE; } } |
socket_select
这个函数是同时接受多个连接的关键,我的理解它是为了阻塞程序继续往下执行和自动选择当前有活动的连接。socket_select ($sockets, $write = NULL, $except = NULL, NULL);
$sockets可以理解为一个数组,这个数组中存放的是文件描述符。当它有变化(就是有新消息到或者有客户端连接/断开)时,socket_select函数才会返回,继续往下执行。
$write是监听是否有客户端写数据,传入NULL是不关心是否有写变化。
$except是$sockets里面要被排除的元素,传入NULL是”监听”全部。
最后一个参数是超时时间
如果为0:则立即结束
如果为n>1: 则最多在n秒后结束,如遇某一个连接有新动态,则提前返回
如果为null:如遇某一个连接有新动态,则返回
为了理解,dump测试一下:
PHP
1 2 3 4 5 | $this->cycle = $this->accept; $this->cycle[] = $this->socket; var_dump($this->cycle);//array(n),n>=1 socket_select($this->cycle, $write, $except, null);//有活动后继续往下 var_dump($this->cycle);//array(0),n==0 |
socket_accept
此函数接受唯一参数,即前面socket_create创建的socket文件(句柄)。返回一个新的资源,或者FALSE。本函数将会通知socket_listen(),将会传入一个连接的socket资源。一旦成功建立socket连接,将会返回一个新的socket资源,用于通信。如果有多个socket在队列中,那么将会先处理第一个。关键就是这里:如果没有socket连接,那么本函数将会等待,直到有新socket进来。如果前面不用socket_select在没有socket的时候阻塞住程序,那么就卡在这里永远无法结束了。
后面的流程就很清晰了,当有一个新的客户端请求到达,用socket_accept创建一个资源,并加入到$this->accept连接池里面。并将它的标示isHand设为false,那么下次循环(因为$this->cycle[] = $this->socket;$this->cycle有变化,所以socket_select会返回)的时候就会执行upgrade握手。然后等待它的新消息即可。
相关文章推荐
- 小胖学PHP总结1-----PHP的数据类型
- 织梦图集上传照片失败,提示302
- PHP概率算法(适用于抽奖、随机广告)
- PHP+mysql+Highcharts实现饼状统计图
- PHP-Socket服务端客户端发送接收通信实例详解
- 基于thinkphp实现根据用户ip判断地理位置并提供对应天气信息的应用
- 织梦上传图片失败,提示302错误
- php实现socket
- 织梦登录后台,提示验证码不正确
- 织梦搜索时间间隔的修改方法
- 深入浅出讲解:php的socket通信
- 织梦里面的data、module不支持写入
- [php] 面包屑导航
- javaBridge php调用java
- php全面获取url地址栏及各种参数
- php字符串比较函数
- NTP时间服务器
- PHP+mysql+Highcharts实现饼状统计图
- PHP 数组array
- PHP基础语法