WebSocket API

https://msdn.microsoft.com/library/hh673567

http://www.jnecw.com/p/1523

经朋友推荐去一家手游公司面试,原谅我不厚道的只是好奇手游公司到底是啥样的才去的。工作虽然没找到,但是跟他们的技术总监套近乎聊了几乎一晚上,受益良多,知道了运营多个手游大体需要的技术,当然还是厚道的不爆料了。面试中被问及socket和多线程编程,对这两个知识点完全是空白,回来果断开始研究。还是那句话,不懂裁缝的厨师不是好司机。何况这两个知识也在前端开发的范畴之内。

对我来说最快的学习途径是实践,所以找两个东西来练手。一个是websocket一个是webwoker,今天先说第一个。

要理解socket就要先理解http和tcp的区别,简单说就是一个是短链,一个是长链,一个是去服务器拉数据,一个是服务器可以主动推数据。

而socket就是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。-来自网络。

那么如何用php+js做到服务器推呢?

客户端

客户端非常简单,利用现代浏览器的WebSocket API,这里介绍的很详细:http://msdn.microsoft.com/zh-cn/library/ie/hh673567

核心代码:

var wsServer = 'ws://127.0.0.1:8080';
var ws = new WebSocket(wsServer);
ws.onmessage = function (evt) {
do sth
};

前两行会向指定服务器发送一个握手请求,如果服务器返回合法的http头,则握手成功,之后可通过监听onmessage事件来处理服务器发来的消息。还有很多其他事件可监听,见前面的url。

服务器

思路

难点是服务器,没有了apache和nginx这些http服务器在前面顶着,只用php该怎么写?

这里有个教程讲的很深入http://blog.csdn.net/shagoo/article/details/6396089

写之前捋一捋思路:

1 监听:首先要挂起一个进程来监听来自客户端的请求
2 握手:对于第一次合法的请求,发送合法的header回去
3 保持连接:有新消息到了就广播出去。直到客户端断开
4 接受另一个请求,重复2和3

关键代码如下:

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

关键地方有那么几个,一是while(true)挂起进程,不然执行一次后进程就退出了。二是socket_select和socket_accept函数的使用。三是客户端第一次请求时握手。

socket_select

这个函数是同时接受多个连接的关键,我的理解它是为了阻塞程序继续往下执行和自动选择当前有活动的连接。

socket_select ($sockets, $write = NULL, $except = NULL, NULL);

$sockets可以理解为一个数组,这个数组中存放的是文件描述符。当它有变化(就是有新消息到或者有客户端连接/断开)时,socket_select函数才会返回,继续往下执行。
$write是监听是否有客户端写数据,传入NULL是不关心是否有写变化。
$except是$sockets里面要被排除的元素,传入NULL是”监听”全部。
最后一个参数是超时时间
如果为0:则立即结束
如果为n>1: 则最多在n秒后结束,如遇某一个连接有新动态,则提前返回
如果为null:如遇某一个连接有新动态,则返回

为了理解,dump测试一下:

$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_select之前把所有的socket连接都丢进去给它,其中一个有活动时它就把那个连接抛出来给我们用。表达能力有限,大概就是这么个意思。。。

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握手。然后等待它的新消息即可。

程序经调试可以成功运行,php5.3+websocket13。

php使用websocket示例详解

http://www.jb51.net/article/48019.htm

php socket编程参考资料的更多相关文章

  1. JAVA Socket 编程学习笔记(二)

    在上一篇中,使用了 java Socket+Tcp/IP  协议来实现应用程序或客户端--服务器间的实时双向通信,本篇中,将使用 UDP 协议来实现 Socket 的通信. 1. 关于UDP UDP协 ...

  2. Linux下TCP网络编程与基于Windows下C#socket编程间通信

    一.linux下TCP网络编程基础,需要了解相关函数 Socket():用于套接字初始化. Bind():将 socket 与本机上的一个端口绑定,就可以在该端口监听服务请求. Listen():使s ...

  3. Python socket编程应用

    最近因为考试各种复习顺便刷电视剧,感觉跟小伙伴玩的越来越不开心了,一定是最近太闲了,恩.于是想研究一下代理服务器,下载了一份代码,发现竟然还涉及到socket编程,所以把之前网络课的socket聊天室 ...

  4. c++中Socket编程(入门)

    转载 :http://www.cnblogs.com/L-hq815/archive/2012/07/09/2583043.html 但该作者也是转载,国外网站翻译之作 本人在学习Socket编程时, ...

  5. 《网络安全编程基础》之Socket编程

    <网络安全编程基础>之Socket编程 我的代码 server.c // server.cpp : Defines the entry point for the console appl ...

  6. 基于TCP/UDP的socket编程

    基于TCP(面向连接)的socket编程服务器端顺序: 1. 创建套接字(socket) 2. 将套接字绑定到一个本地地址和端口上(bind) 3. 将套接字设为监听模式,准备接收客户请求(liste ...

  7. Linux socket 编程中存在的五个隐患

    前言:         Socket API 是网络应用程序开发中实际应用的标准 API.尽管该 API 简单,但是   开发新手可能会经历一些常见的问题.本文识别一些最常见的隐患并向您显示如何避免它 ...

  8. 20182332 实验四《Java Socket编程 》实验报告

    20182332 实验肆<数据结构与面向对象程序设计>实验报告 课程:<程序设计与数据结构> 班级: 1823 姓名: 盛国榕 学号:20182332 实验教师:王志强 实验日 ...

  9. C++ socket编程-转载

    转自:https://www.cnblogs.com/L-hq815/archive/2012/07/09/2583043.html 若有违规请联系我删除. 介绍 Socket编程让你沮丧吗?从man ...

随机推荐

  1. DB2学习

    1.为了使用ORG_ID,由于OCRM_F_MM_MKT_PLAN a 没有ORG_ID,但是OCRM_F_MM_EXECUTE_INST_DESC d 表里面有,可以使a表连接b表,创建临时表t. ...

  2. poj 2599 A funny game 博弈论

    思路:无向图,走过的点不能在走.dfs搞定…… 再就是后继中有必败点的为必胜点! 代码如下: #include<iostream> #include<cstdio> #incl ...

  3. NPOI之Excel——合并单元格、设置样式、输入公式

    首先建立一个空白的工作簿用作测试,并在其中建立空白工作表,在表中建立空白行,在行中建立单元格,并填入内容: //建立空白工作簿 IWorkbook workbook = new HSSFWorkboo ...

  4. C# 使用WIN32API设置外部程序窗口无边框

    使用代码 var wnd = win32.FindWindowA(null, "窗口标题"); Int32 wndStyle = win32.GetWindowLong(wnd, ...

  5. for语句中声明变量

    在C语言中,局部变量应该在函数的可执行语句之前定义,但在C++中变量可在任何语句位置定义,只要允许程序语句的地方,都允许定义变量. 在C99标准中C同C++一样允许在for循环语句中定义变量.并且这个 ...

  6. iOS:核心动画之关键帧动画CAKeyframeAnimation

    CAKeyframeAnimation——关键帧动画 关键帧动画,也是CAPropertyAnimation的子类,与CABasicAnimation的区别是: –CABasicAnimation只能 ...

  7. swift:入门知识之泛型

    在尖括号里写一个名字来创建一个泛型函数或者类型 例如<T>.<Type> 可以创建泛型类.枚举和结构体 在类型后使用where来指定一个需求列表.例如,要限定实现一个协议的类型 ...

  8. iOS:Autolayout自动布局实例

    Autolayout自动布局实例即可以用故事板布局,也可以用纯代码创建,各有各的优点:用故事板布局,比较方便,而且直观,可以很直白的看到视图布局后的变化:采用代码布局,虽然代码比较多,有些麻烦,但是可 ...

  9. 运行java -version报cannot restore segment prot after reloc: Permission denied

    linux 安装jdk1.6后,运行java -version,没有出现相关的版本信息,而是出现了以下错误: dl failure on line 685Error: failed /usr/loca ...

  10. jmeter之配置文件介绍

    jmeter.bat, jmeter.properties在installpath/bin目录下 jmeter.bat文件包含如下内容: set HEAP=-Xms512m(初始化堆内存大小) -Xm ...