第九章 高级套接字函数编程
·9.1 发送和接收函数的高级用法
头文件:<sys/types.h>, <sys/socket.h>
int send(int sockfd, void *buf, int len, int flags);
[flags=MSG_OOB, MSG_DONTWAIT, MSG_DONTROUTE]
int recv(int sockfd, void *buf, int len, int flags);
[flags=MSG_OOB, MSG_PEEK, MSG_WAITALL, MSG_DONTROUTE]
头文件:<sys/uio.h>
int readv(int fd, struct iovec *iov, int iovlen); //将套接字缓冲区数据读到多个应用缓冲区中
int writev(int fd, struct iovec *iov, int iovlen); //将多个应用缓冲区写到套接字缓冲区数据中
struct iovec{
void *iov_base; .//指向应用缓冲区结构体的数组
size_t iov_len; //缓冲区的个数
};
头文件:<sys/types.h>, <sys/socket.h>
int recvmsg(int sockfd, struct msghdr *msg, int flag); //常用在UNIX域套接字中对
int sendmsg(int sockfd, struct msghdr *msg, int flag); //进程间发送/接收文件描述符使用
struct msghdr{
void *msg_name; //发送端的地址信息
int msg_namelen; //
struct iovec *msg_iov; //缓冲区结构体指针
int msg_iovlen; //缓冲区个数
void *msg_control; //控制信息
int msg_controllen; //控制信息长度
int msg_flags;
};
使用这两个函数发送附加消息时候通常需要定义以下结构体:
union{
struct cmsghdr cm;
char control[CMSG_SPACE(sizeof(附加段长度))];
};
操作宏,头文件: <sys/socket.h>, <sys/param.h>
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msghdrptr);//指向第一个cmsghdr结构指针
struct cmsghdr *CMSG_NEXTHDR(struct msghdr *msghdrptr, struct cmsghdr *cmsgptr);
unsigned char *CMSG_DATA(struct cmsghdr *cmsghdr); //返回指向cmsghdr结构第一个字节
unsigned CMSG_LEN(unsigned int lenght); //获取cmsghdr中存放数据字节数
第十章 守护进程和超级服务器inetd
·10.1 守护进程的原理
只要系统没有关机或者崩溃,守护进程将在系统中不间断运行。关键是如何把守护进程的运行环境和其他进程的运行环境隔离。步骤:
1 第一次fork和setsid函数调用建立新会话组
这个操作的主要目的是为了让一个进程和控制终端脱离,这样来自终端的信号就不会影响到守护进程。setsid()函数的功能是让创建一个新的会话过程,并让调用setsid()的进程成为该会话过程的领头进程。但是setsid()的调用条件是这个进程不是一个进程组的主进程。因此我们先fork()一个子进程并终止该进程组主进程的运行,在子进程中调用setsid()让子进程成为不带控制终端的新会话过程的领头进程。于是这个进程就和原控制终端脱离,并成为新会话组的领导进程。
2 第二次fork和setpgrp函数调用建立新进程组
调用setsid()之后,虽然在新的会话组中的领导进程不带控制终端。但是如果这个进程打开了一个终端,那么整个会话组将又重新的控制终端。因此我们需要fork()一个新的子进程继续运行,并终止该会话组领头进程的运行。此时这个新进程不是会话组领导进程,所以即使打开一个终端也不会成为整个会话组的控制终端。
但由于父进程是会话组的领头进程,如果让父进程退出而子进程继续运行,那么将发送SIGHUP(中断和挂起信号)给这个会话组中所有进程。因此我们要先忽略信号SIGHUP,然后再fork,接着退出父进程而子进程继续运行。
但是这个新进程仍然和已退出父进程同在一个进程组中,仍然会受到同个进程组中的信号影响。所以我们要使用setpgrp()让这个进程成为新的进程组的领导者。
3 关闭所有文件描述符
fork()时候子进程将继承父进程的文件描述符。我们需要在守护进程中关闭先前打开的文件描述符,以免对其他进程造成影响。使用sysconf(_SC_OPEN_MAX)函数获取系统中每个进程可以打开文件的最大数目,然后使用close()关闭它们。
4 消除umask的影响
每个进程都有一个umask同它关联。umask指定进程创建文件的保护掩码,是系统提供的一种安全机制,限制进程创建文件的权限。比如进程创建一个文件的权限为0777,进程umask为0277,那么实际文件权限为0777-0277=0500。此时如果其他进程去读写守护进程创建的文件可能会受到文件掩码的影响。使用umask(0)系统调用清除旧的文件掩码。
5 改变守护进程的当前目录
每个进程都有一个当前目录,当进程产生错误时可以将错误信息记录在当前目录的core文件中供以后分析错误使用。如果不把守护进程修改到一个安全的目录,那么守护进程的当前目录是不确定的,并可能影响到其他系统的管理工作。我们可以使用chdir(“/”);将守护进程当前目录修改到根目录下。
6 对标准I/O描述符重定向
由于守护进程不连接任何控制终端,并且所有文件描述符都关闭。那么如果意外调用printf, perror等输出将导致出错。我们把标准I/O重定向到无伤害设备(harmless device)描述符上,那么对标准I/O的操作都将忽略避免人为意外使用出错。
7 使用syslog记录守护进程的错误
syslogd本身就是一个守护进程,使用UDP 514端口。应用程序可以发送UDP报文记录错误信息。void syslog(int priority, const char *message, ...); //#incluse <syslog.h>
8 文件锁和控制守护进程副本互斥运行
为了避免运行多个相同的守护进程产生的干扰,因此使用锁文件的方式记录守护进程的工作情况。linux系统的锁采用咨询锁,只是系统将告知文件锁定情况而不阻止进程对文件的操作。我们可以使用int flock(int fd, int operation); //#include <sys/file.h>
[operation = LOCK_SH共享锁, LOCK_EX互斥锁,LOCK_UN解锁,LOCK_NB进程获取锁失败不堵塞,缺省为进程堵塞]
示例代码:
int init_daemon(const char *pathname, int facility);
{
struct sigaction act;
int max_fd, i, ret;
int lock_fd;
char buf[10];
//打开一个文件锁
lock_fd = open(LOCKFILE, O_RDWR | O_CREAT, 0640);
if (lock_fd < 0)
error_proc();
//加锁
ret = flock(lock_fd, LOCK_EX LOCK_NB);
if (ret < 0)
error_proc();
//第一次fork
ret = fork();
if (ret < 0)
error_proc();
else if (ret != 0)
exit(0); //关闭进程组的主进程,子进程继续运行
ret = setsid(); //这个子进程成为新会话组的领导进程
if (ret < 0)
error_proc();
//忽略SIG_IGN
act.sa_handler = SIG_IGN;
sigemptyset(&act.sa_mask);
act.sa_flag = 0;
sigaction(SIGHUP, &act, NULL);
//第二次fork
ret = fork();
if (ret < 0)
error_proc();
else
exit(0); //关闭进程组的主进程,子进程继续运行
chdir(“/”); //改变守护进程的当前目录
umask(0); //清除旧的文件掩码
setpgrp(); //让这个进程成为新进程组的领导进程
sprintf(buf, “%6d/n”, getpid()); //获取守护进程的ID号,保存进buf数组中
write(lock_fd, buf, strlen(buf)); //把守护进程ID号保存进锁文件中
max_fd = sysconf(_SC_OPEN_MAX); //获取进程最大打开的描述符
for (i = 0; i < max_fd; ++i)
close(i); //逐个关闭
open(“dev/null”, O_RDWR); //打开 无伤害设备
dup(1); //
dup(2); //标准I/O描述符重定向
openlog(pathname, LOG_PID,facility); //打开syslogd记录文件
return 0; //守护进程标准创建过程结束
}
·10.2 超级服务器inetd的工作原理
由于服务器套接字初始化方式非常类似,所以可以设计一个专门的服务器负责初始化工作,并且它将根据接入端口不同调用相应的服务程序进行工作,这些服务程序在未被接入前都处于睡眠等待状态。采用超级服务器的方式可以让服务器程序采用统一方式管理。
超级服务器将采用select的方式并发检测在文件/etc/inetd.conf中说明的TCP/UDP端口,一旦发现有客户接入就创建一个子进程。超级服务器inetd是服务接入者,它在创建字进程时候调用exec()载入具体的服务程序。在子进程中关闭倾听套接字,父进程中关闭连接套接字,于是父进程继续检测,子进程开始为客户端进行服务。对于wait服务程序,超级服务器inet载入它时候将其在检测集合中删除,等待该服务结束后才能接入下次服务。服务程序完毕后将发送SIGCHLD信号,超级服务器将其继续加入检测集合。当系统管理员修改超级服务器配置文件后将发送SIGHUP信号,超级服务器将重新初始化。