Home > php教程 > php手册 > body text

php长连接,奏是这么简单

WBOY
Release: 2016-06-13 09:01:59
Original
1188 people have browsed it

php长连接,奏是这么简单

说到长链接大家肯定不陌生,就是复用一个链接持续不断的进行数据交互,它不像那些一夜情似的服务,需要频繁的打开和关闭链接,效率低的同时还增加了业务的复杂度。在裆下很多互联网业务场景都需要长连接的支持,比如:游戏、聊天、信息推送等等等,今天我们就一步一步来揭秘php长连接的玩法。我相信任何一项技术的实施都是因为业务场景的需要,所以这次我们还拿聊天室说事儿。

0x00 初试牛刀

记得以前用php写聊天室还是用polling的方式,毫无疑问,一提到polling,肯定会有人说long polling,没错!long polling也很不错,但在nginx+fpm上面玩这个多少有些费劲,毕竟一个请求需要占一个php进程(就算是用apache+php_mod,也需要一个请求一个线程),所以要是几个人随便玩玩还行,一旦放到线上人多起来,这基本就废了。所以还是采用polling的方式,这样不会阻塞进程,并且一个请求能立即得到响应,但是带来的新问题是需要不停的向服务器发送请求,而且随着间隔的时间越大导致消息延迟就越大。

0x01 华丽变身

在经历了上面那种一秒一小卡,三秒一大卡的场面!再也看不下去了,于是决定变身为真正的男人,哦不对,应该是真正的长连接。去他妈的polling, 去他妈的long polling,去他妈的webserver,统统靠边站,让flash socket(或者说websocket)来统治这个世界!开始了真正意义上的长连接之旅。要玩长连接总是少不了跟socket打交道吧,作为世界上最好的语言(没有之一),socket的封装自然是少不了滴。抄起socket_***就开干,于是就有了下面这一托代码,长连接是吧?延迟是吧?socket是吧?汤药费是吧?so easy....

<ol class="dp-c"><li class="alt"><span><span class="vars">$sfd</span><span> = socket_create(AF_INET, SOCK_STREAM, 0); </span></span></li><li><span> </span></li><li class="alt"><span>socket_bind(<span class="vars">$sfd</span><span>, </span><span class="string">"0.0.0.0"</span><span>, 1234); </span></span></li><li><span> </span></li><li class="alt"><span>socket_listen(<span class="vars">$sfd</span><span>, 511); </span></span></li><li><span> </span></li><li class="alt"><span>socket_set_option(<span class="vars">$sfd</span><span>, SOL_SOCKET, SO_REUSEADDR, 1); </span></span></li><li><span> </span></li><li class="alt"><span>socket_set_nonblock(<span class="vars">$sfd</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span><span class="vars">$rfds</span><span> = </span><span class="keyword">array</span><span>(</span><span class="vars">$sfd</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span><span class="vars">$wfds</span><span> = </span><span class="keyword">array</span><span>(); </span></span></li><li><span> </span></li><li class="alt"><span><span class="keyword">do</span><span>{ </span></span></li><li><span> </span></li><li class="alt"><span>    <span class="vars">$rs</span><span> = </span><span class="vars">$rfds</span><span>; </span></span></li><li><span> </span></li><li class="alt"><span>    <span class="vars">$ws</span><span> = </span><span class="vars">$wfds</span><span>; </span></span></li><li><span> </span></li><li class="alt"><span>    <span class="vars">$es</span><span> = </span><span class="keyword">array</span><span>(); </span></span></li><li><span> </span></li><li class="alt"><span>    <span class="vars">$ret</span><span> = socket_select(</span><span class="vars">$rs</span><span>, </span><span class="vars">$ws</span><span>, </span><span class="vars">$es</span><span>, 3); </span></span></li><li><span> </span></li><li class="alt"><span>     </span></li><li><span> </span></li><li class="alt"><span>    <span class="comment">//read event</span><span> </span></span></li><li><span> </span></li><li class="alt"><span>    <span class="keyword">foreach</span><span>(</span><span class="vars">$rs</span><span> </span><span class="keyword">as</span><span> </span><span class="vars">$fd</span><span>){ </span></span></li><li><span> </span></li><li class="alt"><span>        <span class="keyword">if</span><span>(</span><span class="vars">$fd</span><span> == </span><span class="vars">$sfd</span><span>){ </span></span></li><li><span> </span></li><li class="alt"><span>            <span class="vars">$cfd</span><span> = socket_accept(</span><span class="vars">$sfd</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span>            socket_set_nonblock(<span class="vars">$cfd</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span>            <span class="vars">$rfds</span><span>[] = </span><span class="vars">$cfd</span><span>; </span></span></li><li><span> </span></li><li class="alt"><span>            <span class="func">echo</span><span> </span><span class="string">"new client coming, fd=$cfd\n"</span><span>; </span></span></li><li><span> </span></li><li class="alt"><span>        }<span class="keyword">else</span><span>{ </span></span></li><li><span> </span></li><li class="alt"><span>            <span class="vars">$msg</span><span> = socket_read(</span><span class="vars">$fd</span><span>, 1024); </span></span></li><li><span> </span></li><li class="alt"><span>            <span class="keyword">if</span><span>(</span><span class="vars">$msg</span><span> <= 0){ </span></span></li><li><span> </span></li><li class="alt"><span>                <span class="comment">//close</span><span> </span></span></li><li><span> </span></li><li class="alt"><span>            }<span class="keyword">else</span><span>{ </span></span></li><li><span> </span></li><li class="alt"><span>                <span class="comment">//recv msg</span><span> </span></span></li><li><span> </span></li><li class="alt"><span>                <span class="func">echo</span><span> </span><span class="string">"on message, fd=$fd data=$msg\n"</span><span>; </span></span></li><li><span> </span></li><li class="alt"><span>            } </span></li><li><span> </span></li><li class="alt"><span>        } </span></li><li><span> </span></li><li class="alt"><span>    } </span></li><li><span> </span></li><li class="alt"><span>     </span></li><li><span> </span></li><li class="alt"><span>    <span class="comment">//write event</span><span> </span></span></li><li><span> </span></li><li class="alt"><span>    <span class="keyword">foreach</span><span>(</span><span class="vars">$ws</span><span> </span><span class="keyword">as</span><span> </span><span class="vars">$fd</span><span>){ </span></span></li><li><span> </span></li><li class="alt"><span>        socket_write(<span class="vars">$fd</span><span>, ........); </span></span></li><li><span> </span></li><li class="alt"><span>    } </span></li><li><span> </span></li><li class="alt"><span>     </span></li><li><span> </span></li><li class="alt"><span>}<span class="keyword">while</span><span>(true); </span></span></li></ol>
Copy after login

0x02 登峰造极

从玩socket的那天起,google就轻言细语的跟我说,高并发下的select不要用啊,效率底啊,win要用iocp啊, linux要用epoll啊,blablablabla...哦!好吧,既然google都这么说了,我也不能跟他老人家较真不是,又一次决定(为什么要说又呢?)要听google话,把epoll搞起来,可总不能自己写啊?像我这么懒的人还是整个扩展好了,libevent走你!经过疯狂的编(co)码(py),神作终于出山,具体能有多高效,能撑多少并发,不造,反正没用select了,我奏是屌!

<ol class="dp-c"><li class="alt"><span><span class="vars">$sfd</span><span> = stream_socket_server (</span><span class="string">'tcp://0.0.0.0:1234'</span><span>, </span><span class="vars">$errno</span><span>, </span><span class="vars">$errstr</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span>stream_set_blocking(<span class="vars">$sfd</span><span>, 0); </span></span></li><li><span> </span></li><li class="alt"><span><span class="vars">$base</span><span> = event_base_new(); </span></span></li><li><span> </span></li><li class="alt"><span><span class="vars">$event</span><span> = event_new(); </span></span></li><li><span> </span></li><li class="alt"><span>event_set(<span class="vars">$event</span><span>, </span><span class="vars">$sfd</span><span>, EV_READ | EV_PERSIST, </span><span class="string">'ev_accept'</span><span>, </span><span class="vars">$base</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span>event_base_set(<span class="vars">$event</span><span>, </span><span class="vars">$base</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span>event_add(<span class="vars">$event</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span>event_base_loop(<span class="vars">$base</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span><span class="keyword">function</span><span> ev_accept(</span><span class="vars">$socket</span><span>, </span><span class="vars">$flag</span><span>, </span><span class="vars">$base</span><span>) </span></span></li><li><span> </span></li><li class="alt"><span>{ </span></li><li><span> </span></li><li class="alt"><span>    <span class="vars">$connection</span><span> = stream_socket_accept(</span><span class="vars">$socket</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span>    stream_set_blocking(<span class="vars">$connection</span><span>, 0); </span></span></li><li><span> </span></li><li class="alt"><span>    <span class="vars">$buffer</span><span> = event_buffer_new(</span><span class="vars">$connection</span><span>, </span><span class="string">'ev_read'</span><span>, NULL, </span><span class="string">'ev_error'</span><span>,  </span><span class="vars">$connection</span><span>);     </span></span></li><li><span> </span></li><li class="alt"><span>    event_buffer_base_set(<span class="vars">$buffer</span><span>, </span><span class="vars">$base</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span>    event_buffer_timeout_set(<span class="vars">$buffer</span><span>, 30, 30); </span></span></li><li><span> </span></li><li class="alt"><span>    event_buffer_watermark_set(<span class="vars">$buffer</span><span>, EV_READ, 0, 0xffffff); </span></span></li><li><span> </span></li><li class="alt"><span>    event_buffer_priority_set(<span class="vars">$buffer</span><span>, 10); </span></span></li><li><span> </span></li><li class="alt"><span>    event_buffer_enable(<span class="vars">$buffer</span><span>, EV_READ | EV_PERSIST); </span></span></li><li><span> </span></li><li class="alt"><span>} </span></li><li><span> </span></li><li class="alt"><span><span class="keyword">function</span><span> ev_error(</span><span class="vars">$buffer</span><span>, </span><span class="vars">$error</span><span>, </span><span class="vars">$connection</span><span>) </span></span></li><li><span> </span></li><li class="alt"><span>{ </span></li><li><span> </span></li><li class="alt"><span>    event_buffer_disable(<span class="vars">$buffer</span><span>, EV_READ | EV_WRITE);                 </span></span></li><li><span> </span></li><li class="alt"><span>    event_buffer_free(<span class="vars">$buffer</span><span>);                 </span></span></li><li><span> </span></li><li class="alt"><span>    fclose(<span class="vars">$connection</span><span>);                 </span></span></li><li><span> </span></li><li class="alt"><span>} </span></li><li><span> </span></li><li class="alt"><span><span class="keyword">function</span><span> ev_read(</span><span class="vars">$buffer</span><span>, </span><span class="vars">$connection</span><span>) </span></span></li><li><span> </span></li><li class="alt"><span>{ </span></li><li><span> </span></li><li class="alt"><span>    <span class="vars">$read</span><span> = event_buffer_read(</span><span class="vars">$buffer</span><span>, 256); </span></span></li><li><span> </span></li><li class="alt"><span>    <span class="comment">//do something....</span><span> </span></span></li><li><span> </span></li><li class="alt"><span>} </span></li></ol>
Copy after login

0x03 绝处逢生

随着人数的增长,并发的提升,单个进程已经满足不了需求了,田伯光的故事告诉我们,单挑是斗不过群P的,咋整?俗话说,大事化小,小事化,停!!别化了,再化就没了。拆吧,把单进程拆成多进程,可是拆完之后又面临新的问题,进程间通信、负载均衡、session唯一等。既然已经提出这样的问题,肯定是有解决方案,现成的就有扩展和库来解决这个事,比如:swoole,workerman等?相比之下swoole更屌一些,性、功能,呃!好像这样简写不太雅观,好吧,性能和功能更屌一些(桶哥,请原谅我的无聊~)。。。。等一下!!!但是,我们在使用php来开发web的时候,也没有使用webserver相关的库来做开发对不对?咱只是简单的echo而已。这些繁杂的事都交给了nginx或者是apache,是他们义无反顾的顶在前面,让我们可以专心写逻辑。写web我们只需要简单的配置nginx和fpm就好了,那写socket服务呢?我们为什么不能像nginx+fpm一样简单配置就好了呢??当然能,必须能。。。。。看这个剧情怕是广告要来了。。。

0x04 出其不意

写socket服务不比写web高级,都是打码,都是完成需求,通信那层都是固定的,只不过一个由nginx完成,另一个由自己完成。。可是现在不需要自己完成了,类似nginx+fpm的方案,fooking+fpm=php长连接,gateway用于承载连接,router用于转发消息,进程间通信?负载均衡?session唯一?so easy..

<ol class="dp-c"><li class="alt"><span><span class="vars">$sid</span><span> = </span><span class="vars">$_SERVER</span><span>[</span><span class="string">'SESSIONID'</span><span>];</span><span class="comment">//这是sessionid</span><span> </span></span></li><li><span> </span></li><li class="alt"><span><span class="vars">$data</span><span> = </span><span class="func">file_get_contents</span><span>(</span><span class="string">"php://input"</span><span>);//这样就能拿到请求内容了 </span></span></li><li><span> </span></li><li class="alt"><span><span class="comment">//想要返回消息只需要两步</span><span> </span></span></li><li><span> </span></li><li class="alt"><span>header(<span class="string">'Content-Length: 11'</span><span>);</span><span class="comment">//返回给客户端字节数</span><span> </span></span></li><li><span> </span></li><li class="alt"><span><span class="func">echo</span><span> </span><span class="string">"hello world"</span><span>; </span></span></li><li><span> </span></li><li class="alt"><span><span class="comment">//想要给别的用户发消息</span><span> </span></span></li><li><span> </span></li><li class="alt"><span><span class="keyword">include</span><span> </span><span class="string">'api.php'</span><span>; </span></span></li><li><span> </span></li><li class="alt"><span><span class="vars">$router</span><span> = </span><span class="keyword">new</span><span> RouterClient(</span><span class="string">'router host'</span><span>, </span><span class="string">'router port'</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span><span class="vars">$router</span><span>->sendMsg(用户sessionid, </span><span class="string">"fuck you"</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span><span class="comment">//想要给所有人要消息</span><span> </span></span></li><li><span> </span></li><li class="alt"><span><span class="vars">$router</span><span>->sendAllMsg(</span><span class="string">"fuck all"</span><span>); </span></span></li><li><span> </span></li><li class="alt"><span><span class="comment">//想给指定组发消息(类似redis的pub/sub)</span><span> </span></span></li><li><span> </span></li><li class="alt"><span><span class="vars">$router</span><span>->publish(</span><span class="string">"channel name"</span><span>, </span><span class="string">"fuck all"</span><span>); </span></span></li></ol>
Copy after login

项目地址: http://git.oschina.net/scgywx/fooking

文档地址(不定期更新):http://my.oschina.net/scgywx/blog/465186 

 



Related labels:
php
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Recommendations
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template