前言

管道是进程间通信 IPC 的最基础的方式,管道有两种类型:命名管道和匿名管道,匿名管道专门用于具有血缘关系的进程之间,完成数据传递,命名管道可以用于任何两个进程之间。swoole 中的管道都是匿名管道。

在 swoole 中,有三种不同类型的管道,其中 swPipeBase 是最基础的管道,swPipeUnsock 是利用 socketpair 实现的管道,swPipeEventfd 是 eventfd 实现的管道。swoole 并没有使用 FIFO 命名管道。

Pipe 数据结构

不管哪种类型的管道,其基础都是 swPipe,该结构体包含一个具体的 pipe 类 object,代表着是否阻塞的 blocking,超时时间 timeout,还有对管道的操作函数read、write、getfd、close


typedef struct _swPipe
{
void *object;
int blocking;
double timeout; int (*read)(struct _swPipe *, void *recv, int length);
int (*write)(struct _swPipe *, void *send, int length);
int (*getFd)(struct _swPipe *, int master);
int (*close)(struct _swPipe *);
} swPipe;

swPipeBase 匿名管道

swPipeBase 数据结构

数据结构非常简单,就是一个数组,存放着 pipe 的读端和写端。值得注意的是,swPipeBase 是半全工的管道,也就是说 pipes[0] 只能用于读,pipes[1] 只能用于写。

当多个进程共享这个管道的时候,所有的进程读取都需要 read 读端 pipes[0],进程写入消息都要 write 写端 pipes[1]。

因此使用这个匿名管道的时候,一般情形是一个进程只负责写,另一个进程只负责读,只能单向传递消息,不能双向传递,否则很有可能读到了自己刚刚发送的消息。


typedef struct _swPipeBase
{
int pipes[2];
} swPipeBase;

swPipeBase 的创建

创建匿名管道就是调用 pipe 函数,程序自动设置管道为非阻塞式。


int swPipeBase_create(swPipe *p, int blocking)
{
int ret;
swPipeBase *object = sw_malloc(sizeof(swPipeBase));
if (object == NULL)
{
return -1;
}
p->blocking = blocking;
ret = pipe(object->pipes);
if (ret < 0)
{
swWarn("pipe() failed. Error: %s[%d]", strerror(errno), errno);
sw_free(object);
return -1;
}
else
{
//Nonblock
swSetNonBlock(object->pipes[0]);
swSetNonBlock(object->pipes[1]);
p->timeout = -1;
p->object = object;
p->read = swPipeBase_read;
p->write = swPipeBase_write;
p->getFd = swPipeBase_getFd;
p->close = swPipeBase_close;
}
return 0;
}

swPipeBase_read 管道的读

由于匿名管道被设置为非阻塞式,无法实现超时等待写入。如果想要阻塞式的向管道写入数据,设置一定超时时间,就需要利用 poll 函数。当 pipefd 可读时,poll 立刻返回,或者达到超时时间。


static int swPipeBase_read(swPipe *p, void *data, int length)
{
swPipeBase *object = p->object;
if (p->blocking == 1 && p->timeout > 0)
{
if (swSocket_wait(object->pipes[0], p->timeout * 1000, SW_EVENT_READ) < 0)
{
return SW_ERR;
}
}
return read(object->pipes[0], data, length);
} int swSocket_wait(int fd, int timeout_ms, int events)
{
struct pollfd event;
event.fd = fd;
event.events = 0; if (events & SW_EVENT_READ)
{
event.events |= POLLIN;
}
if (events & SW_EVENT_WRITE)
{
event.events |= POLLOUT;
}
while (1)
{
int ret = poll(&event, 1, timeout_ms);
if (ret == 0)
{
return SW_ERR;
}
else if (ret < 0 && errno != EINTR)
{
swWarn("poll() failed. Error: %s[%d]", strerror(errno), errno);
return SW_ERR;
}
else
{
return SW_OK;
}
}
return SW_OK;
}

swPipeBase_write 管道的写入

管道的写入直接调用 write 即可,非阻塞式 IO 会立刻返回结果。


static int swPipeBase_write(swPipe *p, void *data, int length)
{
swPipeBase *this = p->object;
return write(this->pipes[1], data, length);
}

swPipeBase_getFd

本函数用于获取管道的读端或者写端。


static int swPipeBase_getFd(swPipe *p, int isWriteFd)
{
swPipeBase *this = p->object;
return (isWriteFd == 0) ? this->pipes[0] : this->pipes[1];
}

swPipeBase_close 关闭管道


static int swPipeBase_close(swPipe *p)
{
int ret1, ret2;
swPipeBase *this = p->object;
ret1 = close(this->pipes[0]);
ret2 = close(this->pipes[1]);
sw_free(this);
return 0 - ret1 - ret2;
}

swPipeEventfd 管道

swPipeEventfd 数据结构

数据结构中仅仅存放 eventfd 函数返回的文件描述符。

和 pipe 管道不同的是,eventfd 只有一个文件描述符,读和写都是对这个文件描述符进行操作。

该管道同样也是只适用于进程间单向通信。


typedef struct _swPipeEventfd
{
int event_fd;
} swPipeEventfd;

swPipeEventfd_read 管道的读取

类似于匿名管道,eventfd 也不支持超时等待,因此还是利用 poll 函数进行超时等待。

由于 eventfd 可能是阻塞式,因此 read 时可能会被信号打断。


static int swPipeEventfd_read(swPipe *p, void *data, int length)
{
int ret = -1;
swPipeEventfd *object = p->object; //eventfd not support socket timeout
if (p->blocking == 1 && p->timeout > 0)
{
if (swSocket_wait(object->event_fd, p->timeout * 1000, SW_EVENT_READ) < 0)
{
return SW_ERR;
}
} while (1)
{
ret = read(object->event_fd, data, sizeof(uint64_t));
if (ret < 0 && errno == EINTR)
{
continue;
}
break;
}
return ret;
}

swPipeEventfd_write 管道的写入

写入和读取的过程类似,注意被信号打断后继续循环即可。


static int swPipeEventfd_write(swPipe *p, void *data, int length)
{
int ret;
swPipeEventfd *this = p->object;
while (1)
{
ret = write(this->event_fd, data, sizeof(uint64_t));
if (ret < 0)
{
if (errno == EINTR)
{
continue;
}
}
break;
}
return ret;
}

swPipeEventfd_getFd


static int swPipeEventfd_getFd(swPipe *p, int isWriteFd)
{
return ((swPipeEventfd *) (p->object))->event_fd;
}

swPipeEventfd_close 关闭管道


static int swPipeEventfd_close(swPipe *p)
{
int ret;
ret = close(((swPipeEventfd *) (p->object))->event_fd);
sw_free(p->object);
return ret;
}

swPipeUnsock 管道

swPipeUnsock 数据结构

不同于 pipe 的匿名管道,swPipeUnsock 管道是双向通信的管道。

因此两个进程利用 swPipeUnsock 管道进行通信的时候,独占一个 sock,也就是说 A 进程读写都是用 socks[0],B 进程读写都是用 socks[1],socks[0] 写入的消息会在 socks[1] 读出来,反之,socks[0] 读出的消息是 sock[1] 写入的,这样就实现了两个进程的双向通信。


typedef struct _swPipeUnsock
{
/**
* master : socks[1]
* worker : socks[0]
*/
int socks[2];
/**
* master pipe is closed
*/
uint8_t pipe_master_closed;
/**
* worker pipe is closed
*/
uint8_t pipe_worker_closed;
} swPipeUnsock;

swPipeUnsock 的创建

swPipeUnsock 的创建主要是调用 socketpair 函数,protocol 决定了创建的 socket 是 SOCK_DGRAM 类型还是 SOCK_STREAM 类型。


int swPipeUnsock_create(swPipe *p, int blocking, int protocol)
{
int ret;
swPipeUnsock *object = sw_malloc(sizeof(swPipeUnsock));
if (object == NULL)
{
swWarn("malloc() failed.");
return SW_ERR;
}
bzero(object, sizeof(swPipeUnsock));
p->blocking = blocking;
ret = socketpair(AF_UNIX, protocol, 0, object->socks);
if (ret < 0)
{
swWarn("socketpair() failed. Error: %s [%d]", strerror(errno), errno);
sw_free(object);
return SW_ERR;
}
else
{
//Nonblock
if (blocking == 0)
{
swSetNonBlock(object->socks[0]);
swSetNonBlock(object->socks[1]);
} int sbsize = SwooleG.socket_buffer_size;
swSocket_set_buffer_size(object->socks[0], sbsize);
swSocket_set_buffer_size(object->socks[1], sbsize); p->object = object;
p->read = swPipeUnsock_read;
p->write = swPipeUnsock_write;
p->getFd = swPipeUnsock_getFd;
p->close = swPipeUnsock_close;
}
return 0;
} int swSocket_set_buffer_size(int fd, int buffer_size)
{
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size)) < 0)
{
swSysError("setsockopt(%d, SOL_SOCKET, SO_SNDBUF, %d) failed.", fd, buffer_size);
return SW_ERR;
}
if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &buffer_size, sizeof(buffer_size)) < 0)
{
swSysError("setsockopt(%d, SOL_SOCKET, SO_RCVBUF, %d) failed.", fd, buffer_size);
return SW_ERR;
}
return SW_OK;
}

swPipeUnsock_getFd 函数

同样的获取管道文件描述符根据 master 来决定。


static int swPipeUnsock_getFd(swPipe *p, int master)
{
swPipeUnsock *this = p->object;
return master == 1 ? this->socks[1] : this->socks[0];
}

swPipeUnsock_close 关闭管道

关闭管道就是调用 close 来依次关闭两个 socket.


static int swPipeUnsock_close(swPipe *p)
{
swPipeUnsock *object = p->object;
int ret = swPipeUnsock_close_ext(p, 0);
sw_free(object);
return ret;
} int swPipeUnsock_close_ext(swPipe *p, int which)
{
int ret1 = 0, ret2 = 0;
swPipeUnsock *object = p->object; if (which == SW_PIPE_CLOSE_MASTER)
{
if (object->pipe_master_closed)
{
return SW_ERR;
}
ret1 = close(object->socks[1]);
object->pipe_master_closed = 1;
}
else if (which == SW_PIPE_CLOSE_WORKER)
{
if (object->pipe_worker_closed)
{
return SW_ERR;
}
ret1 = close(object->socks[0]);
object->pipe_worker_closed = 1;
}
else
{
ret1 = swPipeUnsock_close_ext(p, SW_PIPE_CLOSE_MASTER);
ret2 = swPipeUnsock_close_ext(p, SW_PIPE_CLOSE_WORKER);
} return 0 - ret1 - ret2;
}

管道的应用

tasker 模块

当调用 taskwait 函数后,投递的 worker 进程会阻塞在 serv->task_notify[SwooleWG.id] 管道的读取中,tasker 模块处理完毕后,会向 serv->task_notify[source_worker_id] 管道写入数据。

这个就是 pipe 函数或者 eventfd 创建的匿名管道的用途,用于单向的进程通信(tasker 进程向 worker 进程传递数据)。


static inline int swPipeNotify_auto(swPipe *p, int blocking, int semaphore)
{
#ifdef HAVE_EVENTFD
return swPipeEventfd_create(p, blocking, semaphore, 0);
#else
return swPipeBase_create(p, blocking);
#endif
}

worker 模块

manager 负责为 worker 进程创建 pipe_master 与 pipe_worker。用于 reactor 线程与 worker 进程直接进行通信。


int swManager_start(swFactory *factory)
{
... for (i = 0; i < serv->worker_num; i++)
{
if (swPipeUnsock_create(&object->pipes[i], 1, SOCK_DGRAM) < 0)
{
return SW_ERR;
}
serv->workers[i].pipe_master = object->pipes[i].getFd(&object->pipes[i], SW_PIPE_MASTER);
serv->workers[i].pipe_worker = object->pipes[i].getFd(&object->pipes[i], SW_PIPE_WORKER);
serv->workers[i].pipe_object = &object->pipes[i];
swServer_store_pipe_fd(serv, serv->workers[i].pipe_object);
} ... }

当 reactor 线程启动的时候,会将 pipe_master 加入 reactor 的监控当中。


static int swReactorThread_loop(swThreadParam *param)
{ ... for (i = 0; i < serv->worker_num; i++)
{
if (i % serv->reactor_num == reactor_id)
{
pipe_fd = serv->workers[i].pipe_master; swSetNonBlock(pipe_fd);
reactor->add(reactor, pipe_fd, SW_FD_PIPE); if (thread->notify_pipe == 0)
{
thread->notify_pipe = serv->workers[i].pipe_worker;
} } }
...
}

在 worker 进程中,会将 pipe_worker 作为另一端 socket 放入 worker 的 reactor 事件循环中进行监控。


int swWorker_loop(swFactory *factory, int worker_id)
{
... int pipe_worker = worker->pipe_worker; swSetNonBlock(pipe_worker);
SwooleG.main_reactor->ptr = serv;
SwooleG.main_reactor->add(SwooleG.main_reactor, pipe_worker, SW_FD_PIPE | SW_EVENT_READ);
SwooleG.main_reactor->setHandle(SwooleG.main_reactor, SW_FD_PIPE, swWorker_onPipeReceive);
SwooleG.main_reactor->setHandle(SwooleG.main_reactor, SW_FD_WRITE, swReactor_onWrite); ... }

tasker 进程

tasker 进程中管道的创建是 swProcessPool_create 函数完成的。


int swProcessPool_create(swProcessPool *pool, int worker_num, int max_request, key_t msgqueue_key, int ipc_mode)
{
... else if (ipc_mode == SW_IPC_UNIXSOCK)
{
pool->pipes = sw_calloc(worker_num, sizeof(swPipe));
if (pool->pipes == NULL)
{
swWarn("malloc[2] failed.");
return SW_ERR;
} swPipe *pipe;
int i;
for (i = 0; i < worker_num; i++)
{
pipe = &pool->pipes[i];
if (swPipeUnsock_create(pipe, 1, SOCK_DGRAM) < 0)
{
return SW_ERR;
}
pool->workers[i].pipe_master = pipe->getFd(pipe, SW_PIPE_MASTER);
pool->workers[i].pipe_worker = pipe->getFd(pipe, SW_PIPE_WORKER);
pool->workers[i].pipe_object = pipe;
}
} ...
}

向 tasker 进程发布任务的时候,会调用 swProcessPool_dispatch 函数,进而会向 pipe_master 管道写入任务数据。


int swProcessPool_dispatch(swProcessPool *pool, swEventData *data, int *dst_worker_id)
{
... ret = swWorker_send2worker(worker, data, sendn, SW_PIPE_MASTER | SW_PIPE_NONBLOCK); ...
} int swWorker_send2worker(swWorker *dst_worker, void *buf, int n, int flag)
{
int pipefd, ret; if (flag & SW_PIPE_MASTER)
{
pipefd = dst_worker->pipe_master;
}
else
{
pipefd = dst_worker->pipe_worker;
} ... if ((flag & SW_PIPE_NONBLOCK) && SwooleG.main_reactor)
{
return SwooleG.main_reactor->write(SwooleG.main_reactor, pipefd, buf, n);
}
else
{
ret = swSocket_write_blocking(pipefd, buf, n);
} return ret; }

tasker 进程并没有 reactor 事件循环,只会阻塞在某个系统调用中,如果 tasker 进程采用的是 unix socket 进行投递任务的时候,就会阻塞在对管道的 read 当中。


static int swProcessPool_worker_loop(swProcessPool *pool, swWorker *worker)
{
... while (SwooleG.running > 0 && task_n > 0)
{
... else
{
n = read(worker->pipe_worker, &out.buf, sizeof(out.buf));
if (n < 0 && errno != EINTR)
{
swSysError("[Worker#%d] read(%d) failed.", worker->id, worker->pipe_worker);
}
} ...
} ...
}

原文地址:https://segmentfault.com/a/1190000016391195

Swoole 源码分析——基础模块之 Pipe 管道的更多相关文章

  1. Swoole 源码分析——Server模块之TaskWorker事件循环

    swManager_start 创建进程流程 task_worker 进程的创建可以分为三个步骤:swServer_create_task_worker 申请所需的内存.swTaskWorker_in ...

  2. Swoole 源码分析——Server模块之Worker事件循环

    swManager_loop 函数 manager 进程管理 manager 进程开启的时候,首先要调用 onManagerStart 回调 添加信号处理函数 swSignal_add,SIGTERM ...

  3. 精尽 MyBatis 源码分析 - 基础支持层

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  4. nginx源码分析之模块初始化

    在nginx启动过程中,模块的初始化是整个启动过程中的重要部分,而且了解了模块初始化的过程对应后面具体分析各个模块会有事半功倍的效果.在我看来,分析源码来了解模块的初始化是最直接不过的了,所以下面主要 ...

  5. [Abp vNext 源码分析] - 2. 模块系统的变化

    一.简要说明 本篇文章主要分析 Abp vNext 当中的模块系统,从类型构造层面上来看,Abp vNext 当中不再只是单纯的通过 AbpModuleManager 来管理其他的模块,它现在则是 I ...

  6. zepto源码分析·ajax模块

    准备知识 在看ajax实现的时候,如果对ajax技术知识不是很懂的话,可以参看下ajax基础,以便读分析时不会那么迷糊 全局ajax事件 默认$.ajaxSettings设置中的global为true ...

  7. zepto源码分析·core模块

    准备说明 该模块定义了库的原型链结构,生成了Zepto变量,并将其以'Zepto'和'$'的名字注册到了window,然后开始了其它模块的拓展实现. 模块内部除了对选择器和zepto对象的实现,就是一 ...

  8. nginx源码分析——event模块

    源码:nginx 1.12.0   一.简介      nginx是一款非常受欢迎的软件,具备高性能.模块化可定制的良好特性.之前写了一篇nginx的http模块分析的文章,主要对http处理模块进行 ...

  9. zepto源码分析·event模块

    准备知识 事件的本质就是发布/订阅模式,dom事件也不例外:先简单说明下发布/订阅模式,dom事件api和兼容性 发布/订阅模式 所谓发布/订阅模式,用一个形象的比喻就是买房的人订阅楼房消息,售楼处发 ...

随机推荐

  1. CGLib与JDK的动态代理

    一.CGLib 简单介绍 CGLib (Code Generation Library) 是一个强大的,高性能,高质量的Code生成类库. 它能够在执行期扩展Java类与实现Java接口. Hiber ...

  2. 3D语音天气球(源代码分享)——通过天气服务动态创建3D球

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 开篇废话: 这个项目准备分四部分介绍: 一:创建可旋转的"3D球":3 ...

  3. libyuv库的使用

    libyuv是Google开源的实现各种YUV与RGB之间相互转换.旋转.缩放的库.它是跨平台的,可在Windows.Linux.Mac.Android等操作系统.x86.x64.arm架构上进行编译 ...

  4. 例题 2-1 aabb 2-2 3n+1问题

    例题2-1  aabb 输出全部形如aabb的四位全然平方数(即前两位数字相等,后两位数字也相等) #include <stdio.h> #include <stdlib.h> ...

  5. 关于Linux静态库和动态库的分析

    关于Linux静态库和动态库的分析 关于Linux静态库和动态库的分析 1.什么是库 在windows平台和linux平台下都大量存在着库. 本质上来说库是一种可运行代码的二进制形式.能够被操作系统加 ...

  6. MySQL数据库管理(二)单机环境下MySQL Cluster的安装

    上文<MySQL数据库管理(一)MySQL Cluster集群简单介绍>对MySQL Cluster集群做了简要介绍.本文将教大家一步步搭建单机环境下的MySQL数据库集群. 一.单机环境 ...

  7. DexClassLoader和PathClassLoader类载入机制

    0x00 在DexClassLoader和PathClassLoader载入Dex流程一文中,我们分析了dex文件怎样形成了DexFile结构体.本文中解说类载入机制,实际上就是生成ClassObje ...

  8. Running the app on your device

    So far, you've run the app on the Simulator. That's nice and all but probably notwhy you're learning ...

  9. Uva1335 二分+贪心

    /* 奇数怎么搞呢 二分到答案怎么judge呢 贪心怎么贪呢 假设贪心方案是 前两个挨着取 后面的能靠前就靠前 这样子似乎保证了ans最min 但是不管贪的对不对 操作起来时间GG 而且 如果真的这样 ...

  10. ubuntu修改capslock键,单独使用为esc,组合使用时为ctrl+

    一.下面这部分可以将capslock与ctrl互换 将下面的代码放入-/.Xmodmap中, remove Lock = Caps_Lock remove Control = Control_L ke ...