From the source code, the principle of swoole inter-process communication

  1. This document assumes you have basic knowledge of C++ and multiprocessing programming.
  2. Swoole interprocess communication can use sockets (swoole_process::write/ swoole_process::read) or message queues (push/pop). Only the socket communication part is covered in this article.
  3. The swoole source code used in this article is version 1.9

1. What exactly does __construct and start in swoole_process do

In order to illustrate how swoole processes communicate using unix sockets, let's start with the source code and see what the __construct and start functions do. For the source code, we only select the part related to this question for interpretation.

1.1 __construct

swoole_process.c 
static PHP_METHOD(swoole_process, __construct)
{
    /*pipe_type即是__constrct的第三参数$create_pipe, 默认为2*/
    long pipe_type = 2;

    if (pipe_type > 0)
    {
        swPipe *_pipe = emalloc(sizeof(swWorker));
        int socket_type = pipe_type == 1 ? SOCK_STREAM : ,SOCK_DGRAM;
        /*创建pipe, 本质是套接字*/
        if (swPipeUnsock_create(_pipe, 1, socket_type) < 0)
        {
            RETURN_FALSE;
        }

        process->pipe_object = _pipe;
        /*获取主进程用于读写的文件描述符*/
        process->pipe_master = _pipe->getFd(_pipe, SW_PIPE_MASTER);
        /*获取工作进程用于读写的文件描述符*/
        process->pipe_worker = _pipe->getFd(_pipe, SW_PIPE_WORKER); 
        process->pipe = process->pipe_master;
        /*对当前swoole_process对象(php中new出的)的属性值pipe进行赋值*/
        zend_update_property_long(swoole_process_class_entry_ptr, getThis(), ZEND_STRL("pipe"), process->pipe_master TSRMLS_CC)
}

What does swPipeUnsock_create do?

pipe/PipeUnsock.c 
int swPipeUnsock_create(swPipe *p, int blocking, int protocol)
{

    p->blocking = blocking;
    /*创建本地套接字*/
    ret = socketpair(AF_UNIX, protocol, 0, object->socks);
    if (ret < 0)
    {
        swWarn("socketpair() failed. Error: %s [%d]", strerror(errno), errno);
        return SW_ERR;
    }
    else
    {
        /*
         * swoole_config.h:#define SW_SOCKET_BUFFER_SIZE      (8*1024*1024)
         * 由此可知套接字buffer大小为8M
         */
        int sbsize = SwooleG.socket_buffer_size;
        swSocket_set_buffer_size(object->socks[0], sbsize);
        swSocket_set_buffer_size(object->socks[1], sbsize);
    }

    return 0;
}

1.2 start

swoole_process.c
static PHP_METHOD(swoole_process, start)
{
    swWorker *process = swoole_get_object(getThis());

    /*
     * 创建进程
     * 注意:
     * 1. 创建后父子进程中各有一个swoole_process对象,它们是双胞胎。
     * 2. 子进程继承父进程打开的文件描述符,所以之前__construct时创建的套接子,子进程中也有一份
     */

    pid_t pid = fork();
    if (pid < 0)
    {
        swoole_php_fatal_error(E_WARNING, "fork() failed. Error: %s[%d]", strerror(errno), errno);
        RETURN_FALSE;
    }
    /*主进程逻辑*/
    else if (pid > 0)
    {
        process->pid = pid;
        process->child_process = 0;
        zend_update_property_long(swoole_server_class_entry_ptr, getThis(), ZEND_STRL("pid"), process->pid TSRMLS_CC);
        RETURN_LONG(pid);
    }
    /*子进程逻辑*/
    else
    {
        process->child_process = 1;
        SW_CHECK_RETURN(php_swoole_process_start(process, getThis() TSRMLS_CC));
    }
    RETURN_TRUE;
}

The subprocess logic is executed in php_swoole_process_start(), we continue to see

swoole_process.c
int php_swoole_process_start(swWorker *process, zval *object TSRMLS_DC)
{
    /*
     * 设置子进程中的swoole_process用于通信的描述符
     * 对比前文,主进程中的swoole_process使用的是process->pipe_master
     */
    process->pipe = process->pipe_worker;
    process->pid = getpid();

    /*更新子进程中swoole_process(php对象)的相应属性*/
    zend_update_property_long(swoole_process_class_entry_ptr, object, ZEND_STRL("pid"), process->pid TSRMLS_CC);
    zend_update_property_long(swoole_process_class_entry_ptr, object, ZEND_STRL("pipe"), process->pipe_worker TSRMLS_CC);
}

It can be seen that 
the main job of __construct is to use socketpair to create a pair of sockets and specify the sockets used by the swoole_process object in the main process for reading and writing. 
The main job of start is to create a child process and set the socket used for reading and writing by the swoole_process object in the child process.

2. Communication principle

2.1 Interpretation of read/write source code

swoole_process.c
static PHP_METHOD(swoole_process, read)
{
    /*默认每次读写大小*/
    long buf_size = 8192;

    /*上限值*/
    if (buf_size > 65536)
    {
        buf_size = 65536;
    }

    swWorker *process = swoole_get_object(getThis());

    char *buf = emalloc(buf_size + 1);
    /*从进程中保留的套接字中读取数据*/
    int ret = read(process->pipe, buf, buf_size);;

    /*设置php函数swoole_process->read返回值*/
    SW_ZVAL_STRINGL(return_value, buf, ret, 0);
}
swoole_process.c
static PHP_METHOD(swoole_process, write)
{
    int ret;

    /*以下两种情况的本质都是调用write函数向process-pipe中写入数据*/
    //async write
    if (SwooleG.main_reactor)
    {
        ret = SwooleG.main_reactor->write(SwooleG.main_reactor, process->pipe, data, (size_t) data_len);
    }
    else
    {
        ret = swSocket_write_blocking(process->pipe, data, data_len);
    }

    ZVAL_LONG(return_value, ret);
}

2.2 Summary of Communication Principles

The principle of using socket communication between swoole processes is as follows: 
1. The parent process uses socketpair to create a pair of sockets 
2. When creating a child process, the child process inherits the pair of sockets 
3. The parent and child processes use the system's read, write The function reads and writes to the respective sockets to complete the communication. 
4. For multiple child processes, the parent process actually creates a pair of sockets for each child process for communication. 
5. The communication between child processes, such as A sends a message to B, is essentially fork A process, A inherits the socket for sending messages to B from the parent process, thus completing the communication to B.

3. About SOCK_STREAM and SOCK_DGRAM

3.1 A bug in the manual

The manual says: The default way is streaming. But from the __construct source code in Section 1.1, we can see that the SOCK_DGRAM method is used by default

3.2 Difference between SOCK_STREAM and SOCK_DGRAM

This parameter is passed in through the third parameter of __construct, and finally acts on the protocol field of socketpair

ret = socketpair(AF_UNIX, protocol, 0, object->socks);

In the usual sense, SOCK_STREAM and SOCK_DGRAM are used for tcp communication and udp communication respectively. The former is orderly (first come, first come) and reliable; the latter does not guarantee order and data reliability. However, in the local socket, since it is a local two-process communication, there will be no problems such as data loss and out-of-order. So what is the difference between these two parameters? 
Here is a very clear and straightforward explanation that I see:

The difference between SOCK_STREAM and SOCK_DGRAM is in the semantics of consuming data out of the socket.

Stream socket allows for reading arbitrary number of bytes, but still preserving byte sequence. In other words, a sender might write 4K of data to the socket, and the receiver can consume that data byte by byte. The other way around is true too - sender can write several small messages to the socket that the receiver can consume in one read. Stream socket does not preserve message boundaries.

Datagram socket, on the other hand, does preserve these boundaries - one write by the sender always corresponds to one read by the receiver (even if receiver’s buffer given to read(2) or recv(2) is smaller then that message).

That is to say, SOCK_STREAM is streaming, and the data has no message boundary. The data written by the sender multiple times may be read by the reader at one time. The data written once is sent, and the reader may read it several times. Going back to swoole means that in this way, the number of write and read is not one-to-one. You need to set your own boundaries to segment messages.

In SOCK_DGRAM mode, data is naturally bounded, and the number of reads and writes must be in one-to-one correspondence. Going back to swoole means that in this way, as long as your single message does not exceed the upper limit of single read and write (default 8192 bytes), you do not need to set boundaries to segment messages.

see an example

<?php                                                                                 
$process1 = new swoole_process(function($process){                                    
    $i = 1;                                                                           
    while (true) {                                                                    
        $msg = $process->read();                                                      
        echo $msg,"\n";                                                               
        echo "read $i time\n";                                                        
        $i++;                                                                         
    }                                                                                 
}, false, 1);                                                                         

$num = 10;                                                                            

$process2 = new swoole_process(function($process) use ($process1, $num){              
    for ($i=0; $i<$num; $i++){                                                        
        $msg = $process1->write("hello 1 i'm 2;");                                    
        if ($i % 5 == 0){                                                             
            sleep(1);                                                                 
        }                                                                             
    }                                                                                 
});

$process2->start();                                                                   
$process1->start();  

The third parameter of new is set to 1, and SOCK_STREAM communication is used. The results are as follows:

hello 1 i'm 2;
read 1 time
hello 1 i'm 2;hello 1 i'm 2;hello 1 i'm 2;
read 2 time
hello 1 i'm 2;hello 1 i'm 2;
read 3 time
hello 1 i'm 2;hello 1 i'm 2;
read 4 time
hello 1 i'm 2;hello 1 i'm 2;
read 5 time

process2 writes data to process1 10 times, process1 uses 5 reads 
to set the third parameter of new to 2, and uses SOCK_DGRAM to communicate. The results are as follows:

hello 1 i'm 2;
read 1 time
hello 1 i'm 2;
read 2 time
hello 1 i'm 2;
read 3 time
hello 1 i'm 2;
read 4 time
hello 1 i'm 2;
read 5 time
hello 1 i'm 2;
read 6 time
hello 1 i'm 2;
read 7 time
hello 1 i'm 2;
read 8 time
hello 1 i'm 2;
read 9 time
hello 1 i'm 2;
read 10 time

10 writes correspond to 10 reads

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324399441&siteId=291194637