Swoole源码学习记录(十一)——Worker,Connection

Swoole版本:1.7.5-stable

Github地址: https://github.com/LinkedDestiny/swoole-src-analysis

本章将分析Swoole中的三个比较重要的模块,Worker,ReactorProcess和Connection。其中Worker和ReactorProcess其实是对前面三章的一个补充,在前面的章节中为了分析结果的流畅性没有针对这些模块做特定分析,在此做出补充。

Worker模块

首先是Worker模块。Worker在Swoole中为核心工作进程的封装,包括用于处理核心逻辑的worker和用于处理任务的task_worker。在Swoole中使用了结构体swWorker来封装worker进程的相关属性,其声明在swoole.h文件中的727 – 787行,其声明如下:

1struct _swWorker 2{ 3 /** 4 * worker process 5 */ 6 pid_tpid; 7 8 /** 9 * worker thread 10 */ 11 pthread_ttid; 12 13 swProcessPool*pool; 14 15 swMemoryPool*pool_output; 16 17 swQueue*queue; 18 19 /** 20 * redirect stdout to pipe_master 21 */ 22 uint8_tredirect_stdout; 23 24 /** 25 * worker status, IDLE or BUSY 26 */ 27 uint8_tstatus; 28 29 uint8_tipc_mode; 30 31 /** 32 * redirect stdin to pipe_worker 33 */ 34 uint8_tredirect_stdin; 35 36 /** 37 * worker id 38 */ 39 uint32_tid; 40 41 /** 42 * eventfd, process notify 43 */ 44 swPipe*notify; 45 46 /** 47 * share memory store 48 */ 49 struct 50 { 51 uint8_tlock; 52 void*ptr; 53 }store; 54 55 intpipe_master; 56 intpipe_worker; 57 intpipe; 58 intreactor_id; 59 void*ptr; 60 void*ptr2; 61};

标有注释的变量就不说明了,大家一看就明白。剩下的几个,pool是个进程池,用于分配task_worker,pool_output用于存放task_worker执行结束后的结果,queue是消息队列,ipc_mode是进程间的通讯模式,pipe_master和pipe_worker是管道的fd,分别用于写消息到master进程和从master读取消息,pipe当然就是管道的id,reactor_id是该worker归属的reactor的标志。(ptr和ptr2待补充,实在没找到这俩变量在哪用的)

这里做一点补充,一个Worker中有两个管道,一个管道用于和master进程通信,一个管道用于和Reactor通信。而实际上如果指定了消息队列模式,则通信方式都是通过读写swQueue队列来实现的。这几点在之前的分析中已经有说明了,再此补充说明一下。

swWorker有四个操作函数,这些函数声明在Server.h文件中的545 – 548行,其声明如下:

1int swWorker_create(swWorker *worker); 2void swWorker_free(swWorker *worker); 3void swWorker_signal_init(void); 4void swWorker_signal_handler(int signo);

这四个函数声明在Worker.c中,其中swWorker_free只是释放了worker的store内存并且关闭了notify管道(不接收reactor消息),swWorker_signal_init指定了对应信号的回调函数,swWorker_signal_handler规定了对应信号的操作(基本——没内容,看一看就好……),这里只贴出swWorker_create的源码:

1void*store = sw_shm_malloc(SwooleG.serv->buffer_output_size); 2 if(store == NULL) 3 { 4 swWarn("mallocfor worker->store failed."); 5 returnSW_ERR; 6 } 7 8 swPipe*worker_notify = sw_malloc(sizeof(swPipe)); 9 if(worker_notify == NULL) 10 { 11 swWarn("mallocfor worker->notify failed."); 12 sw_shm_free(store); 13 returnSW_ERR; 14 } 15 16 /** 17 * Create notify pipe 18 */ 19 if(swPipeNotify_auto(worker_notify, 1, 0)) 20 { 21 sw_shm_free(store); 22 sw_free(worker_notify); 23 returnSW_ERR; 24 }

源码解释:创建共享内存store,分配通信管道notify的内存,并调用swPipeNotify_auto创建实际管道(根据内核版本决定使用eventfd管道还是Base管道)。

Connection模块

Connection模块应该是少有的有自己独立的头文件和C文件的模块了……

Connection用于存储一个实际的连接(C-to-S),用来存放一些相关的变量,比如连接时长、上一次响应时间之类的,当然另一个好处就在于可以给这个连接加一个SSL控制然后就能实现收发数据的安全加密解密了对不对(好像很厉害的样子)……

首先是swConnection结构体,该结构体封装了所有Connection的属性变量,其声明在Connection.c文件中,声明如下:

1typedef struct _swConnection 2{ 3 /** 4 * is active 5 * system fd must be 0. en: timerfd, signalfd,listen socket 6 */ 7 uint8_tactive; 8 9 /** 10 * file descript 11 */ 12 intfd; 13 14 /** 15 * ReactorThread id 16 */ 17 uint16_tfrom_id; 18 19 /** 20 * from which socket fd 21 */ 22 uint16_tfrom_fd; 23 24 /** 25 * socket address 26 */ 27 structsockaddr_in addr; 28 29 /** 30 * link any thing 31 */ 32 void*object; 33 34 /** 35 * input buffer 36 */ 37 swBuffer*in_buffer; 38 39 /** 40 * output buffer 41 */ 42 swBuffer*out_buffer; 43 44 /** 45 * connect time(seconds) 46 */ 47 time_tconnect_time; 48 49 /** 50 * received time with last data 51 */ 52 time_tlast_time; 53 54#ifdef SW_USE_OPENSSL 55 SSL*ssl; 56 uint32_tssl_state; 57#endif 58 59} swConnection;

基本上每一个变量都有注释我就不多废话了……不过这里说明一下,swConnection中的fd变量存放的是该连接所对应的描述符,而那个from_fd吧……经过一系列的排查我最终发现……from_fd是Server创建的监听fd。

(大概说一下排查过程,首先找到创建Connection的函数,因为知道Connection大概会在Server处创建,所以在Server.h中找到swServer_connection_new,然后看每个变量的具体赋值,然后发现from_fd是参数中的swDataHead赋予的,于是回追到调用swServer_connection_new的函数swServer_master_onAccept,然后就发现fd来自于accept的返回值,是一个新创建的socket连接,而from_fd是server创建的用于监听的socketfd)

Connection的操作函数分布在两个地方,new和close函数声明在Server.h的476 – 477 行,而其他操作函数都在Connection.h文件中声明(收发数据、操作缓存)。

首先是在Server.h中用于新建和关闭连接的函数,其声明如下:

1swConnection* swServer_connection_new(swServer *serv, swDataHead *ev); 2int swServer_connection_close(swServer*serv, int fd, int notify);

这两个函数的具体定义在Server.c文件中,swServer_connection_new函数定义在文件最末尾,其核心代码如下:

1int conn_fd = ev->fd; 2 swConnection* connection = NULL; 3 4 SwooleStats->accept_count++; 5 sw_atomic_fetch_add(&SwooleStats->connection_num, 1); 6 7 if (conn_fd > swServer_get_maxfd(serv)) 8 { 9 swServer_set_maxfd(serv, conn_fd); 10#ifdef SW_CONNECTION_LIST_EXPAND 11 //新的fd超过了最大fd 12 //需要扩容 13 if (conn_fd == serv->connection_list_capacity - 1) 14 { 15 void *new_ptr = sw_shm_realloc(serv->connection_list,sizeof(swConnection)*(serv->connection_list_capacity +SW_CONNECTION_LIST_EXPAND)); 16 if (new_ptr == NULL) 17 { 18 swWarn("connection_listrealloc fail"); 19 return SW_ERR; 20 } 21 else 22 { 23 serv->connection_list_capacity += SW_CONNECTION_LIST_EXPAND; 24 serv->connection_list =(swConnection *)new_ptr; 25 } 26 } 27#endif 28 } 29 30 connection = &(serv->connection_list[conn_fd]); 31 32 connection->fd = conn_fd; 33 connection->from_id = ev->from_id; 34 connection->from_fd = ev->from_fd; 35 connection->connect_time = SwooleGS->now; 36 connection->last_time = SwooleGS->now; 37 connection->active= 1; //使此连接激活,必须在最后,保证线程安全

源码解释:首先将accept_count和connection_num两个计数器加1。如果新的conn_fd超过了serv中连接列表的最大容量,就将连接列表扩容(注意到这里调用的是sw_shm_realloc,也就是connection_list的内存是可共享的)。随后将新的conn_fd加入连接列表中,然后给connection的属性赋值。主要的属性有:连接描述符fd,来自于哪个reactor、来自于哪个监听fd、连接的时间和上一次响应时间、连接的active状态。

然后是swServer_connection_close函数,该函数也定义在Server.c的末尾,让我没想到的是一个close函数也有这么长,以至于我不得不分段进行分析……

1conn->active = 0; 2 /** 3 * Close count 4 */ 5 sw_atomic_fetch_add(&SwooleStats->close_count,1); 6 sw_atomic_fetch_sub(&SwooleStats->connection_num,1); 7 8 intreactor_id = conn->from_id; 9 10 reactor= &(serv->reactor_threads[reactor_id].reactor);

源码解释:首先将conn设置为关闭状态,然后修改两个计数器close_count和connection_num.随后根据conn中的reactor_id从serv的reactor列表中获取对应的reactor。

1//释放缓存区占用的内存 2 if (serv->open_eof_check) 3 { 4 if (conn->in_buffer) 5 { 6 swBuffer_free(conn->in_buffer); 7 conn->in_buffer = NULL; 8 } 9 } 10 else if (serv->open_length_check) 11 { 12 if (conn->object) 13 { 14 swString_free(conn->object); 15 } 16 } 17 else if (serv->open_http_protocol) 18 { 19 if (conn->object) 20 { 21 swHttpRequest *request = (swHttpRequest *) conn->object; 22 if (request->state > 0 && request->buffer) 23 { 24 swTrace("ConnectionClose.free buffer=%p, request=%p\n", request->buffer, request); 25 swString_free(request->buffer); 26 bzero(request,sizeof(swHttpRequest)); 27 } 28 } 29 } 30 if(conn->out_buffer != NULL) 31 { 32 swBuffer_free(conn->out_buffer); 33 conn->out_buffer = NULL; 34 } 35 36 if (conn->in_buffer != NULL) 37 { 38 swBuffer_free(conn->in_buffer); 39 conn->in_buffer = NULL; 40 }

源码解释:这里用于释放conn开启的缓存区。如果开启了eof_check,则需要将conn的输入缓存释放(这里放着没有检测到eof的数据);如果打开了length_check,则将object指向的对象释放;如果使用了http协议,则释放object指向的swHttpRequest对象。最后,释放out_buffer和in_buffer。

1//通知到worker进程 2 if (serv->onClose != NULL && notify == 1) 3 { 4 //通知worker进程 5 notify_ev.from_id = reactor_id; 6 notify_ev.fd = fd; 7 notify_ev.type = SW_EVENT_CLOSE; 8 SwooleG.factory->notify(SwooleG.factory, ¬ify_ev); 9 }

源码解释:如果设置了onClose回调并且指定需要通知,则调用factory的notify方法发送通知到worker进程。(所以发现onClose回调异常的童鞋可以通过这里查找bug)

1#ifdef SW_USE_OPENSSL 2 if(conn->ssl) 3 { 4 swSSL_close(conn); 5 } 6#endif 7 8 /** 9 * reset maxfd, for connection_list 10 */ 11 if (fd == swServer_get_maxfd(serv)) 12 { 13 SwooleG.lock.lock(&SwooleG.lock); 14 int find_max_fd = fd - 1; 15 swTrace("set_maxfd=%d|close_fd=%d\n", find_max_fd, fd); 16 /** 17 * Find the new max_fd 18 */ 19 for (; serv->connection_list[find_max_fd].active == 0 &&find_max_fd > swServer_get_minfd(serv); find_max_fd--); 20 swServer_set_maxfd(serv, find_max_fd); 21 SwooleG.lock.unlock(&SwooleG.lock); 22 } 23 24 //关闭此连接,必须放在最前面,以保证线程安全 25 returnreactor->del(reactor, fd);

源码解释:如果使用了SSL功能,则需要从SSL中移除该SSL。如果关闭的fd是maxfd,则需要重新设置serv中的连接列表,操作步骤为先锁住全局变量SwooleG,然后从末尾循环便利connection_list直到找到当前最大的fd,最后解锁。最后一步,从reactor中移除监听的fd(谁能给我解释一下这句注释啥意思,很急,在线等)

在Connection.h中一共声明了10个操作函数,其中三个内联函数。据我目测,实际上原来应该没有swConnection_recv和swConnection_send这两个函数,应该是有了SSL特性后专门添加上的,因为这俩函数就只是判断了一下有没有开启SSL特性,如果开启了并且conn设置了ssl,则调用ssl的安全读写方法。swConnection_error函数也不具体分析了,只是根据errcode的值返回对应的SW_*。

另外7个操作函数声明如下:

1int swConnection_send_blocking(int fd, void*data, int length, int timeout); 2int swConnection_buffer_send(swConnection*conn); 3 4swString *swConnection_get_string_buffer(swConnection *conn); 5void swConnection_clear_string_buffer(swConnection *conn); 6volatile swBuffer_trunk*swConnection_get_out_buffer(swConnection *conn, uint32_t type); 7volatile swBuffer_trunk*swConnection_get_in_buffer(swConnection *conn); 8int swConnection_sendfile(swConnection*conn, char *filename);

可以看到基本上都是用于发送数据、文件以及操作缓存区的函数,下面一个个上分析。

首先是swConnection_send_blocking函数,该函数是一个阻塞式的发送函数,其源码如下:

1while (writen > 0) 2 { 3 if (swSocket_wait(fd, timeout, SW_EVENT_WRITE) < 0) 4 { 5 return SW_ERR; 6 } 7 else 8 { 9 n = send(fd, data, writen, MSG_NOSIGNAL | MSG_DONTWAIT); 10 if (n < 0) 11 { 12 swWarn("send() failed.Error: %s[%d]", strerror(errno), errno); 13 return SW_ERR; 14 } 15 else 16 { 17 writen -= n; 18 continue; 19 } 20 } 21 }

源码解释:首先调用swSocket_wait函数(调用poll函数监听socket直到socket满足监听条件)监听到fd可写,随后调用send函数发送数据;循环写入直到所有数据都写入fd。

这里我补充说明一下swoole的Buffer.c,不作详细分析,只说明其功能。Buffer是个链表式结构,每个链表节点是一个trunk,trunk用于存放具体的数据。

下面分析swConnection_buffer_send函数,这个函数是将conn中的输出缓存区中的数据发出,核心源码如下:

1swBuffer *buffer= conn->out_buffer; 2 swBuffer_trunk *trunk = swBuffer_get_trunk(buffer); 3 sendn = trunk->length - trunk->offset; 4 5 if (sendn == 0) 6 { 7 swBuffer_pop_trunk(buffer, trunk); 8 return SW_CONTINUE; 9 } 10 ret = swConnection_send(conn, trunk->store.ptr + trunk->offset,sendn, 0); 11 //printf("BufferOut:reactor=%d|sendn=%d|ret=%d|trunk->offset=%d|trunk_len=%d\n",reactor->id, sendn, ret, trunk->offset, trunk->length); 12 if (ret < 0) 13 { 14 switch (swConnection_error(errno)) 15 { 16 case SW_ERROR: 17 swWarn("send to fd[%d] failed. Error: %s[%d]", conn->fd,strerror(errno), errno); 18 return SW_OK; 19 case SW_CLOSE: 20 return SW_CLOSE; 21 case SW_WAIT: 22 return SW_WAIT; 23 default: 24 return SW_CONTINUE; 25 } 26 } 27 //trunk full send 28 else if (ret == sendn || sendn == 0) 29 { 30 swBuffer_pop_trunk(buffer, trunk); 31 } 32 else 33 { 34 trunk->offset += ret; 35 }

源码解释:首先从conn中的输出缓存中拿到首部的trunk,并计算该trunk还有多少数据需要发送。如果已经发送完了,弹出该trunk,继续发送下一个trunk。如果没有发送完,调用swConnection_send方法发送剩余数据。随后从trunk中移除已经发送的数据,并再次判定是否发送结束。

swConnection中有个void* object变量,这个指针可以用来指向一些奇奇怪怪的东西,比如一个swString对象,于是就有了两个操作函数swConnection_get_string_buffer和swConnection_clear_string_buffer,这俩函数不贴代码了,一个new一个free而已。

接着分析两个操作buffer的函数。首先是swConnection_get_in_buffer,该函数的作用是创建一个新的trunk加入到in_buffer中,并返回这个trunk的指针,其核心源码如下:

1if (conn->in_buffer == NULL) 2 { 3 buffer = swBuffer_new(SW_BUFFER_SIZE); 4 //buffer create failed 5 if (buffer == NULL) 6 { 7 return NULL; 8 } 9 //new trunk 10 trunk = swBuffer_new_trunk(buffer, SW_TRUNK_DATA,buffer->trunk_size); 11 if (trunk == NULL) 12 { 13 sw_free(buffer); 14 return NULL; 15 } 16 conn->in_buffer = buffer; 17 } 18 else 19 { 20 buffer = conn->in_buffer; 21 trunk = buffer->tail; 22 if (trunk == NULL || trunk->length == buffer->trunk_size) 23 { 24 trunk = swBuffer_new_trunk(buffer, SW_TRUNK_DATA,buffer->trunk_size); 25 } 26 }

源码解释:如果in_buffe为空,则创建一个swBuffer,并在这个buffer中创建一个新的trunk;否则,直接在in_buffer中创建trunk并添加到buffer末尾。

swConnection_get_out_buffer用于从out_buffer中拿到一个trunk。核心源码如下:

1if (conn->out_buffer == NULL) 2 { 3 conn->out_buffer = swBuffer_new(SW_BUFFER_SIZE); 4 if (conn->out_buffer == NULL) 5 { 6 return NULL; 7 } 8 } 9 if (type == SW_TRUNK_SENDFILE) 10 { 11 trunk = swBuffer_new_trunk(conn->out_buffer, SW_TRUNK_SENDFILE, 0); 12 } 13 else 14 { 15 trunk = swBuffer_get_trunk(conn->out_buffer); 16 if (trunk == NULL) 17 { 18 trunk = swBuffer_new_trunk(conn->out_buffer, SW_TRUNK_DATA,conn->out_buffer->trunk_size); 19 } 20 }

源码解释:如果out_buffer为空,尝试创建,如果创建失败,返回NULL。然后判定type类型,如果为SENDFILEl类型,则直接创建type类型的trunk;如果不是,则尝试从buffer中获取首部的trunk,如果该trunk为空,则创建新的trunk。

这里重点分析一下swConnection_sendfile函数,该函数用于在out_buffer中添加一个用于sendfile的trunk。其核心源码如下:

1if(conn->out_buffer == NULL) 2 { 3 conn->out_buffer = swBuffer_new(SW_BUFFER_SIZE); 4 if (conn->out_buffer == NULL) 5 { 6 return SW_ERR; 7 } 8 } 9 10 swBuffer_trunk *trunk = swBuffer_new_trunk(conn->out_buffer,SW_TRUNK_SENDFILE, 0); 11 if (trunk == NULL) 12 { 13 swWarn("get out_buffer trunk failed."); 14 return SW_ERR; 15 } 16 swTask_sendfile *task = sw_malloc(sizeof(swTask_sendfile)); 17 if (task == NULL) 18 { 19 swWarn("malloc for swTask_sendfile failed."); 20 //TODO: 回收这里的内存 21 return SW_ERR; 22 } 23 bzero(task, sizeof(swTask_sendfile)); 24 25 task->filename = strdup(filename); 26 int file_fd = open(filename, O_RDONLY); 27 if (file_fd < 0) 28 { 29 swWarn("open file[%s] failed. Error: %s[%d]",task->filename, strerror(errno), errno); 30 return SW_ERR; 31 } 32 struct stat file_stat; 33 if (fstat(file_fd, &file_stat) < 0) 34 { 35 swWarn("swoole_async_readfile: fstat failed. Error: %s[%d]",strerror(errno), errno); 36 return SW_ERR; 37 } 38 39 task->filesize = file_stat.st_size; 40 task->fd = file_fd; 41 trunk->store.ptr= (void *)task;

源码解释:先判定out_buffer是否存在,如果不存在则创建,随后在out_buffer中创建一个类型为sendfile的trunk。创建一个sendfile的task(swTask_sendfile),指定文件名以及文件的描述符fd,通过fstat函数获取文件的大小。随后将task添加到trunk的存储空间中。

以上就是Connection.c模块的相关分析。这章文字不多,代码不少,各位读者见谅……

(原计划分析ReactorProcess模块,但是发现这里需要和ProcessPool一起分析,因此单独开一章。)

版权声明:本文为博主原创文章,未经博主允许不得转载。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )