日期:2014-05-16  浏览次数:20735 次

UNIX TCP回射服务器/客户端(6):进程池服务器

    《Unix网络编程》这本书附带了许多短小精美的小程序,我在阅读此书的时候,将书上的代码按照自己的理解重写了一遍(大部分是抄书上的),加深一下自己的理解(纯看书太困了,呵呵)。此例子在Ubuntu10.04上测试通过。

    PS:程序里使用了包裹函数(首字母是大写的函数)和常量(所有字母都是大写的常量)的声明在my_unp.h文件中,定义在unp_base.c和unp_thread.c中,地址:http://blog.csdn.net/aaa20090987/article/details/8096701

    程序简介:这是一个基本的线程池服务器模型。服务器启动时,先启动N个用于处理与客户端通信的子进程,主进程与每个子进程之间建立两条用于通信管道,并关闭各自不需要的端口。当一个客户端连接上服务器时(accept),主进程就随机选择一个子进程,通过管理向其传递socket的描述符,与子进程来处理与客户端的通信,通信结束后,子进程通信管道向主进程报告通信结束。


上代码:

#include "my_unp.h"

typedef struct
{
	pid_t child_pid;	 //子进程的ID
	int child_pipefd;	 //子进程与父进程的通信管道
	int child_status;	 //子进程状态,0为准备好了
	long child_count;	 //子进程的处理号
} Child;

Child *cptr;
static int  nchildren;

ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd)
{
	struct msghdr msg;
	struct iovec iov[1];

	//保证cmsghdr和msg_control的对齐  
	union 
	{
		struct cmsghdr cm;
		char    control[CMSG_SPACE(sizeof(int))];
	} control_un;
	struct cmsghdr *cmptr;
	//设置辅助缓冲区和长度 
	msg.msg_control = control_un.control;
	msg.msg_controllen = sizeof(control_un.control);

	//只需要一组附属数据就够了,直接通过CMSG_FIRSTHDR取得
	cmptr = CMSG_FIRSTHDR(&msg);
	//设置必要的字段,数据和长度  
	cmptr->cmsg_len = CMSG_LEN(sizeof(int));
	cmptr->cmsg_level = SOL_SOCKET;
	//指明发送的是描述符   
	cmptr->cmsg_type = SCM_RIGHTS;
	//把fd写入辅助数据中 
	*((int *) CMSG_DATA(cmptr)) = sendfd;

	//UDP才需要这个,直接为空
	msg.msg_name = NULL;
	msg.msg_namelen = 0;

	//设置数据缓冲区,实际上1个字节就够了 
	iov[0].iov_base = ptr;
	iov[0].iov_len = nbytes;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;

	//发送
	return sendmsg(fd, &msg, 0);
}

ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd)
{
	struct msghdr msg;
	struct iovec iov[1];
	ssize_t   n;

	//保证cmsghdr和msg_control的对齐  
	union 
	{
		struct cmsghdr cm;
		char    control[CMSG_SPACE(sizeof(int))];
	} control_un;
	struct cmsghdr *cmptr;

	//设置辅助数据缓冲区和长度   
	msg.msg_control = control_un.control;
	msg.msg_controllen = sizeof(control_un.control);

	//UDP才需要这个,直接为空
	msg.msg_name = NULL;
	msg.msg_namelen = 0;

	//设置数据缓冲区
	iov[0].iov_base = ptr;
	iov[0].iov_len = nbytes;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;

	//设置结束,开始接收   
	if ( (n = recvmsg(fd, &msg, 0)) <= 0)
		return(n);

	//检查一下返回结果
	if ( (cmptr = CMSG_FIRSTHDR(&msg)) != NULL &&
		cmptr->cmsg_len == CMSG_LEN(sizeof(int)) ) 
	{
		if (cmptr->cmsg_level != SOL_SOCKET)
			error_quit("control level != SOL_SOCKET");
		if (cmptr->cmsg_type != SCM_RIGHTS)
			error_quit("control type != SCM_RIGHTS");
		//终于拿到描述符了
		*recvfd = *((int *) CMSG_DATA(cmptr));
	} 
	//出错,没有拿到描述符
	else
		*recvfd = -1;

	return n;
}

//接受来自客户端字符串,然后再原样返回
void str_echo(int sockfd)
{
	ssize_t n;  
	char buf[MAXLINE];  

again:  
	//从套接字中读取数据,写到buffer中去  
	//再将buffer中的数据写到套接字中去  
	while( (n=read(sockfd, buf, MAXLINE)) > 0 )  
		Writen(sockfd, buf, n); 

	//由于信号中断,没写或读成功任何数据  
	if( n<0 && errno==EINTR )  
		goto again;  
	else if( n < 0 )  
		error_quit("str_echo: read error");  
}

//子进程的主要操作函数
void child_main(int i, int listenfd, int addrlen)
{
	char c;
	int connfd;
	ssize_t n;

	printf("child %ld starting\n", (long) getpid());
	while(1)
	{
		//当子进程没事干的时候,就阻塞在这里,等待父进程的调度
		n = read_fd(STDERR_FILENO, &c, 1, &connfd);
		if ( n < 0 )
			error_quit("read_fd error");
		if (connfd < 0)
			e