Serial Socket depth probe four PHP (i)

Source: Internet
Author: User
Tags epoll php source code
Serial Socket depth Probe 4 PHP (i)

Serial Socket depth Probe 4 PHP (i)
May 05, 2011
Socket (socket) has always been the core content of the network layer, and is also the implementation channel of TCP/IP and UDP underlying protocol. With the explosive development of the Internet Information Age, the performance problem of the contemporary server is facing more and more challenges, and the famous c10k problem (http://www.kegel.com/c10k.html) also comes into being. Fortunately, through the unremitting efforts of the Daniel, different from the traditional select/poll of the Epoll/kqueue Way appeared, at present linux2.6 above the core are generally supported, this is the Socket field a huge progress, not only solve the c10k problem, has gradually become the core technology of the modern Internet. Libevent Library is one of the more brilliant projects (now a lot of open source projects are useful, including Memcached), interested friends can study.
Because the network on the system introduced this part of the article is not much, and the less involved in PHP, so the stone in this hope through the "socket depth Inquiry 4PHP" this series to interested in this field of readers will certainly help, but also hope that we can work with me on this issue more in-depth discussion. First of all, to explain the current Socket domain is more easily confused concepts are: blocking/non-blocking, synchronous/asynchronous, multiplexing and so on.
1, blocking/non-blocking: These two concepts are for the status of the process in the IO process, blocking IO means that the current thread will be suspended until the call results are returned, whereas non-blocking means that the function does not block the current thread until the result is immediately available and returns immediately.
2, synchronous/asynchronous: These two concepts are for the invocation if the return result, the so-called synchronization, is the issue of a function call, the call does not return until the result is not obtained, on the contrary, when an asynchronous procedure call is issued, the caller cannot immediately get the result, the actual processing of the part of the call after completion, Notifies the caller by status, notification, and callback.
3, Multiplexing (io/multiplexing): In order to improve the efficiency of the transmission of data information in the network communication line, the establishment of a number of logical communication channels on a physical communication line, while the transmission of a number of signal technology is called multiplexing technology. For sockets, it should be said that the model that can handle multiple connections at the same time should be called multiplexing, which is now more commonly used with Select/poll/epoll/kqueue these IO models (there are also IO models like Apache that each connect with a separate process/thread to handle , but the efficiency is relatively poor, but also very prone to problems, so temporarily do not introduce. In these multiplexed modes, the scalability and performance of asynchronous blocking/nonblocking mode is best.
The concept of feeling is very abstract right, "all the answer lies in the scene", let us from three kinds of classic PHP Socket IO model instance to do the above concept again analysis:
1. The old model using the Accept block: it belongs to the synchronous blocking IO model, the code is as follows:
socket_server.php
**/set_time_limit (0); Class Socketserver {private static $socket; function Socketserver ($port) {global $errno, $errstr; if ($port socket = str Eam_socket_server ("tcp://0.0.0.0:{$port}", $errno, $ERRSTR); if (! $socket) Die ("$errstr ($errno)"); Stream_set_timeout ($socket,-1); Ensure that the server socket does not time out, it seems useless:) while ($conn = Stream_socket_accept ($socket,-1)) {//This setting does not time out only oil with static $id = 0; Static $CT = 0; $ct _last = $ct; $ct _data = "; $buffer = "; $id + +; Increase on each accept echo "Client $id come.\n"; while (!preg_match ('/\r?\n/', $buffer)) {//Do not read to Terminator, continue to read//if (feof ($conn)) break;//Prevent Popen and fread bugs from causing a dead loop $buff ER = fread ($conn, 1024); Echo ' R '; Number of print reads $ct + = strlen ($buffer); $ct _data. = Preg_replace ('/\r?\n/', ' ', $buffer); } $ct _size = ($ct-$ct _last) * 8; echo "[$id]". __method__. ">". $ct _data. "\ n"; Fwrite ($conn, "Received $ct _size byte data.\r\n"); Fclose ($conn); } fclose ($socket); }} new Socketserver (2000);
socket_client.php
Socket Test Client * by James.huang **/function Debug ($msg) {//Echo $msg; Error_log ($msg, 3, '/tmp/socket.log ');} if ($argv [1]) {$socket _client = stream_socket_client (' tcp://0.0.0.0:2000 ', $errno, $errstr, +);//Stream_set_blocking ($socket _ Client, 0); Stream_set_timeout ($socket _client, 0, 100000); if (! $socket _client) {die ("$errstr ($errno)"),} else {$msg = Trim ($argv [1]); for ($i = 0; $i wait} fwrite ($socket _client , "\ r \ n"); Transmission Terminator Debug (fread ($socket _client, 1024)); Fclose ($socket _client); }} else {//$PHARR = Array ();//for ($i = 0; $i send data, last send Terminator; the server socket_server.php receives the socket connection using the Accept blocking method, and then loops over the data. Until the Terminator is received, the result data (the number of bytes received) is returned. Although the logic is simple, there are several things that are worth analyzing:
A> by default, the PHP socket_client.php test is run, the client hits 10 W, the server outputs a number of R followed by the received data, and/tmp/socket.log records the received data returned by the server. The situation is easy to understand and not to repeat. Then, using the Telnet command to open multiple clients at once, you will find that the server only handles one client at a time, others need to "queue" later, and this is the feature of blocking IO, the weakness of this model is very obvious and inefficient.
B> only open the comment code of the 26th line of socket_client.php, run the PHP socket_client.php test client again to hit a W, the server also played a R, then two programs are stuck. This is why, after parsing the logic you will find that this is because the client is going to return data to the server before the Terminator is sent, and the server is going to end up with a terminator to the client because it does not receive the Terminator, causing a deadlock. Only one W and R are typed because Fread is blocked by default. To resolve this deadlock, you must open the comment code on line 16th of the socket_client.php, set a 0.1-second timeout for the socket, run again and you will see that a W and R appear normally after 0.1 seconds, and the received data returned by the server is normally logged. Visible fread Default is blocked, we have to pay special attention when programming, if not set timeout, it is easy to deadlock.
c> only open 15 lines of comments, run PHP socket_client.php test, the results are basically the same as the case A, the only difference is that/tmp/socket.log does not record the return data. Here you can see the difference between the client running in blocking and nonblocking mode, and of course the non-blocking mode can be used to get maximum efficiency when the client does not care about accepting the result.
D> running PHP socket_client.php is running 10 times the above logic, this is not a problem, but it is very strange that if you use 35-41 lines of code, with popen simultaneously open 10 processes to run, it will cause the server side of the dead loop, very strange! Later, the investigation found that as long as the connection created by the process opened with Popen will cause fread or socket_read error directly return empty string, resulting in a dead loop, after reviewing the PHP source code found that PHP popen and fread function is completely not C-native, A large number of php_stream_* implementation logic is inserted, the initial estimate is that one of the bugs caused by the Socket connection interruption caused by the solution is to open the socket_server.php 31 lines of code, if the connection is interrupted to jump out of the loop, However, there will be a lot of data loss, this problem needs special attention!
2, using Select/poll synchronization model: belongs to the synchronous non-blocking IO model, the code is as follows:
select_server.php
   **/set_time_limit (0); Class Selectsocketserver {private static $socket; private static $timeout =; private static $maxconns = 1024x768; private Static $connections = Array (); function Selectsocketserver ($port) {global $errno, $errstr; if ($port socket = Socket_create_listen ($port); if (! $socket) Die ("Listen $port failed"); Socket_set_nonblock ($socket); Non-blocking while (true) {$readfds = Array_merge (self:: $connections, Array ($socket)); $writefds = Array ();//Select a connection to get a read, write connection Channel if (Socket_select ($readfds, $writefds, $e = null, $t = self:: $timeout)) {//If it is the current server-side listener connection if (In_array ($socket, $readfds ) {//Accept client Connection $newconn = Socket_accept ($socket), $i = (int) $newconn; $reject = '; if (self:: $connections) >= Self:: $maxconns) {$reject = ' Server full, Try again later.\n ';}//Put the current client connection into Socket_select Select self:: $connections [$i] = $newconn; Enter the connection resource cache container $writefds [$i] = $newconn; The connection is not normal if ($reject) {socket_write ($writefds [$i], $reject); unset ($writefds [$i]); Self::close ($i);} else {echo "Client $i come.\n";}//Remove the listening socket from the clients-with-data array $key = Array_search ($ socket, $readfds); Unset ($readfds [$key]); }//Round-robin channel foreach ($readfds as $RFD) {//Client connection $i = (int) $rfd;//read from channel $line = @socket_read ($RFD, 2048, Php_normal_rea D); if ($line = = = False) {//Do not read content, end connection echo "Connection closed on socket $i. \ n"; Self::close ($i); continue;} $tmp = substr ($line,-1); if ($tmp! = "\ r" && $tmp! = "\ n") {//waits for more data continue;}//processing logic $line = Trim ($line); if ($line = = "Quit") {echo "client $i quit.\n"; Self::close ($i); break;} if ($line) {echo "Client $i >>". $line . "\ n"; }}//Round write channel foreach ($writefds as $wfd) {$i = (int) $wfd; $w = Socket_write ($WFD, "Welcome Client $i!\n");}} }} function Close ($i) {Socket_shutdown (self:: $connections [$i]), Socket_close (self:: $connections [$i]); Unset (self::$ connections[$i]); }} new Selectsocketserver (2000);
select_client.php
**/function Debug ($msg) {//Echo $msg; Error_log ($msg, 3, '/tmp/socket.log ');} if ($argv [1]) {$socket _client = stream _socket_client (' tcp://0.0.0.0:2000 ', $errno, $errstr, 30); Stream_set_timeout ($socket _client, 0, 100000); if (! $socket _client) {die ("$errstr ($errno)"),} else {$msg = Trim ($argv [1]); for ($i = 0; $i wait} fwrite ($socket _client , "quit\n"); Add End Token Debug (fread ($socket _client, 1024)); Fclose ($socket _client); }} else {$phArr = array (); for ($i = 0; $i here if we execute PHP select_client.php the program will open 10 connections at the same time, while performing a simulated login user operation; Observing the data printed on the server, you will find the server It is true that these connections are handled at the same time, which is the non-blocking IO model implemented by multiplexing, and of course this model is not really implemented asynchronously, because the end-of-service program is going to read the data inside the channel and get the results back to the client synchronously. If you also use the Telnet command to open multiple clients at the same time, you will find that the server can handle these connections at the same time, which is non-blocking IO, of course, more efficient than the old blocking IO, but this mode is still limited, continue to see you will find ~
B> I set a few parameters in the select_server.php, you can adjust to try:
$timeout: Indicates the time-out for select, which is generally not too short, or causes the CPU to load too high.
$maxconns: Indicates the maximum number of connections, the server will refuse to receive if the client exceeds this number. One of the things to mention here is that because select is read and written through a handle, it is subject to the default __fd_setsize of the system, the default value is 1024, the kernel needs to be recompiled, and the test finds the Select The performance of the pattern will be linearly poor as the number of connections increases (see "Socket depth probe 4PHP (ii)" for details), which is the biggest problem with select mode, so if the next mode is recommended for ultra-high concurrent servers.
3. Asynchronous model using Epoll/kqueue: An asynchronous blocking/nonblocking IO model with the following code:
epoll_server.php
   * * Defined Constants: * * Ev_timeout (integer) * ev_read (integer) * ev_write (integer) * ev_signal (integer) * ev_per SIST (Integer) * evloop_nonblock (integer) * evloop_once (integer) **/set_time_limit (0); Class Epollsocketserver {private static $socket; private static $connections; private static $buffers; function Epollsock Etserver ($port) {Global $errno, $errstr, if (!extension_loaded (' libevent ')) {die ("Install libevent extension fir Stly\n "); if ($port socket, $flag, $base) {static $id = 0; $connection = stream_socket_accept ($socket); Stream_set_blocking ($conn ection, 0); $id + +; Increase on each accept $buffer = Event_buffer_new ($connection, Array (__class__, ' Ev_read '), Array (__class__, ' Ev_writ E '), Array (__class__, ' ev_error '), $id); Event_buffer_base_set ($buffer, $base); Event_buffer_timeout_set ($buffer, 30, 30); Event_buffer_watermark_set ($buffer, Ev_read, 0, 0XFFFFFF); Event_buffer_priority_set ($buffer, 10); Event_buffer_enable ($buffer, Ev_read | Ev_persist); We need to save both buffer and connection outside self:: $connections [$id] = $connection; Self:: $buffers [$id] = $buffer; } function Ev_error ($buffer, $error, $id) {event_buffer_disable (self:: $buffers [$id], Ev_read | Ev_write); Event_buffer_free (self:: $buffers [$id]); Fclose (self:: $connections [$id]); Unset (self:: $buffers [$id], Self:: $connections [$id]); The function Ev_read ($buffer, $id) {static $ct = 0; $ct _last = $ct; $ct _data = "; while ($read = Event_buffer_read ($buffer , 1024x768) {$ct + = strlen ($read); $ct _data. = $read;} $ct _size = ($ct-$ct _last) * 8; echo "[$id]". __method__. ">". $ct _data. "\ n"; Event_buffer_write ($buffer, "Received $ct _size byte data.\r\n"); } function Ev_write ($buffer, $id) {echo "[$id]". __method__. "\ n"; }} new Epollsocketserver (2000);
epoll_client.php
**/function Debug ($msg) {//Echo $msg; Error_log ($msg, 3, '/tmp/socket.log ');} if ($argv [1]) {$socket _client = stream _socket_client (' tcp://0.0.0.0:2000 ', $errno, $errstr, 30); Stream_set_blocking ($socket _client, 0); if (! $socket _client) {die ("$errstr ($errno)"),} else {$msg = Trim ($argv [1]), for ($i = 0; $i Returns the result (number of bytes received). However, when you run PHP epoll_client.php you will find that the server print out the results and the accept blocking model is very different, of course, the efficiency of the operation is also greatly improved, this is why? The next step is to introduce the Epoll/kqueue model: When we introduce the Select mode we mention the limitations of this model, and epoll is to solve these two shortcomings of poll. First of all, there is no limit to the Epoll mode (refer to Cat/proc/sys/fs/file-max by default to reach 300K, it is very exciting, actually this is the so-called Epoll based Erlang server can handle so many concurrent connections at the root cause, but now PHP can also be done in theory, hehe); In addition, the performance of the Epoll mode will not become worse as the number of connections increases as the Select mode does, and the test finds that the performance is still very stable (the next chapter will be described in detail).
Epoll work has two modes of LT (level triggered) and ET (edge-triggered), the former is the default mode, while supporting blocking and non-blocking IO mode, although performance than the latter almost, but relatively stable, in general, in practical use, We all use this pattern (ET and WinSock are all pure asynchronous non-blocking models). And another point to say is that Libevent is in the compilation phase to select the system's I/O demultiplex mechanism, does not support in the run phase according to the configuration to choose again, so we will not discuss the details of the implementation of libevent here, if friends are interested in further understanding, Please refer to: http://monkey.org/~provos/libevent/.
Here, the first part of the end of the content, I believe you have already understood the socket programming several key concepts and some practical skills, the next "socket depth Inquiry 4PHP (ii)" I will Select/poll/epoll/kqueue Several modes are introduced and compared in depth, and there are also two important I/O multiplexing modes involved: Reactor and Proactor modes.
To be Continued ...
  • Related Article

    Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.