今天做項目,借鑑nginx的代碼,做了一個優雅的重啓,在重啓過程中不斷服務,保持系統可用性。具體的過程如下
1. 註冊信號量
程序在運行過程中,當kill -s SIGUSR1 pid的shell語句執行時,程序接收到該信號,執行sig_reload函數, 將全局變量reload = 1
static void sig_reload(int signo)
{
reload = 1;
}
struct sigaction sa_reload;
sa_reload.sa_handler = sig_reload;
sigaction(SIGUSR1, &sa_reload, NULL);
2. 主程序邏輯
我的主進程在while的循環反覆執行, 當reload = 1 的時候,先將reload置回0,以免重複調用reload,接着執行CoreExecNewBin的函數, 這個函數接下去會講。它的主要功能是創建子進程,父進程及時退出,子進程執行新的bin文件。
父進程在退出時,使stop= 1 從而使得父進程stop,從而執行停止的邏輯,釋放資源。
while (!stop)
{
sleep(10);
int ret;
if (reload == 1)
{
reload = 0;
log_debug("reloading");
ret = CoreExecNewBin();
if (ret != 0)
{
log_error("fail in reloading");
continue;
}
log_debug("reload success");
stop = 1;
}
}
3.CoreExecNewBin函數
這裏關鍵的CoreExecNewBin函數來了
它先fork進程,根據pid判斷父子進程,父進程直接退出, 子進程獲取全局變量g_szenv,然後複製給envp,envp作爲執行新的bin文件的環境變量
static int CoreExecNewBin()
{
char *fds = NULL;
int pid = fork();
switch (pid) {
case -1:
log_error("fork in CoreExecNewBin got error");
return -1;
case 0: /* child */
break;
default: /* parent */
return 0;
}
char *envp[] = { NULL, NULL };
envp[0] = g_szenv;
log_error("fork env %s", g_szenv);
execve(g_argv[0], g_argv , envp);
}
如上可能,實現了功能,可是有兩個疑問,
1.如果父進程綁定了一個端口,那麼這個端口已經被佔用,子進程綁定同樣的端口時fail,怎麼辦?
2.如上的環境變量起到什麼作用。
那麼就扯到環境變量的問題上了,本文的環境變量記錄了被佔用的fd,從fd可以獲取端口號,從而做下述邏輯
我在綁定端口的時候,有4步要走:
1. 從環境變量讀取當前的環境變量,下面是讀取的代碼,
首先用getenv獲取得到char類型的環境變量,如果爲null,則表示沒有端口被佔用,那麼返回0.
然後解析環境變量的值,從它獲得綁定的fd
UnresolveDesc通過得到的fd,獲得與其綁定的addr,如果這個addr與現在想要綁定的addr一樣,則dup一個fd1,返回fd1.
char *p, *q;
int sock;
char *inherited;
inherited = getenv(NC_ENV_FDS);
//log_debug("sock addr %s", UnresolveDesc(sock));
char address[NI_MAXHOST + NI_MAXSERV];
if (inherited == NULL) {
return 0;
}
strncpy(address, listen_address, sizeof(address));
log_debug("address %s", address);
for (p = inherited, q = inherited; *p; p++) {
if (*p == ';') {
sock = cpa_atoi(q, p - q);
//log_debug("listen_address %s", UnresolveDesc(sock));
if (strcmp(address, UnresolveDesc(sock)) == 0) {
log_debug("get inherited socket %d for '%s' from '%s'", sock,
address, inherited);
sock = dup(sock);
log_debug("dup inherited socket as %d", sock);
return sock;
}
q = p + 1;
}
}
return 0;
char *UnresolveDesc(int sd)
{
struct sockaddr *addr;
socklen_t addrlen;
int status;
addr = (struct sockaddr *)malloc(sizeof(struct sockaddr));
status = getsockname(sd, addr, &addrlen);
if (status < 0) {
return "unknown";
}
return UnresolveAddr(addr, addrlen);
}
2. 判斷通過環境變量獲得的fd值,如果fd<= 0 , 新綁定一個fd2,否則fd值作爲現在fd使用
if (fd <= 0)
{
if ((fd = SockBind(&m_Addr, blog, 0, 0, 1/*reuse*/, 1/*nodelay*/, 1/*defer_accept*/)) == -1)
{
return -1;
}
}
3. 通過環境變量,關閉已經打開的fd,從而讓父進程的fd關閉
int CleanSocketEnv()
{
int sock = 0;
char *inherited;
char *p, *q;
inherited = getenv(NC_ENV_FDS);
if (inherited == NULL) {
return 0 ;
}
for (p = inherited, q = inherited; *p; p++) {
if (*p == ';') {
sock = cpa_atoi(q, p - q);
close(sock);
q = p + 1;
}
}
return 0;
}
4. 將現在佔用的fd寫入全局變量,作爲下次的reload的環境變量
int len = 0;
memcpy(g_szenv, NC_ENV_FDS, strlen(NC_ENV_FDS));
len = strlen(NC_ENV_FDS);
memcpy(g_szenv + len, "=", strlen("="));
len += strlen("=");
sprintf(g_szenv + len, "%d;", fd);
log_debug("global env is %s", g_szenv);
對了比較重要的一點是: 在accept 新的fd的時候要設置,不然在子進程會繼承已經連接的fd,從而導致殭屍連接
int status = fcntl(newfd, F_SETFD, FD_CLOEXEC);