一只简单的网络爬虫(基于linux C/C++)————守护进程
守护进程,也就是通常说的Daemon进程,是Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程常常在系统引导装入时启动,在系统关闭时终止。Linux系统有很多守护进程,大多数服务都是通过守护进程实现的,同时,守护进程还能完成许多系统任务,例如,作业规划进程crond、打印进程lqd等(这里的结尾字母d就是Daemon的意思)。
由于在Linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。但是守护进程却能够突破这种限制,它从被执行开始运转,直到整个系统关闭时才退出。如果想让某个进程不因为用户或终端或其他地变化而受到影响,那么就必须把这个进程变成一个守护进程。
使用setsid函数可以设置为守护进程
其原型如下:
#include <unistd.h>
pid_t setsid(void);
当进程是会话的领头进程时setsid()调用失败并返回(-1)。setsid()调用成功后,返回新的会话的ID,调用setsid函数的进程成为新的会话的领头进程,并与其父进程的会话组和进程组脱离。由于会话对控制终端的独占性,进程同时与控制终端脱离。
pid_t pid = fork(); //fork a process
if (pid < 0) exit(0); //fork error
if (pid > 0) exit(0); //father process exit
setsid(); //creat a new session for a process
//之前parent和child运行在同一个session里,parent是会话(session)的领头进程,
//parent进程作为会话的领头进程,如果exit结束执行的话,那么子进程会成为孤儿进程,并被init收养。
//执行setsid()之后,child将重新获得一个新的会话(session)id。
//这时parent退出之后,将不会影响到child了。
在爬虫中的守护进程设计可参考下面:
//设置守护进程
static void daemonize()
{
int fd;
if (fork() != 0) exit(0);
//setsid()调用成功后,返回新的会话的ID,
//调用setsid函数的进程成为新的会话的领头进程,
//并与其父进程的会话组和进程组脱离。由于会话对控制终端的独占性,
//进程同时与控制终端脱离
setsid();
SPIDER_LOG(SPIDER_LEVEL_INFO, "Daemonized...pid=%d", (int)getpid());
//是空设备,也称为位桶(bit bucket),任何写入它的输出都会被抛弃。
//如果不想让消息以标准输出显示或写入文件,那么可以将消息重定向到位桶。
if ((fd = open("/dev/null", O_RDWR, 0)) != -1)
{//复制/dev/null文件描述符到几个标准输入输出(守护进程所有消息都不应该在终端输出,这里先重定向到null舍弃)
//int dup2(int odlfd,int newfd);
dup2(fd, STDIN_FILENO);//标准输入0
dup2(fd, STDOUT_FILENO);//标准输出1
dup2(fd, STDERR_FILENO);//标准错误2
if (fd > STDERR_FILENO)
close(fd);
}
//将日志文件的描述符重定向到标准输出,即让输出都写到日志文件(每次添加到末尾,如果文件不存在就创建)
if (g_conf->logfile != NULL && (fd = open(g_conf->logfile, O_RDWR | O_APPEND | O_CREAT, 0)) != -1)
{
dup2(fd, STDOUT_FILENO);//复制文件描述符到标准输出,标准输出的东西会写入文件
if (fd > STDERR_FILENO)
close(fd);
}
}
其中if (fork() != 0) exit(0);
和前面
if (pid < 0) exit(0); //fork error
if (pid > 0) exit(0); //father process exit
达到同样的效果,pid<0是fork进程出错,pid>0是父进程。
为了能够让日志文件输出到文件,需要将日志文件的描述符重定向到标准输出。
另外需注意,这里使用的是open而不是fopen。
fopen是用来打开文件的,返回值是FILE*类型
open还可以用来打开设备,返回值是int的文件描述符,如果这个值等于-1,说明打开文件出现错误,如果为大于0的值,那么这个值代表的就是文件描述符。一般的写法是
if((fd=open("/dev/ttys0",O_RDWR | O_NOCTTY | O_NDELAY)<0){
perror("open");
}
这个事常用的一种用法fd是设备描述符,linux在操作硬件设备时,屏蔽了硬件的基本细节,只把硬件当做文件来进行操作,而所有的操作都是以open函数来开始,它用来获取fd,然后后期的其他操作全部控制fd来完成对硬件设备的实际操作。你要打开的/dev/ttyS0,代表的是串口1,也就是常说的com1,后面跟的是一些控制字。int open(const char pathname, int oflag, …/, mode_t mode * / ) ;这个就是open函数的公式。控制字可以有多种,大致如下:
O_RDONLY 只读打开。
O_WRONLY 只写打开。
O_RDWR 读、写打开。
O_APPEND 每次写时都加到文件的尾端。
O_CREAT 若此文件不存在则创建它。使用此选择项时,需同时说明第三个参数mode,用其说明该新文件的存取许可权位。
O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则出错。这可测试一个文件是否存在,如果不存在则创建此文件成为一个原子操作。
O_TRUNC 如果此文件存在,而且为只读或只写成功打开,则将其长度截短为0。
O_NOCTTY 如果p a t h n a m e指的是终端设备,则不将此设备分配作为此进程的控制终端。
O_NONBLOCK 如果p a t h n a m e指的是一个F I F O、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I / O操作设置非阻塞方式。
O_SYNC 使每次w r i t e都等到物理I / O操作完成。
这些控制字都是通过“或”符号分开(|)
当调用系统调用open时,操作系统会将文件系统对应设备文件的inode中的file_operations安装进用户进程的task_struct中的file_struct,然后再调用具体文件的file_operations中的open函数,其他的read、write等等也是如此,所以实际上open操作实际上是一个连接过程。
/dev/null和/dev/zero:
/dev/null——它是空设备,也称为位桶(bit bucket)。任何写入它的输出都会被抛弃。如果不想让消息以标准输出显示或写入文件,那么可以将消息重定向到位桶。
/dev/zero——该设备无穷尽地提供0,可以使用任何你需要的数目——设备提供的要多的多。他可以用于向设备或文件写入字符串0(因此可用它来初始化文件)。