【原创】服务器开发之 Daemon 和 Keepalive

      由于业务开发需要,需要对数据库代理进行研究,在研究 MySQL Proxy 实现原理的过程中,对一些功能点进行了分析总结。本文主要讲解下 MySQL Proxy 的 daemon 和 keepalive 功能实现原理。

      MySQL Proxy 是数据库代理实现中的一种,提供了 MySQL server 与 MySQL client 之间的通信功能。由于 MySQL Proxy 使用的是 MySQL网络协议,故其可以在不做任何修改的情况下,配合任何符合该协议的且与 MySQL 兼容的客户端一起使用。在最基本的配置下,MySQL Proxy 仅仅是简单地将自身置于服务器和客户端之间,负责将 query 从客户端传递到服务器,再将来自服务器的应答返回给相应的客户端。在高级配置下,MySQL Proxy 可以用来监视和改变客户端和服务器之间的通信。查询注入(query interception) 功能允许你按需要添加性能分析命令 (profiling) ,且可以通过 Lua 脚本语言对注入的命令进行脚本化控制。

     本文不讨论 MySQL Proxy 作为数据库代理在功能上和实践中的优劣,而是着重讲述其源码实现中的两个功能点:daemon 功能和 keepalive 功能。

      通过命令行启动 MySQL Proxy 时经常会用到如下两个配置项:--daemon 和 –keepalive 。在其相应的帮助命令中的解释为:

  • --daemon       Start in daemon-mode
  • --keepalive      try to restart the proxy if it crashed

      keepalive 功能从字面理解为提供保活功能, daemon 为守护进程。但 daemon 的功能究竟是如何定义的呢?

      APUE 上的定义如下: 守护进程也称 daemon 进程,是生存期较长的一种进程,它们常常在系统自举时启动,仅在系统关闭时才终止。因为它们没有控制终端,所以说它们是再后台运行的。 

【Daemon 功能实现】 

      首先,讲解下 daemon 实现的基本原则。事实上,编写守护进程程序时是存在一些基本规则的,目的是防止产生不需要的交互作用(比如与终端的交互)。规则如下:

  1. 调用 umask 将文件模式创建屏蔽字设置为 0 。原因:防止继承得来的文件模式创建屏蔽字会拒绝设置某些权限的情况。
  2. 调用 fork ,然后使父进程退出(exit)。原因:第一,令启动 daemon 进程的 shell 认为命令已经执行完毕;第二,令产生的子进程不是其所在进程组的组长。
  3. 调用 setsid 以创建一个新会话。原因:使调用进程,第一,成为新会话的首进程;第二,成为新进程组的组长进程;第三,没有控制终端(在基于System V 的系统中可以通过 fork 两次来达到防止取得控制终端的效果的,其不再需要下面的规则6)。
  4. 将当前工作目录更改为根目录。原因:防止出现不能 umount 的问题。
  5. 关闭不再需要的文件描述符。原因:令守护进程不再持有从父进程继承来的某些文件描述符。
  6. 某些守护进程打开 /dev/null 使其具有文件描述符0、1和2。原因:防止守护进程与终端设备相关联。

有了上面的原则,现在对照下 MySQL Proxy 中的代码: 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

/**

 * start the app in the background

 *

 * UNIX-version

 */

void chassis_unix_daemonize(void) {

#ifdef _WIN32

    g_assert_not_reached(); /* shouldn't be tried to be called on win32 */

#else

#ifdef SIGTTOU

    signal(SIGTTOU, SIG_IGN);

#endif

#ifdef SIGTTIN

    signal(SIGTTIN, SIG_IGN);

#endif

#ifdef SIGTSTP

    signal(SIGTSTP, SIG_IGN);

#endif

    if (fork() != 0) exit(0);

     

    if (setsid() == -1) exit(0);

 

    signal(SIGHUP, SIG_IGN);

 

    if (fork() != 0) exit(0);

     

    chdir("/");

     

    umask(0);

#endif

}

从上面的实现代码中,可以看出以下几点:

  • 代码执行的先后顺序有的是必须的(如setsid 之前的 fork),有的不是必须的(如 umask 放在最后执行)。
  • 实现中使用了两次 fork ,为 System V 中理念。
  • 在 setsid 和第二次 fork 之间插入了 signal 处理,用于对 SIGHUP 执行 SIG_IGN 处理。

      在上述 6 条 daemon 编程规则中没有提到 signal 处理的问题,那么针对 SIGHUP 的处理代表的是什么意思呢?还是参阅 APUE :

      如果终端接口检测到一个连接断开,则将此信号发送给与该终端相关的控制进程(会话首进程)。仅当终端的 CLOCAL 标志没有设置时,上述条件下才产生此信号。

      有别于由终端正常产生的信号(如中断、退出和挂起)-- 这些信号总是传递给前台进程组 --  SIGHUP 信号可以发送到位于后台运行的会话首进程。SIGHUP 信号的默认处理动作是终止当前进程。通常会使用该信号来通知守护进程,以重新读取它们的配置文件,因为守护进程不会有控制终端,而且通常决不会收到这种信号。

      从上面这段文字可以看出,这里增加了 signal 信号处理的原因是,在 setsid 和第二次 fork 之间,当前的子进程仍旧是会话首进程,有可能会在收到 SIGHUP 信号时终止,所以这里通过设置  SIG_IGN 进行忽略。

至此,一个 daemon-mode 的守护进程就启动了。

【Keepalive 功能实现】 

      下面讲解下 keepalive 功能的实现。简单的说,MySQL Proxy 的服务器编程模型为:1个 daemon 父进程 + 一个工作子进程(在其中可以再启动 n 个工作线程)。而 keepalive 的功能就是要求 daemon 进程在发现工作子进程被异常终结后,能够重新启动该子进程。

      首先讲下 daemon 进程中的实现代码,其主要实现的功能为:

  • fork 一个工作子进程,并通过 waitpid 阻塞方式获取子进程的退出状态信息,若子进程为正常退出,即 exit-code 为 0 时,则守护进程也正常退出;若信号导致子进程正常退出,则守护进程同样正常退出;若信号导致子进程异常退出,则在延时2s后,由daemon进程重新启动子进程;对于 SIGSTOP 信号按照系统默认处理;
  • 将发送给 daemon 进程的 SIGINT/SIGTERM/SIGHUP/SIGUSR1/SIGUSR2 信号通过信号处理函数 chassis_unix_signal_forward() 转发到 daemon 所在进程组中的所有进程(这里就是为了发送给子进程)。

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

/**

 * forward the signal to the process group, but not us

 */

static void chassis_unix_signal_forward(int sig) {

#ifdef _WIN32

    g_assert_not_reached(); /* shouldn't be tried to be called on win32 */

#else

    signal(sig, SIG_IGN); /* we don't want to create a loop here */

 

    kill(0, sig);

#endif

}

 

/**

 * keep the ourself alive

 *

 * if we or the child gets a SIGTERM, we quit too

 * on everything else we restart it

 */

int chassis_unix_proc_keepalive(int *child_exit_status) {

#ifdef _WIN32

    g_assert_not_reached(); /* shouldn't be tried to be called on win32 */

    return 0; /* for VC++, to silence a warning */

#else

    int nprocs = 0;

    pid_t child_pid = -1;

 

    /* we ignore SIGINT and SIGTERM and just let it be forwarded to the child instead

     * as we want to collect its PID before we shutdown too

     *

     * the child will have to set its own signal handlers for this

     */

 

    for (;;) {

        /* try to start the children */

        while (nprocs < 1) {

            pid_t pid = fork();

 

            if (pid == 0) {

                /* child */

                 

                g_debug("%s: we are the child: %d",

                        G_STRLOC,

                        getpid());

                return 0;

            } else if (pid < 0) {

                /* fork() failed */

 

                g_critical("%s: fork() failed: %s (%d)",

                    G_STRLOC,

                    g_strerror(errno),

                    errno);

 

                return -1;

            } else {

                /* we are the angel, let's see what the child did */

                g_message("%s: [angel] we try to keep PID=%d alive",

                        G_STRLOC,

                        pid);

 

                /* forward a few signals that are sent to us to the child instead */

                signal(SIGINT, chassis_unix_signal_forward);

                signal(SIGTERM, chassis_unix_signal_forward);

                signal(SIGHUP, chassis_unix_signal_forward);

                signal(SIGUSR1, chassis_unix_signal_forward);

                signal(SIGUSR2, chassis_unix_signal_forward);

 

                child_pid = pid;

                nprocs++;

            }

        }

 

        if (child_pid != -1) {

            struct rusage rusage;

            int exit_status;

            pid_t exit_pid;

 

            g_debug("%s: waiting for %d",

                    G_STRLOC,

                    child_pid);

#ifdef HAVE_WAIT4

            exit_pid = wait4(child_pid, &exit_status, 0, &rusage);

#else

            memset(&rusage, 0, sizeof(rusage)); /* make sure everything is zero'ed out */

            exit_pid = waitpid(child_pid, &exit_status, 0);

#endif

            g_debug("%s: %d returned: %d",

                    G_STRLOC,

                    child_pid,

                    exit_pid);

 

            if (exit_pid == child_pid) {

                /* our child returned, let's see how it went */

                if (WIFEXITED(exit_status)) {

                    g_message("%s: [angel] PID=%d exited normally with exit-code = %d (it used %ld kBytes max)",

                            G_STRLOC,

                            child_pid,

                            WEXITSTATUS(exit_status),

                            rusage.ru_maxrss / 1024);

                    if (child_exit_status) *child_exit_status = WEXITSTATUS(exit_status);

                    return 1;

                } else if (WIFSIGNALED(exit_status)) {

                    int time_towait = 2;

                    /* our child died on a signal

                     *

                     * log it and restart */

 

                    g_critical("%s: [angel] PID=%d died on signal=%d (it used %ld kBytes max) ... waiting 3min before restart",

                            G_STRLOC,

                            child_pid,

                            WTERMSIG(exit_status),

                            rusage.ru_maxrss / 1024);

 

                    /**

                     * to make sure we don't loop as fast as we can, sleep a bit between

                     * restarts

                     */

     

                    signal(SIGINT, SIG_DFL);

                    signal(SIGTERM, SIG_DFL);

                    signal(SIGHUP, SIG_DFL);

                    while (time_towait > 0) time_towait = sleep(time_towait);

 

                    nprocs--;

                    child_pid = -1;

                } else if (WIFSTOPPED(exit_status)) {

                } else {

                    g_assert_not_reached();

                }

            } else if (-1 == exit_pid) {

                /* EINTR is ok, all others bad */

                if (EINTR != errno) {

                    /* how can this happen ? */

                    g_critical("%s: wait4(%d, ...) failed: %s (%d)",

                        G_STRLOC,

                        child_pid,

                        g_strerror(errno),

                        errno);

 

                    return -1;

                }

            } else {

                g_assert_not_reached();

            }

        }

    }

#endif

}

        其次讲解工作子进程中的实现代码,其主要实现的功能为:

      通过 libevent 提供的接口设置对 SIGTERM/SIGINT/SIGHUP 三个信号的处理,通过 libevent 的信号处理方式可以做到,将I/O事件、Timer事件和信号事件统一按event-driven方式进行处理的目的,这样,一旦工作子进程检测到相应的信号,就会将控制变量signal_shutdown设置为1,进而令循环终止。

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

void chassis_set_shutdown_location(const gchar* location) {

    if (signal_shutdown == 0) g_message("Initiating shutdown, requested from %s", (location != NULL ? location : "signal handler"));

    signal_shutdown = 1;

}

 

gboolean chassis_is_shutdown() {

    return signal_shutdown == 1;

}

 

static void sigterm_handler(int G_GNUC_UNUSED fd, short G_GNUC_UNUSED event_type, void G_GNUC_UNUSED *_data) {

    chassis_set_shutdown_location(NULL);

}

 

static void sighup_handler(int G_GNUC_UNUSED fd, short G_GNUC_UNUSED event_type, void *_data) {

    chassis *chas = _data;

 

    g_message("received a SIGHUP, closing log file"); /* this should go into the old logfile */

 

    chassis_log_set_logrotate(chas->log);

     

    g_message("re-opened log file after SIGHUP"); /* ... and this into the new one */

}

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

int chassis_mainloop(void *_chas) {

    chassis *chas = _chas;

    guint i;

    struct event ev_sigterm, ev_sigint;

#ifdef SIGHUP

    struct event ev_sighup;

#endif

    chassis_event_thread_t *mainloop_thread;

 

    /* redirect logging from libevent to glib */

    event_set_log_callback(event_log_use_glib);

 

 

    /* add a event-handler for the "main" events */

    mainloop_thread = chassis_event_thread_new();

    chassis_event_threads_init_thread(chas->threads, mainloop_thread, chas);

    chassis_event_threads_add(chas->threads, mainloop_thread);

 

    chas->event_base = mainloop_thread->event_base; /* all global events go to the 1st thread */

 

    g_assert(chas->event_base);

 

 

    /* setup all plugins all plugins */

    for (i = 0; i < chas->modules->len; i++) {

        chassis_plugin *p = chas->modules->pdata[i];

 

        g_assert(p->apply_config);

        if (0 != p->apply_config(chas, p->config)) {

            g_critical("%s: applying config of plugin %s failed",

                    G_STRLOC, p->name);

            return -1;

        }

    }

 

    /*

     * drop root privileges if requested

     */

#ifndef _WIN32

    if (chas->user) {

        struct passwd *user_info;

        uid_t user_id= geteuid();

 

        /* Don't bother if we aren't superuser */

        if (user_id) {

            g_critical("can only use the --user switch if running as root");

            return -1;

        }

 

        if (NULL == (user_info = getpwnam(chas->user))) {

            g_critical("unknown user: %s", chas->user);

            return -1;

        }

 

        if (chas->log->log_filename) {

            /* chown logfile */

            if (-1 == chown(chas->log->log_filename, user_info->pw_uid, user_info->pw_gid)) {

                g_critical("%s.%d: chown(%s) failed: %s",

                            __FILE__, __LINE__,

                            chas->log->log_filename,

                            g_strerror(errno) );

 

                return -1;

            }

        }

 

        setgid(user_info->pw_gid);

        setuid(user_info->pw_uid);

        g_debug("now running as user: %s (%d/%d)",

                chas->user,

                user_info->pw_uid,

                user_info->pw_gid );

    }

#endif

 

    signal_set(&ev_sigterm, SIGTERM, sigterm_handler, NULL);

    event_base_set(chas->event_base, &ev_sigterm);

    signal_add(&ev_sigterm, NULL);

 

    signal_set(&ev_sigint, SIGINT, sigterm_handler, NULL);

    event_base_set(chas->event_base, &ev_sigint);

    signal_add(&ev_sigint, NULL);

 

#ifdef SIGHUP

    signal_set(&ev_sighup, SIGHUP, sighup_handler, chas);

    event_base_set(chas->event_base, &ev_sighup);

    if (signal_add(&ev_sighup, NULL)) {

        g_critical("%s: signal_add(SIGHUP) failed", G_STRLOC);

    }

#endif

 

    if (chas->event_thread_count < 1) chas->event_thread_count = 1;

 

    /* create the event-threads

     *

     * - dup the async-queue-ping-fds

     * - setup the events notification

     * */

    for (i = 1; i < (guint)chas->event_thread_count; i++) { /* we already have 1 event-thread running, the main-thread */

        chassis_event_thread_t *event_thread;

     

        event_thread = chassis_event_thread_new();

        chassis_event_threads_init_thread(chas->threads, event_thread, chas);

        chassis_event_threads_add(chas->threads, event_thread);

    }

 

    /* start the event threads */

    if (chas->event_thread_count > 1) {

        chassis_event_threads_start(chas->threads);

    }

 

    /**

     * handle signals and all basic events into the main-thread

     *

     * block until we are asked to shutdown

     */

    chassis_event_thread_loop(mainloop_thread);

 

    signal_del(&ev_sigterm);

    signal_del(&ev_sigint);

#ifdef SIGHUP

    signal_del(&ev_sighup);

#endif

    return 0;

}

【测试】 

经过了上述源码分析,下面进行一些实验对其进行检验。

1.启动带 keepalive 功能的 mysql-proxy。

?


1

2

3

4

5

[root@Betty data]# mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

[root@Betty ~]# ps ajx

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND

    1 16766 16765 16765 ?           -1 S        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

16766 16767 16765 16765 ?           -1 S        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

2.向 daemon进程发送 INT 信号。

?


1

[root@Betty ~]# kill -INT 16766

3. MySQL Proxy日志显示内容:

?


1

2

3

4

5

6

2013-03-19 19:31:38: (message) Initiating shutdown, requested from signal handler

2013-03-19 19:31:39: (message) shutting down normally, exit code is: 0

2013-03-19 19:31:39: (debug) chassis-unix-daemon.c:167: 16767 returned: 16767

2013-03-19 19:31:39: (message) chassis-unix-daemon.c:176: [angel] PID=16767 exited normally with exit-code = 0 (it used 1 kBytes max)

2013-03-19 19:31:39: (message) Initiating shutdown, requested from mysql-proxy-cli.c:606

2013-03-19 19:31:39: (message) shutting down normally, exit code is: 0

     可以看出,父子进程均退出。因为其信号处理函数会将全局变量 signal_shutdown 设置为 1,从而导致子进程退出 loop 循环,而处于 waitpid状态的父进程获得的子进程的退出状态为 child_exit_status = 0 ,进而令父进程也会正常退出执行。

 4.重复上述动作,但是改为向子进程发送 INT 信号。

?


1

2

3

4

5

6

[root@Betty ~]# ps ajx

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND

    1 16872 16871 16871 ?           -1 S        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

16872 16873 16871 16871 ?           -1 S        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

 

[root@Betty ~]# kill -INT 16873

日志内容如下,完全相同。

?


1

2

3

4

5

6

2013-03-19 20:03:49: (message) Initiating shutdown, requested from signal handler

2013-03-19 20:03:50: (message) shutting down normally, exit code is: 0

2013-03-19 20:03:50: (debug) chassis-unix-daemon.c:167: 16873 returned: 16873

2013-03-19 20:03:50: (message) chassis-unix-daemon.c:176: [angel] PID=16873 exited normally with exit-code = 0 (it used 1 kBytes max)

2013-03-19 20:03:50: (message) Initiating shutdown, requested from mysql-proxy-cli.c:606

2013-03-19 20:03:50: (message) shutting down normally, exit code is: 0

5. 同样的实验(对子进程和和父进程分别实验一次),只是将信号变为 -TERM ,结果和上面的完全相同(因为代码中对这两个信号的处理方式完全相同)。

 6. 同样的实验(对子进程和和父进程分别实验一次),只是将信号变为 -HUP ,结果如下:

?


1

2

2013-03-19 20:10:03: (message) received a SIGHUP, closing log file

2013-03-19 20:10:03: (message) re-opened log file after SIGHUP

     上述打印出现在子进程的 HUP 信号处理函数中。该函数仅对日志设置了 rotate_logs = true 标识,并没有设置 signal_shutdown = 1 ,所以子进程不会结束,父进程也不会结束。

 7. 同样的实验,将信号变为 -KILL ,向子进程发送:

?


1

2

3

4

5

6

7

[root@Betty ~]# ps ajx

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND

 

    1 16902 16901 16901 ?           -1 S        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

16902 16903 16901 16901 ?           -1 S        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

 

[root@Betty ~]# kill -KILL 16903

输出日志如下:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

2013-03-19 20:09:38: (debug) chassis-unix-daemon.c:121: we are the child: 16903

2013-03-19 20:09:38: (critical) plugin proxy 0.8.3 started

2013-03-19 20:09:38: (debug) max open file-descriptors = 1024

2013-03-19 20:09:38: (message) proxy listening on port 172.16.40.60:4040

2013-03-19 20:09:38: (message) added read/write backend: 172.16.40.60:12345

2013-03-19 20:09:38: (message) chassis-unix-daemon.c:136: [angel] we try to keep PID=16903 alive

2013-03-19 20:09:38: (debug) chassis-unix-daemon.c:157: waiting for 16903

...

...

2013-03-19 20:31:36: (debug) chassis-unix-daemon.c:167: 16903 returned: 16903

2013-03-19 20:31:36: (critical) chassis-unix-daemon.c:189: [angel] PID=16903 died on signal=9 (it used 1 kBytes max) ... waiting 3min before restart

2013-03-19 20:31:38: (debug) chassis-unix-daemon.c:121: we are the child: 16947

2013-03-19 20:31:38: (critical) plugin proxy 0.8.3 started

2013-03-19 20:31:38: (debug) max open file-descriptors = 1024

2013-03-19 20:31:38: (message) proxy listening on port 172.16.40.60:4040

2013-03-19 20:31:38: (message) added read/write backend: 172.16.40.60:12345

2013-03-19 20:31:38: (message) chassis-unix-daemon.c:136: [angel] we try to keep PID=16947 alive

2013-03-19 20:31:38: (debug) chassis-unix-daemon.c:157: waiting for 16947

     从日志和代码上都可以分析得出原因:由于 -KILL 信号是无法获取或者忽略的,所以当发送该信号给子进程后,子进程将被杀死,退出状态为 died on signal=9 ,此时父进程会执行 restart 子进程的操作。

此时重新查看进程信息:

?


1

2

3

4

[root@Betty ~]# ps ajx

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND

    1 16902 16901 16901 ?           -1 S        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

16902 16947 16901 16901 ?           -1 S        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

     若向父进程发送 -KILL 信号,那么父进程将被直接杀死,子进程被 init 收留,而 init 进程根本不会理会是否需要 keepalive 子进程的问题,所以此时再向子进程发送 -KILL ,子进程被杀死后,不会重新被启动。

8. 同样的实验,将信号变为-STOP,向子进程发送:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

[root@Betty ~]# ps ajx

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND

 

    1 16977 16976 16976 ?           -1 S        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

16977 16978 16976 16976 ?           -1 S        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

 

[root@Betty ~]# kill -STOP 16978

 

    1 16977 16976 16976 ?           -1 S        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

16977 16978 16976 16976 ?           -1 T        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

 

 

[root@Betty ~]# kill -CONT 16978

 

    1 16977 16976 16976 ?           -1 S        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

16977 16978 16976 16976 ?           -1 S        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

 

[root@Betty ~]# kill -STOP 16977

 

    1 16977 16976 16976 ?           -1 T        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

16977 16978 16976 16976 ?           -1 S        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

 

[root@Betty ~]# kill -CONT 16977

 

    1 16977 16976 16976 ?           -1 S        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

16977 16978 16976 16976 ?           -1 S        0   0:00 mysql-proxy --defaults-file=/etc/mysql-proxy.cnf

     出现上述结果的原因,是信号 -STOP 同样不可捕获和忽略,而进程对该信号的默认处理方式为暂停进程(可以从进程状态标志看出来)。同时在代码中,父进程在获得子进程状态处于暂停时,没有做任何特别处理,只是重新调用 waitpid 继续获取子进程的状态而已。

【总结】
      daemon 功能和 keepalive 功能属于服务器程序开发过程中经常要面对到的问题,本文提供了上述功能的一种实现方式。通过学习开源代码,可以有机会接触到一些经典的处理问题的方法,通过对一些问题的深入了解,能够进一步完善自身的知识体系,强化对一些知识的理解。最后引用一位大师的名言:源码面前,了无秘密。祝玩的开心!

====================================

再贴两个 daemonize 的实现进行对比(取自 memcached-1.4.14):

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

int daemonize(int nochdir, int noclose)

{

    int fd;

 

    switch (fork()) {

    case -1:

        return (-1);

    case 0:

        break;

    default:

        _exit(EXIT_SUCCESS);

    }

 

    if (setsid() == -1)

        return (-1);

 

    if (nochdir == 0) {

        if(chdir("/") != 0) {

            perror("chdir");

            return (-1);

        }

    }

 

    if (noclose == 0 && (fd = open("/dev/null", O_RDWR, 0)) != -1) {

        if(dup2(fd, STDIN_FILENO) < 0) {

            perror("dup2 stdin");

            return (-1);

        }

        if(dup2(fd, STDOUT_FILENO) < 0) {

            perror("dup2 stdout");

            return (-1);

        }

        if(dup2(fd, STDERR_FILENO) < 0) {

            perror("dup2 stderr");

            return (-1);

        }

 

        if (fd > STDERR_FILENO) {

            if(close(fd) < 0) {

                perror("close");

                return (-1);

            }

        }

    }

    return (0);

}

(下面代码取自 Twemproxy) 

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

static rstatus_t

nc_daemonize(int dump_core)

{

    rstatus_t status;

    pid_t pid, sid;

    int fd;

 

    pid = fork();

    switch (pid) {

    case -1:

        log_error("fork() failed: %s", strerror(errno));

        return NC_ERROR;

 

    case 0:

        break;

 

    default:

        /* parent terminates */

        _exit(0);

    }

 

    /* 1st child continues and becomes the session leader */

 

    sid = setsid();

    if (sid < 0) {

        log_error("setsid() failed: %s", strerror(errno));

        return NC_ERROR;

    }

 

    if (signal(SIGHUP, SIG_IGN) == SIG_ERR) {

        log_error("signal(SIGHUP, SIG_IGN) failed: %s", strerror(errno));

        return NC_ERROR;

    }

 

    pid = fork();

    switch (pid) {

    case -1:

        log_error("fork() failed: %s", strerror(errno));

        return NC_ERROR;

 

    case 0:

        break;

 

    default:

        /* 1st child terminates */

        _exit(0);

    }

 

    /* 2nd child continues */

 

    /* change working directory */

    if (dump_core == 0) {

        status = chdir("/");

        if (status < 0) {

            log_error("chdir(\"/\") failed: %s", strerror(errno));

            return NC_ERROR;

        }

    }

 

    /* clear file mode creation mask */

    umask(0);

 

    /* redirect stdin, stdout and stderr to "/dev/null" */

 

    fd = open("/dev/null", O_RDWR);

    if (fd < 0) {

        log_error("open(\"/dev/null\") failed: %s", strerror(errno));

        return NC_ERROR;

    }

 

    status = dup2(fd, STDIN_FILENO);

    if (status < 0) {

        log_error("dup2(%d, STDIN) failed: %s", fd, strerror(errno));

        close(fd);

        return NC_ERROR;

    }

 

    status = dup2(fd, STDOUT_FILENO);

    if (status < 0) {

        log_error("dup2(%d, STDOUT) failed: %s", fd, strerror(errno));

        close(fd);

        return NC_ERROR;

    }

 

    status = dup2(fd, STDERR_FILENO);

    if (status < 0) {

        log_error("dup2(%d, STDERR) failed: %s", fd, strerror(errno));

        close(fd);

        return NC_ERROR;

    }

 

    if (fd > STDERR_FILENO) {

        status = close(fd);

        if (status < 0) {

            log_error("close(%d) failed: %s", fd, strerror(errno));

            return NC_ERROR;

        }

    }

 

    return NC_OK;

}

时间: 2025-01-02 17:48:44

【原创】服务器开发之 Daemon 和 Keepalive的相关文章

Linux服务器开发之:stat(),fstat(),lstat()详细介绍+案例演示

 1.依赖的头文件 #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> 2.函数定义: //通过传入文件路径,struct stat结构体指针的方式 int stat(const char *path, struct stat *buf); //通过文件描述符获取文件对应的属性.文件打开后这样操作 int fstat(int fd, struct stat *buf); //通过文件描述符

Linux服务器开发之:chmod()函数,chmod命令,以及文件屏蔽umask命令,程序修改umask,详细介绍+案例演示

 1.依赖的头文件 #include<sys/stat.h> 2.函数定义: //通过传入path中给定的文件名的方式来改变文件制定的权限 int chmod(const char *path,mode_t mode); //通过传入文件描述符的方式为一个文件重设权限 int fchmod(int fd,mode_t mode); 注意:如果使用Linux的chmod命令时,得有root权限 3.关于mode_t的定义:   A:mode_t的定义实际就是unsigned int 形式的

基于xmpp openfire smack开发之Android客户端开发[3]

在上两篇文章中,我们依次介绍openfire部署以及smack常用API的使用,这一节中我们着力介绍如何基于asmack开发一个Android的客户端,本篇的重点在实践,讲解和原理环节,大家可以参考前两篇的文章 基于xmpp openfire smack开发之openfire介绍和部署[1] 基于xmpp openfire smack开发之smack类库介绍和使用[2]   1.源码结构介绍 activity包下存放一些android页面交互相关的控制程序,还有一个些公共帮助类 db包为sqli

基于xmpp openfire smack开发之Android消息推送技术原理分析和实践[4]

前面几篇给大家系统讲解的有关xmpp openfire smack asmack相关的技术和使用,大家如果有所遗忘可以参考 基于xmpp openfire smack开发之openfire介绍和部署[1] 基于xmpp openfire smack开发之smack类库介绍和使用[2] 基于xmpp openfire smack开发之Android客户端开发[3]   顺便也一起回顾下xmpp的历程 xmpp协议起源于著名的Linux即时通讯服务服务器jabber,有时候我们会把xmpp协议也叫j

php开发之cookie

cookie是一种在浏览器远端存储数据并以此来跟踪和识别用户的机制.简单的说,cookie是web服务器暂时存储在用户硬盘上的一个文件夹,并随时被web浏览器读取.当用户再次访问web网站的时候,网站通过获取cookie记录用户的特定访问信息(如:上次访问的位置,花费的时间,用户名和密码) ,从而迅速做出相应,比如不需要用户输入密码就可以登录. 文本文件的格式如下:用户名@网站地址[数字].txtcookie 的功能主要有以下几个方面: 1,记录访客的某些信息.如可以利用cookie记录用户的访

ASP.NET移动开发之SelectionList控件

asp.net|select|控件 正如前面提及的那样,SelectionList控件适用于呈现较短列表的数据项.尽管它不具备对长列表的分页显示功能,但是它的呈现形式是丰富多样的.只要设备浏览器支持,SelectionList控件可以以下拉列表.单项按钮.多选按钮和复选框等众多形式存在. SelectionList控件的列表中只有一个可视的数据项,其它的数据项只能以隐藏值的形式与可视的数据项进行关联.要在服务器控件语法中指定隐藏值,可以在<Item>元素中使用Value属性,并且将Value属

ArcGIS Engine开发之旅05---空间数据库

原文:ArcGIS Engine开发之旅05---空间数据库 1  Geodatabase概念 Geodatabase是ArcInfo8引入的一种全新的面向对象的空间数据模型,是建立在DBMS之上的统一的.智能的空间数据模型."统一"是指,Geodatabase之前的多个空间数据模型都不能在一个统一的模型框架下对地理空间要素信息进行统一的描述,而Geodatabase做到了这一点:"智能化"是指,在Geodatabase模型中,对空间要素的描述和表达较之前的空间数据

Android安全开发之ZIP文件目录遍历

Android安全开发之ZIP文件目录遍历 作者:伊樵.呆狐.舟海@阿里聚安全 1.ZIP文件目录遍历简介 因为ZIP压缩包文件中允许存在"../"的字符串,攻击者可以利用多个"../"在解压时改变ZIP包中某个文件的存放位置,覆盖掉应用原有的文件.如果被覆盖掉的文件是动态链接so.dex或者odex文件,轻则产生本地拒绝服务漏洞,影响应用的可用性,重则可能造成任意代码执行漏洞,危害用户的设备安全和信息安全.比如近段时间发现的"寄生兽"漏洞.海豚

ArcGIS Engine开发之旅02--ArcGIS Engine中的类库

原文:ArcGIS Engine开发之旅02--ArcGIS Engine中的类库 System类库 System类库是ArcGIS体系结构中最底层的类库.System类库包含给构成ArcGIS的其他类库提供服务的组件.System类库中定义了大量开发者可以实现的接口.AoInitializer对象就是在System类库中定义的,所有的开发者必须使用这个对象来初始化ArcGISEngine和解除ArcGIS Engine的初始化.开发者不能扩展这个类库,但可以通过实现这个类库中包含的接口来扩展A