五、nginx的进程间通信之socketpair(参考《深入剖析Nginx》)

(一)父子进程通信

nginx父子进程之间或子进程之间势必涉及到进程间通信,这里采用了socketpair进行通信。在Linux下,可使用socketpair函数创造一对的、相互连接的域套接字。套接字对建立的通道是双向的,每一端都可以进行读写

   // socketpair — create a pair of connected sockets
    int socketpair(int domain, int type, int protocol, int *sv);

前一篇文章(nginx启动过程中的进程创建)中提到了nginx启动子进程的函数,ngx_spawn_process,nginx进程间通信的套接字就是在这个函数中创建的。其主要代码如下:

    if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
    {
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "socketpair() failed while spawning \"%s\"", name);
        return NGX_INVALID_PID;
    }
    /* ...*/
     pid = fork();
     switch (pid) {
      /* ... */
     }

重点在这一句:

socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel)

AF_UNIX用于同一台机器上的进程间通信;SOCK_STREAM提供的稳定数据传输,即TCP协议; ngx_processes是全局变量,ngx_processes[s].channel用于指定存储套接字的容器。

由于socketpair函数是在fork之前调用的,所以在fork之后,父子进程都会拥有该套接字。那么,只要父进程使用channel[0],子进程使用channel[1],就能实现父子进程之间的通信。

(二)子进程间通信

若是不同子进程之间想要通信,又该如何呢?既然套接字存储在ngx_processes[s]
.channel中,而ngx_processes又是全局变量,只要子进程的ngx_processes中存储着其它所有子进程的channel信息,就能给任意一个子进程发送消息。

显然,ngx_processes是从父进程处继承而来的,虽然父进程中的ngx_processes始终是最新最全,但子进程之间是有先后顺序的。比如说,在用户自定义工作进程为5个时,nginx的master进程将for循环5次产生5个子进程,则第5个子进程可以从父进程处获得前四个子进程的channel信息,而第4个子进程继承父进程时,由于第5个子进程还未产生,自然无法获得第5个子进程的channel信息。

如此一来,后产生的子进程拥有其“哥哥”们的channel信息,可以给“哥哥”们发消息,而“哥哥”们没有后产生的子进程的channel信息,便无法给“弟弟”们发消息。解决办法很简单,只需把“弟弟”们的相关信息发送给“哥哥”们即可。

执行这个任务的,正是父进程(master)。在ngx_start_worker_processes中的定义如下:

      for (i = 0; i < n; i++) {
    cpu_affinity = ngx_get_cpu_affinity(i);
    ngx_spawn_process(cycle, ngx_worker_process_cycle, NULL,
                      "worker process", type);
    ch.pid = ngx_processes[ngx_process_slot].pid;
    ch.slot = ngx_process_slot;
    ch.fd = ngx_processes[ngx_process_slot].channel[0];
    ngx_pass_open_channel(cycle, &ch);
}

ngx_spawn_process函数用于产生子进程,然后将子进程的信息存储在结构体变量ch中,最后用 ngx_pass_open_channel函数将存储了新子进程相关信息的结构体ch发送给其它子进程。ngx_pass_open_channel的定义如下:

  static void
  ngx_pass_open_channel(ngx_cycle_t *cycle, ngx_channel_t *ch)
{
ngx_int_t  i;
for (i = 0; i < ngx_last_process; i++) {
    /* ... */
    ngx_write_channel(ngx_processes[i].channel[0],
                      ch, sizeof(ngx_channel_t), cycle->log);
    }
}

代码很清晰,就是不断地把子进程的信息通过第i个子进程的channel[0]发送,当第i个进程接收到结构体ch(存储着新子进程的pid和channel信息)后,再进行相关处理即可。

那么当子进程收到其它新子进程的信息时,具体是怎么处理的呢?

在上一篇文章中,已经知道在子进程产生后,会执行ngx_worker_process_cycle函数。此函数的开头将调用ngx_worker_process_init函数初始化子进程,而在初始化过程中,将把自己从父进程继承的ngx_channel[1](channel[0]被用于父进程或其它子进程写消息)加入到读事件监听集里。

下面先看一看ngx_worker_process_init的定义:

      if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
          ngx_channel_handler)  == NGX_ERROR)
    {
        /* fatal */
        exit(2);
     }

其中,ngx_channel 是全局变量,在ngx_spawn_process中被赋值为:

    ngx_channel = ngx_processes[s].channel[1];

ngx_channel_handler是对应的处理函数,在该函数中,对此事件的处理方式为:

     case NGX_CMD_OPEN_CHANNEL:
        /*...*/
        ngx_processes[ch.slot].pid = ch.pid;
        ngx_processes[ch.slot].channel[0] = ch.fd;
        break;

也就是将接收到的新子进程的pid和channel信息存储到全局变量ngx_processes的相应位置中,如此一来,进程之间都互相有了彼此的channel信息和pid号,也就可以互相通信了。

(三)关于nginx_channel.c

这里关注一下nginx_channel.c。该文件定义了有关nginx利用channel通信的函数。其结构如下:


nginx_channel结构.png

可以看到,只有四个函数,分别对应着写、读、添加事件监听和关闭channel的功能。

  • 关于写消息的函数,其定义如下。

      ngx_int_t
      ngx_write_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size, ngx_log_t *log)
    

    该函数通过socket s发送大小为size字节的消息ch。

  • 关于读消息的函数,其定义如下。

      ngx_int_t
      ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size, ngx_log_t *log)
    

    该函数通过socket s读取大小为size字节的消息ch。

  • 关于关闭channel的函数,其定义如下。

    void
    ngx_close_channel(ngx_fd_t *fd, ngx_log_t *log)
    {
        if (close(fd[0]) == -1) {
        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "close() channel failed");
        }
        if (close(fd[1]) == -1) {
        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "close() channel failed");
        }
    }
    

    该函数将socket_pair的两个文件描述符依次关闭。

  • 关于ngx_add_channel_event,其定义为

      ngx_int_t
    ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd, ngx_int_t event,
    ngx_event_handler_pt handler)
    

    这里大概是将某文件描述符添加到某事件集中,涉及到nginx的事件监听和处理,其具体的运行原理,待我下一章详述。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容