守護進程
什麼是守護進程?《UNIX環境高級編程》指出屬於生存期長的一種進程,系統級別就有許多進程進行內核級別操作或者交互操作需要設置爲守護進程,暫且不論。
如果我們意圖使服務器一直運行,則需要我們的守護進程
客戶進程-服務器進程模型,在服務器模型中,fork然後exec一個程序來向客戶服務是常見的行爲,這樣我們的守護進程可以讓守護進程的子進程打開文件描述符(fork時會繼承父進程(守護進程)的文件描述符)。
ps -axj命令可以打印到標準輸出:
用戶ID,進程ID,符進程ID,回話ID,終端名稱,命令字符串
由於守護進程不受終端控制,故顯示?
守護進程有一套編寫規則,網上枚舉很多,我結合書中和代碼總結一下:
A:umask(0),意圖給予守護進程高權限,不被權限影響,重設文件權限掩碼
B:fork(),並關閉父進程
C:setsid創建新會話
D:爲了安全,需要把目錄切換到/或者root認爲安全(不被刪除的目錄下) chdir("/")
E:關閉不需要的文件描述符
多線程中推薦阻塞所有信號,然後創建一個線程處理信號,一般守護進程也可以SIG_IGN來標誌SIGHUP,SIGCHID等信號
進程組:每個進程屬於一個進程組,進程組組長PID號爲進程組號
會話期:sesion是一個進程組或者多個進程組的集合,setsid()可以建立新的會話期,setsid
使我們的“子進程”成爲會話組長和進程組長,和原有的登陸會話和進程組脫離,故和終端脫離開來
許多書中介紹的signal(SIGCHID,SIF_IGN)調用不是必須的,但是這一步是爲了防止守護進程的子進程成爲殭屍進程
由於關閉了0,1,2等文件描述符,其出錯記錄交由BSD syslog 進行打log記錄
服務器
下附帶一個簡單返回頁面的http服務器,結合代碼理解守護進程
#include <signal.h>
#include<sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include<iostream>
using namespace std;
int main_ (const char* argv1,const char* argv2 )
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket error");
return 1;}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv1 );
addr.sin_port = htons(atoi(argv2));
int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
if (ret < 0) {
perror("bind error ");
return 1;
}
ret = listen(fd, 10);
if (ret < 0) {
perror("listen error ");
return 1;
}
for (;;)
{
cout<<"http server open...\n"<<endl;
cout<<"下面會打印首行(first_line,頭部(header),空格(\r\n),正文(body))"<<endl;
struct sockaddr_in client_addr;
socklen_t len;
int client_fd = accept(fd, (struct sockaddr*)&client_addr, &len);
if (client_fd < 0) {
perror("accept");
continue;
}
char input_buf[1024 * 10] = {0}; // 用一個足夠大的緩衝區直接把數據讀完.
ssize_t read_size = read(client_fd, input_buf, sizeof(input_buf) - 1);
if (read_size < 0) {
return 1;
}
printf("[Request-------------->] %s", input_buf);
char buf[1024] = {0};
const char* hello ="<a href=\"https://github.com/Chinaliuxin\" title=\"open_to liuxin's github\"target=\"_blank\">click the mouse to github</a><a></a>";
sprintf(buf, "HTTP/1.1 200 OK\nContent-length:%lu\n\n%s", strlen(hello), hello);
write(client_fd,buf,strlen(buf));
//same to send(client_fd, buf, strlen(buf),0);
}return 0;
}
int main(int argc,char**argv)
{
if(argc!=3){cout<<"input ip and port"<<endl;return 0;}
pid_t pc=fork();
if(pc<0)
{
perror("fork error \n");
exit(1);
}
if(pc>0){exit(0);}
umask(0);//權限
setsid();//新的進程組,會話組
chdir("/");// 切換到“安全的”目錄
for(size_t i=0;i<65535;i++)
close(i);
/*當然也可以只關閉0,1,2,如果我們的父進程沒有打開其他文件描述符
signal(SIGCHLD,SIG_IGN);
main_(argv[1],argv[2]);
return 0;
}
netstat -anp | grep 9999查看我們的端口綁定,發現在監聽中
用瀏覽器訪問綁定的socket,訪問我們的http服務器,瀏覽成功
由於我們的服務器成爲守護進程,關閉標準輸出,不會打印程序中瀏覽器返回的http包
關閉終端,用另外終端登陸,發現我們的服務器依然可以跑起來
殺死我們的服務器進程,可以選擇ps查找,然後使用9號信號來終止進程,就不發圖了
如果我們使用./運行的方式(把服務器當做一個普通進程)去使用
使用同一份代碼,但是去掉守護進程的部分,代碼就以鏈接形式發送,無關緊要簡單的返回HTML的http服務器
這個代碼gcc可以編譯,g++也可以。
運行,可以看到瀏覽器返回連接方式爲GET和瀏覽器相關信息,和wirshark抓包看到的一樣