Swoole源碼學習記錄(六)——Pipe管道

Swoole版本:1.7.4-stable

Pipe(管道)用於進程之間的數據交互,Linux系統本身提供了pipe函數用於創建一個半雙工通信管道,而在swoole中也利用eventfd和unix sock封裝了兩種管道,使得進程間的通信更加靈活。

Swoole提供了一個基本結構體swPipe用於操作不同類型的Pipe,其聲明在swoole.h文件的315 – 326 行,聲明如下:

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 *, intisWriteFd);
         int (*close)(struct _swPipe *);
}swPipe;

其中,首部的void *object指向具體的管道結構體(Base,Eventfd,Unsock),intblocking標記該管道是否是阻塞類型,double timeout爲阻塞的超時時間,餘下四個函數用於對管道進行讀寫、關閉等操作。


一.基於pipe函數的PipeBase

前面提到過,Linux本身提供了一個pipe函數用於創建一個管道,swoole基於這個函數進行了一層封裝用於簡化操作,並命名爲PipeBase,其結構體聲明在PipeBase.c文件中,聲明如下:

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

其中的 int pipes[2] 用於存放pipe的讀端fd和寫端fd。

創建一個PipeBase管道的函數聲明在swoole.h文件的328行,聲明如下:

int swPipeBase_create(swPipe *p, int blocking);

第二個參數標記該管道是否阻塞。該函數具體定義在PipeBase.c文件中,其核心代碼如下:

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

源碼解釋:創建一個PipeBase結構體,指定管道阻塞類型爲blocking參數,並調用pipe函數創建一個linuxpipe,獲取pipe fd。如果創建管道成功,若管道爲非阻塞模式,則調用swSetNonBlock函數(聲明於swoole.h文件的641行,定義於Base.c文件的507 – 529行,該函數的分析在本章末尾)設置兩個fd爲非阻塞;若管道爲阻塞模式,則指定timeout爲-1.

PipeBase有四個操作函數分別用於讀、寫、獲取描述符以及關閉管道。其聲明如下:

static int swPipeBase_read(swPipe *p, void *data, int length);
static int swPipeBase_write(swPipe *p, void *data, int length);
static int swPipeBase_getFd(swPipe *p, int isWriteFd);
static int swPipeBase_close(swPipe *p);

1.      swPipeBase_read函數核心源碼如下:

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);

源碼解釋:如果該管道爲阻塞模式,且指定了timeout時間,則調用swSocket_wait函數(定義於Base.c文件中的236 – 268行,該函數分析在本章末尾)執行等待,若超時,則返回SW_ERR;否則,直接調用read方法讀取數據(此時,若管道爲非阻塞模式,因已經指定了fd的options,所以read方法不會阻塞;若爲阻塞模式,則read函數會一直阻塞到有數據可讀)

2.      swPipeBase_write函數直接調用write函數寫出數據

3.      swPipeBase_getFd函數根據 isWriteFd判定返回 寫fd 或者 讀fd

4.      swPipeBase_close函數調用close函數關閉描述符並釋放swPipeBase內存。

二.基於eventfd函數的PipeEventfd

Eventfd是Linux提供的一個事件提醒(eventnotification)功能,eventfd函數創建一個對象用於事件的等待/提醒,可以從內核態發送通知到用戶態。一個eventfd創建的對象包括一個無符號的64bit整型計數器,該計數器被內核持有。

eventfd函數的第二個參數用於指定flags,其中EFD_NONBLOCK指定eventfd是否非阻塞,EFD_SEMAPHORE用於指定eventfd的讀寫效果。

Swoole用一個結構體swPipeEventfd代表一個eventfd類型的管道,其聲明如下:

typedef struct _swPipeEventfd
{
         int event_fd;
}swPipeEventfd;

其中event_fd就是用eventfd函數獲得的描述符。

創建一個swPipeEventfd的函數聲明在swoole.h的329行,聲明如下:

int swPipeEventfd_create(swPipe *p, int blocking, int semaphore, int timeout);

其中第三個參數semaphore標記該管道是否僅用於提供通知,第四個參數timeout指定阻塞超時的時間。

該函數具體定義在PipeEventfd.c文件,其核心代碼如下:

         swPipeEventfd *object =sw_malloc(sizeof(swPipeEventfd));
         if (object == NULL)
         {
                   return -1;
         }
 
         flag = EFD_NONBLOCK;
 
         if (blocking == 1)
         {
                   if (timeout > 0)
                   {
                            flag = 0;
                            p->timeout = -1;
                   }
                   else
                   {
                            p->timeout =timeout;
                   }
         }
 
#ifdefEFD_SEMAPHORE
         if (semaphore == 1)
         {
                   flag |= EFD_SEMAPHORE;
         }
#endif
 
         p->blocking = blocking;
         efd = eventfd(0, flag);

源碼解釋:創建一個swPipeEventfd對象,默認設置管道爲非阻塞的,若參數blocking指定管道爲阻塞,則指定timeout的值。如果指定了semaphore參數,則將EFD_SEMAPHORE加入flags中。最後調用eventfd函數獲取event_fd.

這裏需要特別說明EFD_SEMAPHORE參數。如果一個eventfd被指定了該參數,當eventfd的計數器不爲0時,對eventfd的read操作會讀到一個8byte長度的整數1,然後計數器的值減1;如果沒有指定該參數,當eventfd的計數器不爲0時,對eventfd的read操作會將計數器的值讀出(8byte的整數),並將計數器置0.

PipeEventfd同樣有四個操作函數分別用於讀、寫、獲取描述符以及關閉管道。其聲明如下

static int swPipeEventfd_read(swPipe *p, void *data, int length);
static int swPipeEventfd_write(swPipe *p, void *data, int length);
static int swPipeEventfd_getFd(swPipe *p, int isWriteFd);
static int swPipeEventfd_close(swPipe *p);

1.      swPipeEventfd_read核心源碼如下:

        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;
         }

源碼解釋:如果管道爲可阻塞且timeout大於0,則調用swSocket_wait函數(定義於Base.c文件中的236 – 268行,該函數分析在本章末尾)執行等待。若超時,則返回SW_ERR;否則,循環調用read函數直到讀取到數據。

2.      swPipeEventfd_write函數循環調用write函數直到寫入數據成功。

3.      swPipeEventfd_getFd函數返回event_fd

4.      swPipeEventfd_close函數調用close關閉event_fd並釋放內存。

三.基於unix socket的PipeUnsock

PipeUnsock使用了socketpair函數和AF_UNIX(Unix Socket)來創建一個全雙工的“管道”。這其實類似於Linux的pipe函數,不同的是文件描述符fd換成了套接字描述符sockfd,半雙工通訊變成了全雙工通訊。

swPipeUnsock結構體聲明在PipeUnsock.c文件中,其聲明如下:

</pre><p><pre name="code" class="cpp">typedef struct _swPipeUnsock
{
         int socks[2];
} swPipeUnsock;

其中socks用於存放兩個socket套接字

創建一個swPipeUnsock的函數聲明在swoole.h文件的330行,聲明如下:

int swPipeUnsock_create(swPipe *p, intblocking, int protocol);

其中第二個參數指定socket使用的協議族(TCP/UDP)

其核心代碼如下:

         p->blocking =blocking;
         ret= socketpair(AF_UNIX, protocol, 0, object->socks);
         if(ret < 0)
         {
                   swWarn("socketpair()failed. Error: %s [%d]", strerror(errno), errno);
                   returnSW_ERR;
         }
         else
         {
                   //Nonblock
                   if(blocking == 0)
                   {
                            swSetNonBlock(object->socks[0]);
                            swSetNonBlock(object->socks[1]);
                   }
 
                   intsbsize = SwooleG.unixsock_buffer_size;
                   setsockopt(object->socks[1],SOL_SOCKET, SO_SNDBUF, &sbsize, sizeof(sbsize));
                   setsockopt(object->socks[1],SOL_SOCKET, SO_RCVBUF, &sbsize, sizeof(sbsize));
                   setsockopt(object->socks[0],SOL_SOCKET, SO_SNDBUF, &sbsize, sizeof(sbsize));
                   setsockopt(object->socks[0],SOL_SOCKET, SO_RCVBUF, &sbsize, sizeof(sbsize));
         }

源碼解釋:設置管道阻塞類型,並調用socketpair獲取兩個套接字,並指定套接字類型爲AF_UNIX,如果管道爲非阻塞類型,則調用swSetNonBlock函數設置套接字屬性。然後指定socket buffer 的大小(SwooleG是一個swServerG結構體,用於存放一些全局變量,該對象聲明在Server.c中,在此不作分析),並調用setsockopt函數設置套接字選項。

swPipeUnsock的操作函數和swPipeBase基本一樣,在此不再分析。

 

至此,Swoole的Pipe模塊已分析完成。


附錄:Base.c中相關函數的分析

1.     swSetNonBlock

函數原型:

void swSetNonBlock(int sock); // swoole.h  641行

核心代碼:(Base.c  507 - 529行)

do
{
           opts =fcntl(sock, F_GETFL);
}
while(opts <0&& errno == EINTR);
if (opts < 0)
{
           swWarn("fcntl(sock,GETFL)fail");
}
opts = opts |O_NONBLOCK;
do
{
           ret =fcntl(sock, F_SETFL, opts);
}
while(ret <0&& errno == EINTR);

源碼解釋:調用fcntl函數,通過F_GETFL命令獲得描述符的狀態flags(沒有一個比較好的翻譯……百度翻譯成旗標);將O_NONBLOCK(非阻塞標記)加入flags,並通過fcntl函數的F_SETFL命令設置描述符的狀態flags。

2.     swSocket_wait

函數原型:

int swSocket_wait(int fd, inttimeout_ms, int events)  // swoole.h  648行

核心代碼:(Base.c  236 – 268行)

structpollfd 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;
           }
}

源碼解釋:創建 pollfd 結構體,指定監聽的fd,並根據events的值指定需要監聽的事件(讀事件or寫事件)。接着在循環中調用poll函數監聽,並指定poll函數的超時時間爲timeout_ms參數。若成功監聽到事件,則返回SW_OK,否則返回SW_ERR。


發佈了31 篇原創文章 · 獲贊 27 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章