A Preliminary Study of PHP socket-Start with a Simple socket Server

[Original Address:https://blog.ti-node.com/blog …]

The Chinese name of socket is socket, which is the “encapsulation” of TCP/IP. In reality, the network has only four layers, namely, the application layer, the transport layer, the network layer and the data link layer from top to bottom. The most commonly used http protocol belongs to the application layer, while socket can be simply and roughly understood as a kind of thing in the transport layer. If it’s still hard to understand, then tcp://218.221.11.23:9999 is the cruelest place, see? This is a tcp socket.

Socket gives us the ability to control the transport layer and the network layer, thus obtaining stronger performance and higher efficiency. socket programming is the most commonly used and mature solution to solve high concurrent network servers. Any server programmer should master socket programming skills.

In php, there are two sets of functions that can control socket, one is socket_The other is stream_A series of functions. socket_Php directly copied the socket from C language to get the implementation, while stream_Php uses the concept of flow to encapsulate it. The following is a simple article for this series of articles using socket_* system function.

Let’s start with the simplest socket server:

<?php
$host = '0.0.0.0';
$port = 9999;
// 创建一个tcp socket
$listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
// 将socket bind到IP:port上
socket_bind( $listen_socket, $host, $port );
// 开始监听socket
socket_listen( $listen_socket );
// 进入while循环,不用担心死循环死机,因为程序将会阻塞在下面的socket_accept()函数上
while( true ){
  // 此处将会阻塞住,一直到有客户端来连接服务器。阻塞状态的进程是不会占据CPU的
  // 所以你不用担心while循环会将机器拖垮,不会的 
  $connection_socket = socket_accept( $listen_socket );
  // 向客户端发送一个helloworld
  $msg = "helloworld\r\n";
  socket_write( $connection_socket, $msg, strlen( $msg ) );
  socket_close( $connection_socket );
}
socket_close( $listen_socket );

Save the file as server.php and run PHP server.php. We can use telnet on the client, open another terminal to execute telnet 127.0.0.11999 and press enter. The operation results are as follows:

Simply analyze the above code to explain the flow of tcp socket server:

  • 1. First, create a socket according to the protocol family (or address family), socket type, and a specific protocol.
  • 2. Second, bind the socket created in the previous step to an ip:port.
  • 3. third, turn on monitoring linten.
  • 4. Fourth, the server code will enter an infinite loop and not exit. When there is no client connection, the program will block on accept. When there is a connection, it will execute downwards and then loop again to provide persistent service for the client.

In the above case, there are two big defects:

  • 1. Only one client can be served at a time. If there is a second client to connect to while helloworld is being sent to the first client, then the second client must wait for a moment.
  • 2. It is easy to be attacked, resulting in denial of service.

After analyzing the above-mentioned problems, we also think of the multi-process mentioned above. Then we can fork a sub-process to process the request of this client after accpet receives a request, so we can fork a sub-process to process the request of the second client after accepting the second client, so the problem is not solved? OK! To demonstrate the code:

<?php
$host = '0.0.0.0';
$port = 9999;
// 创建一个tcp socket
$listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
// 将socket bind到IP:port上
socket_bind( $listen_socket, $host, $port );
// 开始监听socket
socket_listen( $listen_socket );
// 进入while循环,不用担心死循环死机,因为程序将会阻塞在下面的socket_accept()函数上
while( true ){
  // 此处将会阻塞住,一直到有客户端来连接服务器。阻塞状态的进程是不会占据CPU的
  // 所以你不用担心while循环会将机器拖垮,不会的 
  $connection_socket = socket_accept( $listen_socket );
  // 当accept了新的客户端连接后,就fork出一个子进程专门处理
  $pid = pcntl_fork();
  // 在子进程中处理当前连接的请求业务
  if( 0 == $pid ){
    // 向客户端发送一个helloworld
    $msg = "helloworld\r\n";
    socket_write( $connection_socket, $msg, strlen( $msg ) );
    // 休眠5秒钟,可以用来观察时候可以同时为多个客户端提供服务
    echo time().' : a new client'.PHP_EOL;
    sleep( 5 );
    socket_close( $connection_socket );
    exit;
  }
}
socket_close( $listen_socket );

Save the code as server.php, and then execute PHP server.php. The client still uses telnet 127.0.0.11999, but this time we open two terminals to execute Telnet. Focus on observing that when the first client is connected, the second client can also be connected. The operation results are as follows:

By receiving the timestamp of the client request, we can see that the server can now serve N clients at the same time. But then I thought, what if there were 10,000 clients requesting it? At this time, the server will fork out 10,000 subprocesses to handle each client connection, which will kill people. Fork itself is a system call that wastes system resources. 1 fork is enough to crash the system. even if the current system can withstand 1 fork, then the 1W subprocess produced by fork is enough to drink a pot of system memory. finally, the subprocess produced by fork is shut down after the current client is processed. the next request is to fork again. this is a waste in itself and does not conform to the mainstream values of socialism. If someone attacks maliciously, the number of fork will continue to rise in a straight line until the system crashes.

Therefore, we once again propose an enhanced solution. We can estimate the traffic volume, and then fork out a fixed number of subprocesses when the service starts. Each subprocess is in an infinite loop and blocked on accept. When a client connection comes in, we will process the client request. When the processing is completed, we will only close the connection but not destroy it, but continue to wait for the request of the next client. In this way, it not only avoids the huge waste of resources destroyed by process repeated fork, but also protects the system from collapse due to unlimited fork through a fixed number of subprocesses.

<?php
$host = '0.0.0.0';
$port = 9999;
// 创建一个tcp socket
$listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
// 将socket bind到IP:port上
socket_bind( $listen_socket, $host, $port );
// 开始监听socket
socket_listen( $listen_socket );
// 给主进程换个名字
cli_set_process_title( 'phpserver master process' );
// 按照数量fork出固定个数子进程
for( $i = 1; $i <= 10; $i++ ){
  $pid = pcntl_fork();
  if( 0 == $pid ){
    cli_set_process_title( 'phpserver worker process' );
    while( true ){
      $conn_socket = socket_accept( $listen_socket );
      $msg = "helloworld\r\n";
      socket_write( $conn_socket, $msg, strlen( $msg ) );
      socket_close( $conn_socket );
    }
  }
}
// 主进程不可以退出,代码演示比较粗暴,为了不保证退出直接走while循环,休眠一秒钟
// 实际上,主进程真正该做的应该是收集子进程pid,监控各个子进程的状态等等
while( true ){
  sleep( 1 );
}
socket_close( $connection_socket );

After saving the file as server.php, PHP server.php executes it, and then uses ps -ef | grep phpserver | grep -v grep to look at the server process status:

It can be seen that the master process exists, in addition to which there are 10 subprocesses waiting for service, which can serve 10 clients at the same time. Let’s try telnet 127.0.0.1 9999 and the result is as follows:

Ok, php’s new journey series begins with a simple introduction! The next article will talk about some more profound basic theoretical knowledge.

[Original Address:https://blog.ti-node.com/blog …]