源码部分借鉴自游双的《Linux高性能服务器编程》
完整源码参见github-chatroom
1.全局配置
client_data
#define BUFFER_SIZE 64
#define USER_LIMIT 5
#define FD_LIMIT 65535
#define MAX_EVENT_NUMBER 1024
#define PROCESS_LIMIT 65535
int sig_pipefd[2];
bool stop_child = false; //子进程的标志位
struct client_data
{
sockaddr_in address; //客户端的socket地址
int connfd; //socket文件描述符
pid_t pid; //处理这个连接的子进程PID
int pipefd[2]; //和父进程通信用的管道
};
users = new client_data[USER_LIMIT+1];
在主进程里开辟一个空间,储存每个连接的客户的信息。包括客户端的socket地址,socket文件描述符,每一个连接客户端会生成一个工作进程用于处理他,pid储存这个工作进程的pid。每个工作线程通过pipefd[2]和主进程通信。注意这个pipefd是由
//在主进程和子进程之间建立管道,以传递必要数据
ret = socketpair(PF_UNIX,SOCK_STREAM,0,users[user_count].pipefd);
建立的,是双向的通信。约定pipefd[0]是工作线程操作的端口, pipefd[1]是主进程操作的端口。
信号处理
//信号处理函数使用管道把信号传递给主循环
void sig_handler(int sig){
//保留原来的errno,在函数最后恢复,以保证可重入性。
int save_errno = errno;
int msg = sig;
send(sig_pipefd[1],(void*)&msg,1,0);
errno = save_errno;
}
//void (*handler)(int) 表示 接受一个int类型参数,返回值为void的函数指针handler
void addsig(int sig,void (*handler)(int),bool restart = true)
{
struct sigaction sa;
memset(&sa,'\0',sizeof(sa));
sa.sa_handler = handler;
if (restart)
sa.sa_flags |=SA_RESTART;
sigfillset(&sa.sa_mask);
assert(sigaction(sig,&sa,NULL)!=-1);//捕获到信号sig,使用sa中规定的方法处理
}
2.子进程函数
idx表示该子进程处理的客户连接的编号
users是保存所有客户连接数据的数组
share_mem指出共享内存的起始地址
子进程使用epoll来同时监听两个文件描述符:
- 客户连接socket.connfd即users[idx].connfd;
- 与父进程(主进程)通信的管道文件描述符pipefd =即users[idx].pipefd[1];
int run_child(int idx,client_data* users,char* share_mem){
epoll_event events[MAX_EVENT_NUMBER];
int child_epollfd = epoll_create(5);
assert(child_epollfd != -1);
printf("child pid=%d is running\n",getpid());
int connfd = users[idx].connfd;
addfd(child_epollfd,connfd);
int pipefd = users[idx].pipefd[1];
addfd(child_epollfd,pipefd);
int ret;
addsig(SIGTERM,child_term_handler,false);//设置进程被终止时的信号处理函数
while(!stop_child){
int number = epoll_wait(child_epollfd,events,MAX_EVENT_NUMBER,-1);
if ((number<0) && (errno != EINTR))
{
/*EINTR是linux中函数的返回状态,在不同的函数中意义不同。
表示某种阻塞的操作,被接收到的信号中断,造成的一种错误返回值。*/
printf("epoll failure\n");
break;
}
for (int i=0;i<number;i++){
int sockfd = events[i].data.fd;
//本子进程负责的socket有数据进来
if ((sockfd == connfd) &&