You are definitely familiar with long links, which are reusing a link to continuously interact with data. It is not like those one-night stand-like services that require frequent opening and closing of links. It is inefficient and also increases the number of links. the complexity of the business. Many Internet business scenarios require the support of long connections, such as games, chats, information push, etc. Today we will reveal step by step how to play long connections in PHP. I believe that the implementation of any technology is due to the needs of business scenarios, so this time we also talk about chat rooms.
0x00 First try
I remember that when writing chat rooms in PHP, I still used polling. There is no doubt that when polling is mentioned, some people will definitely say long polling, that’s right! Long polling is also very good, but it is a bit laborious to play this on nginx fpm. After all, one request requires one php process (even if you use apache php_mod, one request requires one thread), so it is okay if a few people just play it. Once there are more people online, it will basically be useless. Therefore, we still use the polling method, which will not block the process, and a request can be responded to immediately. However, the new problem is that we need to keep sending requests to the server, and the longer the interval, the greater the message delay. big.
0x01 Gorgeous transformation
After experiencing the above scene where one second is a small calorie, three seconds is a big calorie! I couldn't stand it anymore, so I decided to transform into a real man. Oh no, it should be a real long-term connection. Fuck polling, fuck long polling, fuck webserver, step aside and let flash socket (or websocket) rule the world! The journey of long-term connection in the true sense began. To play with long connections, it is always necessary to deal with sockets. As the best language in the world (no one), socket encapsulation is naturally indispensable. Just copy socket_*** and start working, so you have the following code, long connection, right? Delayed, right? socket, right? The soup and medicine cost, right? 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>
0x02 Reach the pinnacle
From the day I started playing with sockets, Google told me softly: Don’t use select under high concurrency because it is inefficient. Use iocp for win, epoll for linux, blablablabla... oh! Well, since Google has said so, I can't argue with him. I decided again (why do I say so?) to listen to Google and start epoll, but I can't write it myself? A lazy person like me might as well expand the whole thing, libevent is gone! After crazy coding (py), the masterpiece is finally out. How efficient can it be and how much concurrency can it support? If I don’t build it, it’s useless to choose anyway. I’m a dick!
<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>
0x03 Survival from desperate situation
As the number of people increases and concurrency increases, a single process can no longer meet the demand. Tian Boguang’s story tells us that we can’t beat a group of P’s in single combat. What should we do? As the saying goes, make big things small, make small things small, stop! ! Don't melt it, or it will be gone if you melt it again. Let's split it up and split the single process into multiple processes. But after splitting it, we will face new problems, such as inter-process communication, load balancing, session uniqueness, etc. Since such a question has been raised, there must be a solution. There are ready-made extensions and libraries to solve this problem, such as: swoole, workerman, etc.? In comparison, swoole is more cool, with sex and functionality, eh! It seems that this abbreviation is not very elegant. Well, the performance and functions are better (Brother Tong, please forgive me for being boring~). . . . Wait a moment! ! ! However, when we use PHP to develop the web, we do not use webserver-related libraries for development, right? We just do a simple echo. These complicated things are all handed over to nginx or apache, and they take the lead without hesitation, so that we can concentrate on writing logic. To write web, we only need to simply configure nginx and fpm. What about writing socket service? Why can't we simply configure it like nginx fpm? ? Of course it can, it must be possible. . . . . Watching this plot, I am afraid that an advertisement is coming. . .
0x04 Unexpected
Writing socket services is no more advanced than writing web services. They are both coding and completing requirements. The communication layer is fixed, but one is completed by nginx and the other is completed by yourself. . But now you don’t need to complete it yourself. A solution similar to nginx fpm, fooking fpm=php long connection, gateway is used to carry the connection, router is used to forward messages, inter-process communication? Load balancing? Session only? 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>
Project address: http://git.oschina.net/scgywx/fooking
Document address (updated from time to time): http://my.oschina.net/scgywx/blog/465186