Linux_网络编程_7.进程池_01进程池概述,进程池初步搭建步骤(小文件传输实现)

进程池和线程池

1、 进程池

进程池目的和流程

1.1 进程间传递文件描述符(难点)

① 初始化 socketpair 类型描述符
② sendmsg 发送描述符 readv 和 writev 定义结构体struct msghdr 为结构体申请空间
③recvmsg 接收文件描述符
实例1:进程间传递文件描述符实现

1.2 进程池工作流程

进程池流程小节

1.3 主要数据结构

1.4 进程池代码编写流程
第一步:

1. 基本框架—创建5个子进程
头文件 创建子进程makeChild的封装 线程池主函数
2. 创建5个子进程,初始化其数据,创建DEBUG开关测试打印子进程ID和管道对端ID
头文件: 创建子进程makeChild的封装 线程池主函数

第二步

3. 创建子进程,初始化其数据,打印ID测试,启动子进程接收和发送
头文件: sendFd,recvFd 发送接受描述符封装 创建子进程makeChild封装 进程池主函数:
4. 创建子进程,初始化其数据,打印ID测试,启动子进程接收发送,初始化socket,并开启监听
头文件: sendFd,recvFd 发送接受描述符封装 tcpInit 初始化socket,并开启监听封装 创建子进程makeChild封装 进程池主函数:

第三步:

5. 创建子进程,初始化其数据,打印ID测试,启动子进程接收发送,初始化socket开启监听,注册监听每一个子进程管道对端
头文件: sendFd, recvFd发送接收描述符封装 tcpInit 初始化socket,并开启监听封装 注册监听封装epollInAdd 创建子进程makeChild封装 进程池主函数

第四步:

6. 创建子进程,初始化其数据,打印ID测试,启动子进程接收发送,初始化socket开启监听,注册监听每一个子进程管道对端,模拟发送,标记忙碌
头文件 sendFd,recvFd 发送接收描述符封装 tcpInit 初始化socket,并开启监听封装 注册监听封装epollInAdd 创建子进程makeChild封装 主函数进程池

1.5 子进程采用变长结构体发送文件(难点)

7. 使用小火车协议,完成进程池父子进程小文件(固定文件名)传输
头文件: tranFile 给客户端发文件 sendFd,recvFd 发送接收描述符 初始化socket,并开启监听封装 注册监听epollInAdd 创建子进程,初始化数据结构 进程池主函数 客户端
结果

目的: 实现多个客户端同时下载文件
流程: 客户端连接服务器,连接成功后传输文件,传输完毕服务器断开连接


1.1 进程间传递文件描述符(难点)

第一步,初始化 socketpair 类型描述符

  int socketpair(int domain,int type,int protocol,int sv[]);

作用:创造一对未命名的、相互连接的套接字。

参数:

  • 第一个参数 domain 表示协议族,目前为 AF_LOCAL

  • 第二个参数 type 表示类型,既可以是 SOCK_STREAM,又可以是 SOCK_DGRAM
    (当参数指定为 SOCK_STREAM 时,得到的结果为流管道,它是全双工的,两个描述符即可读又可写

  • 第三个参数只能为0;

  • 第四个参数用于保存创建的套接字对;

返回值:
如果函数成功,则返回0,创建好的套接字分别是sv[0] 和 sv[1];
否则返回-1,错误码保存于errno中。

例:

int fds[2]; 
socketpair(AF_LOCAL,SOCK_STREAM,0,fds); 

读端fds[0] (sv[0]),写端fds[1] (sv[1])


第二步:sendmsg 发送描述符

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); 

作用:系统调用,用于发送消息到另一个套接字

使用的 sockfd 即 sockpair 初始化的描述符 fds[1];

readv 和 writev
readv和writev函数用于在一次函数调用中读、写多个非连续缓冲区(结构体)。有时也将这两个函数称为散布读(scatter read)和聚集写(gather write)。
.
#include
.
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
size_t writev(int filedes, const struct iovec *iov, int iovcnt);
.
这两个函数的第二个参数是指向 iovec 结构数组的一个指针:
struct iovec {
void *iov_base; /* starting address of buffer */
size_t iov_len; /* size of buffer */
};
.
例:使用wirtev写入文件
int main(int argc,char* argv[])
{
ARGS_CHECK(argc, 2);
int fd = open(argv[1], O_RDWR);
ERROR_CHECK(fd, -1, "open");
struct iovec iov[2];
char buf1[10] = "hello";
iov[0].iov_base = buf1;
iov[0].iov_len = 5;
char buf2[10] = "world";
iov[1].iov_base = buf2;
iov[1].iov_len = 5;
writev(fd, iov, 2);
close(fd);
return 0;
}

sendmsg 关键是初始化 msghdr 结构体

 struct msghdr {
 	void           *msg_name;       // optional address 没用  
 	socklen_t      msg_namelen;     // size of address  没用 
 	struct iovec   *msg_iov;        // scatter/gather array  没用 
 	size_t         msg_iovlen;      // # elements in msg_iov  没用
 	void           *msg_control;    // ancillary data, see below  关键,即下面的 cmsghdr 结构体地址
 	size_t         msg_controllen;  // ancillary data buffer len   cmsghdr 结构体的长度 
 	int            msg_flags;        // flags (unused)  没用
 }; 

定义结构体

 struct msghdr msg;
 bzero();

iovec 必须赋值
Cmsg 构造结构体 cmsghdr
man cmsg 得到如下信息:
(变长结构体)

struct cmsghdr {
	socklen_t cmsg_len;    // data byte count, including header
	int       cmsg_level;  // originating protocol
	int       cmsg_type;   // protocol-specific type
	/* followed by unsigned char cmsg_data[];  (不一定,传递描述符的个数)*/
}; 

首先*定义 struct cmsghdr cmsg 指针

cmsg_len 中存取 cmsghdr 结构体的长度,通过 CMSG_LEN 进行计算,我们传递的 fd 的大小为整型四个字节,所以

Int len = CMSG_LEN(sizeof(int)); 

然后为结构体申请空间:

cmsg = (struct cmsghdr *)calloc(1,len); 
Cmsg->cmsg_len = len; 
cmsg->cmsg_level = SOL_SOCKET; 
Cmsg->cmsg_type = SCM_RIGHTS; 
int *fdptr; 
fdptr= (int *) CMSG_DATA(cmsg);  //其实是自己申请空间大小加12字节
*fdptr = fd; 

最后就可以通过 sendmsg 来发送文件描述符


第三步: Recvmsg 接收文件描述符,接收的 msghdr 结构体初始化和 sendmsg 几乎完全一致,区别如下:

*fd = *fdptr;


实例1:进程间传递文件描述符实现

int sendFd(int pipeFd, int fd)
{
	struct msghdr msg;
	bzero(&msg, sizeof(msg));
	struct iovec iov[2];
	char buf1[10] = "hello";
	iov[0].iov_base = buf1;
	iov[0].iov_len = 5;
	char buf2[10] = "world";
	iov[1].iov_base = buf2;
	iov[1].iov_len = 5;
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	struct cmsghdr *cmsg;
	int cmsgLen = CMSG_LEN(sizeof(int));
	cmsg = (struct cmsghdr *)calloc(1, cmsgLen);
	cmsg->cmsg_len = cmsgLen;
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	*(int *)CMSG_DATA(cmsg) = fd; //要把传递的描述符告诉内核
	msg.msg_control = cmsg;
	msg.msg_controllen = cmsgLen;
	int ret;
	ret = sendmsg(pipeFd, &msg, 0);
	ERROR_CHECK(ret, -1, "sendmsg");
	return 0;
}

int recvFd(int pipeFd, int *fd)
{
	struct msghdr msg;
	bzero(&msg, sizeof(msg));
	struct iovec iov[2];
	char buf1[10] = "hello";
	iov[0].iov_base = buf1;
	iov[0].iov_len = 5;
	char buf2[10] = "world";
	iov[1].iov_base = buf2;
	iov[1].iov_len = 5;
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	struct cmsghdr *cmsg;
	int cmsgLen = CMSG_LEN(sizeof(int));
	cmsg = (struct cmsghdr *)calloc(1, cmsgLen);
	cmsg->cmsg_len = cmsgLen;
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	msg.msg_control = cmsg;
	msg.msg_controllen = cmsgLen;
	int ret;
	ret = recvmsg(pipeFd, &msg, 0);
	ERROR_CHECK(ret, -1, "recvmsg");
	*fd = *(int *)CMSG_DATA(cmsg);
	return 0;
}

int main(int argc, char* argv[])
{
	int fds[2];
	int ret;
	ret = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); //全双工管道
	ERROR_CHECK(ret, -1, "socketpair");
	if(!fork())
	{
		close(fds[1]);
		int fd;
		recvFd(fds[0], &fd);
		printf("I am child, %d\n", fd);
		char buf[128] = {0};
		read(fd, buf, sizeof(buf));
		printf("I am child, buf = %s\n", buf);
	}else{
		close(fds[0]);
		int fd = open("file", O_RDWR);
		printf("I am parent, fd = %d\n", fd);
		sendFd(fds[1], fd);
		wait(NULL);
	}
	return 0;
}

1.2 进程池工作流程

Linux_网络编程_7.进程池_01进程池概述,进程池初步搭建步骤(小文件传输实现)_第1张图片

如上图所示,首先我们通过父进程创建了很多个子进程,每个子进程与父进程直接都有一条全双工的管道,父进程是我们的代理。
当 1 号客户端请求连接下载文件时,父进程接收到请求,产生 new_fd,并把 new_fd 发送给非忙碌的子进程 a,由子进程 a 将文件传输给 1 号客户端。这时 2 号客户端请求下载文件,父进程接收请求得到 new_fd,由于这时子进程 a 忙碌,所以将 new_fd 发送给子进程 b,由子进程 b 负责给 2 号客户端下载文件。

进程池流程
.
父进程:
.
创建管道 epoll监听管道读端
if(evs.fd == pid.pipe) pid1.busy = 0
创建子进程
for
{
fork
}
newFd = accept;
newFd 交给非忙碌子进程, 标记子进程为忙碌状态
.
子进程:
.
1.子进程在忙碌,传输文件
2.非忙碌
.
while(1)
{
1.等待父进程分配任务
2.拿到newFd,传文件,应用层协议,小火车,变成非忙碌
3.子进程通知父进程,文件传输完毕,不忙碌了,写管道
}


1.3 主要数据结构

父进程管理子进程所使用的数据结构 :

typedef struct{
	pid_t pid; -子进程的 pid   
	int fd; -管道的一端 socket  
	short busy; -代表子进程是否忙碌,0 代表非忙碌,1 代表忙碌 
}process_data; 

父进程, epoll 监控 socketFd 和 所有子进程 fd(每个子进程对应一个结构体都有一个 fd ),监控子进程读事件时,子进程一旦完成任务,就往管道的另一端写,另一端端知道可读,就知道了子进程状态就是非忙碌。

创建多少个子进程,我们就用多少个对应的结构体管理子进程。


1.4 进程池代码编写流程

第一步:

  • make_child 函数初始化子进程
  • 循环创建子进程,并初始化父进程的子进程管理结构体数组 parr(前面的结构体),通过 socket_pair 将 socket 描述符一端放入数组

1. 基本框架—创建5个子进程

头文件:

#include<......>
...
#define ARGS_CHECK(argc,val) {if(argc != val) {printf("error args\n"); return -1;}}
#define ERROR_CHECK(ret,retVal,funcName) { if(ret == retVal) {perror(funcName);return -1;}}
#define THREAD_ERROR_CHECK(ret, funcName) {if(ret != 0) {printf("%s:%s\n", funcName, strerror(ret)); return -1;}}
#define CHILD_THREAD_ERROR_CHECK(ret, funcName) {if(ret != 0) {printf("%s:%s\n", funcName, strerror(ret)); return (void*)-1;}}

typedef struct{
	pid_t pid; //子进程的pid
	int pipeFd; //子进程的管道对端
	short busy; //用来标识子进程是否忙碌,0代表非忙碌,1代表忙碌
}process_data_t;

//创建子进程
int makeChild(process_data_t*, int);
int childHandle(int);

//int sendFd(int, int);
//int recvFd(int, int*);
//int tcpInit(int*, char*, char*);

创建子进程makeChild的封装

int makeChild(process_data_t *p, int processNum)
{
	int i;
	pid_t pid;
	//int fds[2];
	//int ret;
	for(i = 0; i < processNum; i++)
	{
		//ret = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
		//ERROR_CHECK(ret, -1, "socketpair");
		pid = fork();
		
		//子进程
		if(0 == pid)
		{
			//close(fds[0]);
			childHandle(fds[1]);
		}

		//close(fds[1]);
		//子进程pid
		//p[i].pid = pid;
		//存储每个子进程的管道对端
		//p[i].pipeFd = fds[0];
		//p[i].busy = 0;
	}
	return 0;
}

int childHandle(int pipeFd)
{
	//int newFd;
	//char finishFlag;
	while(1)
	//{
		//接收任务,没有任务时,子进程睡觉
		//recvFd(pipeFd, &newFd); 
		//模拟给客户端发文件
		//printf("file send success!\n");
		//子进程通知父进程完成任务了
		//write(pipeFd, &finishFlag, 1);
	//}
}

进程池主函数

//#define DEBUG

int main(int argc, char* argv[])
{
	if(argc != 4)
	{
		printf("./prtcess_poll_server ip port process_num\n");
		return -1;
	}

	//得到进程数
	int processNum = atoi(argv[3]);
	
	process_data_t *pData = (process_data_t*)calloc(processNum, sizeof(process_data_t));
	
	//创建子进程
	makeChild(pData, processNum);

//#ifdef DEBUG
	//int i;
	//for(i = 0; i < processNum; i++)
	//{
		//printf("pid = %d, pidFd = %d\n", pData[i].pid, pData[i].pipeFd);
	//}
//#endif
	
	//初始化socket,并开启监听
	//int socketFd;
	//tcpInit(&socketFd, argv[1], argv[2]);
	while(1);
	return 0;
}

2. 创建5个子进程,初始化其数据,创建DEBUG开关测试打印子进程ID和管道对端ID

头文件:

#include<......>
...
#define ARGS_CHECK(argc,val) {if(argc != val) {printf("error args\n"); return -1;}}
#define ERROR_CHECK(ret,retVal,funcName) { if(ret == retVal) {perror(funcName);return -1;}}
#define THREAD_ERROR_CHECK(ret, funcName) {if(ret != 0) {printf("%s:%s\n", funcName, strerror(ret)); return -1;}}
#define CHILD_THREAD_ERROR_CHECK(ret, funcName) {if(ret != 0) {printf("%s:%s\n", funcName, strerror(ret)); return (void*)-1;}}

typedef struct{
	pid_t pid; //子进程的pid
	int pipeFd; //子进程的管道对端
	short busy; //用来标识子进程是否忙碌,0代表非忙碌,1代表忙碌
}process_data_t;

//创建子进程
int makeChild(process_data_t*, int);
int childHandle(int);

//int sendFd(int, int);
//int recvFd(int, int*);
//int tcpInit(int*, char*, char*);

创建子进程makeChild的封装

//创建子进程,初始化数据结构
int makeChild(process_data_t *p, int processNum)
{
	int i;
	pid_t pid;
	int fds[2];
	int ret;
	for(i = 0; i < processNum; i++)
	{
		ret = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
		ERROR_CHECK(ret, -1, "socketpair");
		pid = fork();
		
		//子进程
		if(0 == pid)
		{
			close(fds[0]);
			childHandle(fds[1]);
		}

		close(fds[1]);
		//子进程pid
		p[i].pid = pid;
		//存储每个子进程的管道对端
		p[i].pipeFd = fds[0];
		p[i].busy = 0;
	}
	return 0;
}

int childHandle(int pipeFd)
{
	//int newFd;
	//char finishFlag;
	while(1)
	//{
		//接收任务,没有任务时,子进程睡觉
		//recvFd(pipeFd, &newFd); 
		//模拟给客户端发文件
		//printf("file send success!\n");
		//子进程通知父进程完成任务了
		//write(pipeFd, &finishFlag, 1);
	//}
}

线程池主函数

#define DEBUG

int main(int argc, char* argv[])
{
	if(argc != 4)
	{
		printf("./prtcess_poll_server ip port process_num\n");
		return -1;
	}

	//得到进程数
	int processNum = atoi(argv[3]);
	
	process_data_t *pData = (process_data_t*)calloc(processNum, sizeof(process_data_t));
	
	//创建子进程
	makeChild(pData, processNum);

#ifdef DEBUG
	int i;
	for(i = 0; i < processNum; i++)
	{
		printf("pid = %d, pidFd = %d\n", pData[i].pid, pData[i].pipeFd);
	}
#endif
	
	//初始化socket,并开启监听
	//int socketFd;
	//tcpInit(&socketFd, argv[1], argv[2]);
	while(1);
	return 0;
}

第二步

  • 子进程流程,目前让子进程死循环,接收任务,给客户端发文件,然后通知父进程完成任务, 退出机制暂时先不考虑
    while(1)
    {
    …Recv_fd 等待父进程发送任务
    …Hand_request 发送文件数据
    …Write 向父进程发送完成任务
    }

3. 创建子进程,初始化其数据,打印ID测试,启动子进程接收和发送

头文件:

#include<......>
...
#define ARGS_CHECK(argc,val) {if(argc != val) {printf("error args\n"); return -1;}}
#define ERROR_CHECK(ret,retVal,funcName) { if(ret == retVal) {perror(funcName);return -1;}}
#define THREAD_ERROR_CHECK(ret, funcName) {if(ret != 0) {printf("%s:%s\n", funcName, strerror(ret)); return -1;}}
#define CHILD_THREAD_ERROR_CHECK(ret, funcName) {if(ret != 0) {printf("%s:%s\n", funcName, strerror(ret)); return (void*)-1;}}

typedef struct{
	pid_t pid; //子进程的pid
	int pipeFd; //子进程的管道对端
	short busy; //用来标识子进程是否忙碌,0代表非忙碌,1代表忙碌
}process_data_t;

//创建子进程
int makeChild(process_data_t*, int);
int childHandle(int);

//发送接收描述符
int sendFd(int, int);
int recvFd(int, int*);

//
//int tcpInit(int*, char*, char*);

sendFd,recvFd 发送接受描述符封装

//发送描述符
int sendFd(int pipeFd, int fd)
{
	struct msghdr msg;
	bzero(&msg, sizeof(msg));
	struct iovec iov[2];
	char buf1[10] = "hello";
	iov[0].iov_base = buf1;
	iov[0].iov_len = 5;
	char buf2[10] = "world";
	iov[1].iov_base = buf2;
	iov[1].iov_len = 5;
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	struct cmsghdr *cmsg;
	int cmsgLen = CMSG_LEN(sizeof(int));
	cmsg = (struct cmsghdr *)calloc(1, cmsgLen);
	cmsg->cmsg_len = cmsgLen;
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	*(int *)CMSG_DATA(cmsg) = fd; //要把传递的描述符告诉内核
	msg.msg_control = cmsg;
	msg.msg_controllen = cmsgLen;
	int ret;
	ret = sendmsg(pipeFd, &msg, 0);
	ERROR_CHECK(ret, -1, "sendmsg");
	return 0;
}

//接收描述符
int recvFd(int pipeFd, int *fd)
{
	struct msghdr msg;
	bzero(&msg, sizeof(msg));
	struct iovec iov[2];
	char buf1[10] = "hello";
	iov[0].iov_base = buf1;
	iov[0].iov_len = 5;
	char buf2[10] = "world";
	iov[1].iov_base = buf2;
	iov[1].iov_len = 5;
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	struct cmsghdr *cmsg;
	int cmsgLen = CMSG_LEN(sizeof(int));
	cmsg = (struct cmsghdr *)calloc(1, cmsgLen);
	cmsg->cmsg_len = cmsgLen;
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	msg.msg_control = cmsg;
	msg.msg_controllen = cmsgLen;
	int ret;
	ret = recvmsg(pipeFd, &msg, 0);
	ERROR_CHECK(ret, -1, "recvmsg");
	*fd = *(int *)CMSG_DATA(cmsg);
	return 0;
}

创建子进程makeChild封装

//创建子进程,初始化数据结构
int makeChild(process_data_t *p, int processNum)
{
	int i;
	pid_t pid;
	int fds[2];
	int ret;
	for(i = 0; i < processNum; i++)
	{
		ret = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
		ERROR_CHECK(ret, -1, "socketpair");
		pid = fork();
		
		//子进程
		if(0 == pid)
		{
			close(fds[0]);

			childHandle(fds[1]);
		}

		close(fds[1]);
		//子进程pid
		p[i].pid = pid;
		//存储每个子进程的管道对端
		p[i].pipeFd = fds[0];
		p[i].busy = 0;

	}
	return 0;
}

int childHandle(int pipeFd)
{
	int newFd;
	char finishFlag;
	while(1)
	{
		//接收任务,没有任务时,子进程睡觉
		recvFd(pipeFd, &newFd); 
		//模拟给客户端发文件
		printf("file send success!\n");
		//子进程通知父进程完成任务了
		write(pipeFd, &finishFlag, 1);
	}
}

进程池主函数:

#define DEBUG

int main(int argc, char* argv[])
{
	if(argc != 4)
	{
		printf("./prtcess_poll_server ip port process_num\n");
		return -1;
	}

	//得到进程数
	int processNum = atoi(argv[3]);
	
	process_data_t *pData = (process_data_t*)calloc(processNum, sizeof(process_data_t));
	
	//创建子进程
	makeChild(pData, processNum);

#ifdef DEBUG
	int i;
	for(i = 0; i < processNum; i++)
	{
		printf("pid = %d, pidFd = %d\n", pData[i].pid, pData[i].pipeFd);
	}
#endif
	
	//初始化socket,并开启监听
	//int socketFd;
	//tcpInit(&socketFd, argv[1], argv[2]);
	while(1);
	return 0;
}

4. 创建子进程,初始化其数据,打印ID测试,启动子进程接收发送,初始化socket,并开启监听

头文件:

#include<......>
...
#define ARGS_CHECK(argc,val) {if(argc != val) {printf("error args\n"); return -1;}}
#define ERROR_CHECK(ret,retVal,funcName) { if(ret == retVal) {perror(funcName);return -1;}}
#define THREAD_ERROR_CHECK(ret, funcName) {if(ret != 0) {printf("%s:%s\n", funcName, strerror(ret)); return -1;}}
#define CHILD_THREAD_ERROR_CHECK(ret, funcName) {if(ret != 0) {printf("%s:%s\n", funcName, strerror(ret)); return (void*)-1;}}

typedef struct{
	pid_t pid; //子进程的pid
	int pipeFd; //子进程的管道对端
	short busy; //用来标识子进程是否忙碌,0代表非忙碌,1代表忙碌
}process_data_t;

//创建子进程
int makeChild(process_data_t*, int);
int childHandle(int);

//发送接收描述符
int sendFd(int, int);
int recvFd(int, int*);

//初始化socket,并开启监听
int tcpInit(int*, char*, char*);

sendFd,recvFd 发送接受描述符封装

//发送描述符
int sendFd(int pipeFd, int fd)
{
	struct msghdr msg;
	bzero(&msg, sizeof(msg));
	struct iovec iov[2];
	char buf1[10] = "hello";
	iov[0].iov_base = buf1;
	iov[0].iov_len = 5;
	char buf2[10] = "world";
	iov[1].iov_base = buf2;
	iov[1].iov_len = 5;
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	struct cmsghdr *cmsg;
	int cmsgLen = CMSG_LEN(sizeof(int));
	cmsg = (struct cmsghdr *)calloc(1, cmsgLen);
	cmsg->cmsg_len = cmsgLen;
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	*(int *)CMSG_DATA(cmsg) = fd; //要把传递的描述符告诉内核
	msg.msg_control = cmsg;
	msg.msg_controllen = cmsgLen;
	int ret;
	ret = sendmsg(pipeFd, &msg, 0);
	ERROR_CHECK(ret, -1, "sendmsg");
	return 0;
}

//接收描述符
int recvFd(int pipeFd, int *fd)
{
	struct msghdr msg;
	bzero(&msg, sizeof(msg));
	struct iovec iov[2];
	char buf1[10] = "hello";
	iov[0].iov_base = buf1;
	iov[0].iov_len = 5;
	char buf2[10] = "world";
	iov[1].iov_base = buf2;
	iov[1].iov_len = 5;
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	struct cmsghdr *cmsg;
	int cmsgLen = CMSG_LEN(sizeof(int));
	cmsg = (struct cmsghdr *)calloc(1, cmsgLen);
	cmsg->cmsg_len = cmsgLen;
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	msg.msg_control = cmsg;
	msg.msg_controllen = cmsgLen;
	int ret;
	ret = recvmsg(pipeFd, &msg, 0);
	ERROR_CHECK(ret, -1, "recvmsg");
	*fd = *(int *)CMSG_DATA(cmsg);
	return 0;
}

tcpInit 初始化socket,并开启监听封装

//初始化socket,并开启监听
int tcpInit(int *sfd,char* ip,char* port)
{
    int socketFd=socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(socketFd,-1,"socket");
    struct sockaddr_in serAddr;
    bzero(&serAddr,sizeof(serAddr));
    serAddr.sin_family=AF_INET;
    serAddr.sin_port=htons(atoi(port));
    serAddr.sin_addr.s_addr=inet_addr(ip);
    int ret;
    ret=bind(socketFd,(struct sockaddr*)&serAddr,sizeof(serAddr));
    ERROR_CHECK(ret,-1,"bind");
    listen(socketFd,10);
    *sfd=socketFd;
    return 0;
}

创建子进程makeChild封装

//创建子进程,初始化数据结构
int makeChild(process_data_t *p, int processNum)
{
	int i;
	pid_t pid;
	int fds[2];
	int ret;
	for(i = 0; i < processNum; i++)
	{
		ret = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
		ERROR_CHECK(ret, -1, "socketpair");
		pid = fork();
		
		//子进程
		if(0 == pid)
		{
			close(fds[0]);

			childHandle(fds[1]);
		}

		close(fds[1]);
		//子进程pid
		p[i].pid = pid;
		//存储每个子进程的管道对端
		p[i].pipeFd = fds[0];
		p[i].busy = 0;

	}
	return 0;
}

int childHandle(int pipeFd)
{
	int newFd;
	char finishFlag;
	while(1)
	{
		//接收任务,没有任务时,子进程睡觉
		recvFd(pipeFd, &newFd); 
		//模拟给客户端发文件
		printf("file send success!\n");
		//子进程通知父进程完成任务了
		write(pipeFd, &finishFlag, 1);
	}
}

进程池主函数:

#define DEBUG

int main(int argc, char* argv[])
{
	if(argc != 4)
	{
		printf("./prtcess_poll_server ip port process_num\n");
		return -1;
	}

	//得到进程数
	int processNum = atoi(argv[3]);
	
	process_data_t *pData = (process_data_t*)calloc(processNum, sizeof(process_data_t));
	
	//创建子进程
	makeChild(pData, processNum);

#ifdef DEBUG
	int i;
	for(i = 0; i < processNum; i++)
	{
		printf("pid = %d, pidFd = %d\n", pData[i].pid, pData[i].pipeFd);
	}
#endif
	
	//初始化socket,并开启监听
	int socketFd;
	tcpInit(&socketFd, argv[1], argv[2]);
	while(1);
	return 0;
}

第三步:

  • 父进程 epoll 监控 fd_listen 描述符。
  • 父进程 epoll 监控 parr 结构体数组的 socket 描述符,结构体数组中的描述符是每一个子进程的管道对端,通过监控这个,当子进程通过 write 向我们写通知时,我们就知道子进程非忙碌了。

5. 创建子进程,初始化其数据,打印ID测试,启动子进程接收发送,初始化socket开启监听,注册监听每一个子进程管道对端

头文件:

#define ARGS_CHECK(argc,val) {if(argc != val) {printf("error args\n"); return -1;}}
#define ERROR_CHECK(ret,retVal,funcName) { if(ret == retVal) {perror(funcName);return -1;}}
#define THREAD_ERROR_CHECK(ret, funcName) {if(ret != 0) {printf("%s:%s\n", funcName, strerror(ret)); return -1;}}
#define CHILD_THREAD_ERROR_CHECK(ret, funcName) {if(ret != 0) {printf("%s:%s\n", funcName, strerror(ret)); return (void*)-1;}}

typedef struct{
	pid_t pid; //子进程的pid
	int pipeFd; //子进程的管道对端
	short busy; //用来标识子进程是否忙碌,0代表非忙碌,1代表忙碌
}process_data_t;

//创建子进程
int makeChild(process_data_t*, int);
int childHandle(int);

int sendFd(int, int);
int recvFd(int, int*);

int tcpInit(int*, char*, char*);

int epollInAdd(int, int);

sendFd, recvFd发送接收描述符封装

int sendFd(int pipeFd, int fd)
{
	struct msghdr msg;
	bzero(&msg, sizeof(msg));
	struct iovec iov[2];
	char buf1[10] = "hello";
	iov[0].iov_base = buf1;
	iov[0].iov_len = 5;
	char buf2[10] = "world";
	iov[1].iov_base = buf2;
	iov[1].iov_len = 5;
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	struct cmsghdr *cmsg;
	int cmsgLen = CMSG_LEN(sizeof(int));
	cmsg = (struct cmsghdr *)calloc(1, cmsgLen);
	cmsg->cmsg_len = cmsgLen;
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	*(int *)CMSG_DATA(cmsg) = fd; //要把传递的描述符告诉内核
	msg.msg_control = cmsg;
	msg.msg_controllen = cmsgLen;
	int ret;
	ret = sendmsg(pipeFd, &msg, 0);
	ERROR_CHECK(ret, -1, "sendmsg");
	return 0;
}

int recvFd(int pipeFd, int *fd)
{
	struct msghdr msg;
	bzero(&msg, sizeof(msg));
	struct iovec iov[2];
	char buf1[10] = "hello";
	iov[0].iov_base = buf1;
	iov[0].iov_len = 5;
	char buf2[10] = "world";
	iov[1].iov_base = buf2;
	iov[1].iov_len = 5;
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	struct cmsghdr *cmsg;
	int cmsgLen = CMSG_LEN(sizeof(int));
	cmsg = (struct cmsghdr *)calloc(1, cmsgLen);
	cmsg->cmsg_len = cmsgLen;
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	msg.msg_control = cmsg;
	msg.msg_controllen = cmsgLen;
	int ret;
	ret = recvmsg(pipeFd, &msg, 0);
	ERROR_CHECK(ret, -1, "recvmsg");
	*fd = *(int *)CMSG_DATA(cmsg);
	return 0;
}

tcpInit 初始化socket,并开启监听封装

int tcpInit(int *sfd,char* ip,char* port)
{
    int socketFd=socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(socketFd,-1,"socket");
    struct sockaddr_in serAddr;
    bzero(&serAddr,sizeof(serAddr));
    serAddr.sin_family=AF_INET;
    serAddr.sin_port=htons(atoi(port));
    serAddr.sin_addr.s_addr=inet_addr(ip);
    int ret;
    ret=bind(socketFd,(struct sockaddr*)&serAddr,sizeof(serAddr));
    ERROR_CHECK(ret,-1,"bind");
    listen(socketFd,10);
    *sfd=socketFd;
    return 0;
}

注册监听封装

int epollInAdd(int epfd, int fd)
{
	struct epoll_event event;
	event.events = EPOLLIN;
	event.data.fd = fd;
	int ret;
	ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
	ERROR_CHECK(ret, -1, "epoll_ctl");
	return 0;
}

创建子进程makeChild封装

//创建子进程,初始化数据结构
int makeChild(process_data_t *p, int processNum)
{
	int i;
	pid_t pid;
	int fds[2];
	int ret;
	for(i = 0; i < processNum; i++)
	{
		ret = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
		ERROR_CHECK(ret, -1, "socketpair");
		pid = fork();
		
		//子进程
		if(0 == pid)
		{
			close(fds[0]);

			childHandle(fds[1]);
		}

		close(fds[1]);
		//子进程pid
		p[i].pid = pid;
		//存储每个子进程的管道对端
		p[i].pipeFd = fds[0];
		p[i].busy = 0;

	}
	return 0;
}

int childHandle(int pipeFd)
{
	int newFd;
	char finishFlag;
	while(1)
	{
		//接收任务,没有任务时,子进程睡觉
		recvFd(pipeFd, &newFd); 
		//模拟给客户端发文件
		printf("file send success!\n");
		//子进程通知父进程完成任务了
		write(pipeFd, &finishFlag, 1);
	}
}

进程池主函数

#define DEBUG

int main(int argc, char* argv[])
{
	if(argc != 4)
	{
		printf("./prtcess_poll_server ip port process_num\n");
		return -1;
	}

	//得到进程数
	int processNum = atoi(argv[3]);
	
	process_data_t *pData = (process_data_t*)calloc(processNum, sizeof(process_data_t));
	
	//创建子进程
	makeChild(pData, processNum);

#ifdef DEBUG
	int i;
	for(i = 0; i < processNum; i++)
	{
		printf("pid = %d, pidFd = %d\n", pData[i].pid, pData[i].pipeFd);
	}
#endif
	
	//初始化socket,并开启监听
	int socketFd;
	tcpInit(&socketFd, argv[1], argv[2]);
	
	int epfd = epoll_create(1);
	struct epoll_event *evs;
	evs = (struct epoll_event*)calloc(processNum + 1, sizeof(struct epoll_event));
	epollInAdd(epfd, socketFd);
	
	//注册监听每一个子进程的管道对端
	for(i = 0; i < processNum; i++)
	{
		epollInAdd(epfd, pData[i].pipeFd);
	}
	
	while(1);
	return 0;
}

第四步:

  • while (1) 启动 epoll_wait,等待是否有客户端连接
  • 有客户端连接后,accept 获得描述符,循环找到非忙碌的子进程,并发送给子进程,标记对应子进程忙碌。
  • 当子进程完成任务后,父进程一旦监控 socket 描述符可读,代表子进程非忙碌,然后标记子进程非忙碌。

6. 创建子进程,初始化其数据,打印ID测试,启动子进程接收发送,初始化socket开启监听,注册监听每一个子进程管道对端,模拟发送,标记忙碌

头文件

#define ARGS_CHECK(argc,val) {if(argc != val) {printf("error args\n"); return -1;}}
#define ERROR_CHECK(ret,retVal,funcName) { if(ret == retVal) {perror(funcName);return -1;}}
#define THREAD_ERROR_CHECK(ret, funcName) {if(ret != 0) {printf("%s:%s\n", funcName, strerror(ret)); return -1;}}
#define CHILD_THREAD_ERROR_CHECK(ret, funcName) {if(ret != 0) {printf("%s:%s\n", funcName, strerror(ret)); return (void*)-1;}}

typedef struct{
	pid_t pid; //子进程的pid
	int pipeFd; //子进程的管道对端
	short busy; //用来标识子进程是否忙碌,0代表非忙碌,1代表忙碌
}process_data_t;

//创建子进程
int makeChild(process_data_t*, int);
int childHandle(int);

int sendFd(int, int);
int recvFd(int, int*);

int tcpInit(int*, char*, char*);

int epollInAdd(int, int);

sendFd,recvFd 发送接收描述符封装

int sendFd(int pipeFd, int fd)
{
	struct msghdr msg;
	bzero(&msg, sizeof(msg));
	struct iovec iov[2];
	char buf1[10] = "hello";
	iov[0].iov_base = buf1;
	iov[0].iov_len = 5;
	char buf2[10] = "world";
	iov[1].iov_base = buf2;
	iov[1].iov_len = 5;
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	struct cmsghdr *cmsg;
	int cmsgLen = CMSG_LEN(sizeof(int));
	cmsg = (struct cmsghdr *)calloc(1, cmsgLen);
	cmsg->cmsg_len = cmsgLen;
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	*(int *)CMSG_DATA(cmsg) = fd; //要把传递的描述符告诉内核
	msg.msg_control = cmsg;
	msg.msg_controllen = cmsgLen;
	int ret;
	ret = sendmsg(pipeFd, &msg, 0);
	ERROR_CHECK(ret, -1, "sendmsg");
	return 0;
}

int recvFd(int pipeFd, int *fd)
{
	struct msghdr msg;
	bzero(&msg, sizeof(msg));
	struct iovec iov[2];
	char buf1[10] = "hello";
	iov[0].iov_base = buf1;
	iov[0].iov_len = 5;
	char buf2[10] = "world";
	iov[1].iov_base = buf2;
	iov[1].iov_len = 5;
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	struct cmsghdr *cmsg;
	int cmsgLen = CMSG_LEN(sizeof(int));
	cmsg = (struct cmsghdr *)calloc(1, cmsgLen);
	cmsg->cmsg_len = cmsgLen;
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	msg.msg_control = cmsg;
	msg.msg_controllen = cmsgLen;
	int ret;
	ret = recvmsg(pipeFd, &msg, 0);
	ERROR_CHECK(ret, -1, "recvmsg");
	*fd = *(int *)CMSG_DATA(cmsg);
	return 0;
}

tcpInit 初始化socket,并开启监听封装

int tcpInit(int *sfd,char* ip,char* port)
{
    int socketFd=socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(socketFd,-1,"socket");
    struct sockaddr_in serAddr;
    bzero(&serAddr,sizeof(serAddr));
    serAddr.sin_family=AF_INET;
    serAddr.sin_port=htons(atoi(port));
    serAddr.sin_addr.s_addr=inet_addr(ip);
    int ret;
    ret=bind(socketFd,(struct sockaddr*)&serAddr,sizeof(serAddr));
    ERROR_CHECK(ret,-1,"bind");
    listen(socketFd,10);
    *sfd=socketFd;
    return 0;
}

注册监听封装

int epollInAdd(int epfd, int fd)
{
	struct epoll_event event;
	event.events = EPOLLIN;
	event.data.fd = fd;
	int ret;
	ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
	ERROR_CHECK(ret, -1, "epoll_ctl");
	return 0;
}

创建子进程makeChild封装

int makeChild(process_data_t *p, int processNum)
{
	int i;
	pid_t pid;
	int fds[2];
	int ret;
	for(i = 0; i < processNum; i++)
	{
		ret = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
		ERROR_CHECK(ret, -1, "socketpair");
		pid = fork();
		
		//子进程
		if(0 == pid)
		{
			close(fds[0]);

			childHandle(fds[1]);
		}

		close(fds[1]);
		//子进程pid
		p[i].pid = pid;
		//存储每个子进程的管道对端
		p[i].pipeFd = fds[0];
		p[i].busy = 0;

	}
	return 0;
}

int childHandle(int pipeFd)
{
	int newFd;
	char finishFlag;
	while(1)
	{
		//接收任务,没有任务时,子进程睡觉
		recvFd(pipeFd, &newFd); 
		send(newFd, "I am Joker", 10, 0);
		//模拟给客户端发文件
		printf("file send success!\n");
		//关闭连接
		close(newFd);
		//子进程通知父进程完成任务了
		write(pipeFd, &finishFlag, 1);
	}
}

主函数进程池

#define DEBUG

int main(int argc, char* argv[])
{
	if(argc != 4)
	{
		printf("./prtcess_poll_server ip port process_num\n");
		return -1;
	}

	//得到进程数
	int processNum = atoi(argv[3]);
	
	process_data_t *pData = (process_data_t*)calloc(processNum, sizeof(process_data_t));
	
	//创建子进程
	makeChild(pData, processNum);

#ifdef DEBUG
	int i;
	for(i = 0; i < processNum; i++)
	{
		printf("pid = %d, pidFd = %d\n", pData[i].pid, pData[i].pipeFd);
	}
#endif
	
	//初始化socket,并开启监听
	int socketFd;
	tcpInit(&socketFd, argv[1], argv[2]);
	
	int epfd = epoll_create(1);
	struct epoll_event *evs;
	evs = (struct epoll_event*)calloc(processNum + 1, sizeof(struct epoll_event));
	epollInAdd(epfd, socketFd);
	
	//注册监听每一个子进程的管道对端
	for(i = 0; i < processNum; i++)
	{
		epollInAdd(epfd, pData[i].pipeFd);
	}
	
	int readyFdCount, newFd, j;
	char noBusyflag;
	while(1)
	{
		readyFdCount = epoll_wait(epfd, evs, processNum + 1, -1);
		for(i = 0; i < readyFdCount; ++i)
		{
			if(evs[i].data.fd == socketFd)
			{
				//接收客户端请求
				newFd = accept(socketFd, NULL, NULL);
				
				//找非忙碌的子进程
				for(j = 0; j < processNum; j++)
				{
					if(0 == pData[j].busy)
					{
						//把任务发给对应的子进程(发送描述符)
						sendFd(pData[j].pipeFd, newFd);
						//子进程标识为忙碌
						pData[j].busy = 1;
						printf("%d pid is busy\n", pData[j].pid);
						break;
					}
				}
				close(newFd);
			}
			for(j = 0; j < processNum; j++)
			{
				if(evs[i].data.fd == pData[j].pipeFd)
				{
					//受到子进程的通知
					read(pData[j].pipeFd, &noBusyflag, 1);
					//子进程设置为非忙碌
					pData[j].busy = 0;
					printf("%d pid is not busy\n", pData[j].pid);
					break;
				}
			}
		}
	}

	return 0;
}

1.5 子进程采用变长结构体发送文件(难点)

由于实际我们发送的文件中可能是字符串,可能是音频,可能是视频,所以发送时,对方要知道多少数据,我们必须采用控制数据,这就是我们的应用层协议设计,这里叫其小火 车,每次火车头 data_len,记录火车 buf 中到底装载了多少数据发到对端。

 typedef struct{
 	int data_len;//控制数据,火车头,记录火车装载内容长度 
 	char buf[1000];//火车车厢
}train; 

7. 使用小火车协议,完成进程池父子进程小文件(固定文件名)传输

头文件:

#define ARGS_CHECK(argc,val) {if(argc != val) {printf("error args\n"); return -1;}}
#define ERROR_CHECK(ret,retVal,funcName) { if(ret == retVal) {perror(funcName);return -1;}}
#define THREAD_ERROR_CHECK(ret, funcName) {if(ret != 0) {printf("%s:%s\n", funcName, strerror(ret)); return -1;}}
#define CHILD_THREAD_ERROR_CHECK(ret, funcName) {if(ret != 0) {printf("%s:%s\n", funcName, strerror(ret)); return (void*)-1;}}

#define FILENAME "file"

typedef struct{
	pid_t pid; //子进程的pid
	int pipeFd; //子进程的管道对端
	short busy; //用来标识子进程是否忙碌,0代表非忙碌,1代表忙碌
}process_data_t;

//传输文件协议-小火车
typedef struct{
	int dataLen; //存储buf上要发送的数据数据长度
	char buf[1000]; //火车车厢
}train_t;

//创建子进程
int makeChild(process_data_t*, int);
int childHandle(int);

//发送接收描述符
int sendFd(int, int);
int recvFd(int, int*);

//初始化socket,并开启监听封装
int tcpInit(int*, char*, char*);

//注册监听
int epollInAdd(int, int);

//给客户端发文件
int tranFile(int);

tranFile 给客户端发文件

//给客户端发文件
int tranFile(int newFd)
{
	train_t train;
	//发送文件名
	train.dataLen = strlen(FILENAME);
	strcpy(train.buf, FILENAME);
	send(newFd, &train, 4 + train.dataLen, 0);
	int fd = open(FILENAME, O_RDWR);
	//收文件内容
	while((train.dataLen = read(fd, train.buf, sizeof(train.buf))))
	{
		//发文件内容
		send(newFd, &train, 4 + train.dataLen, 0);
	}
	//发结束符
	send(newFd, &train, 4, 0);
	return 0;
}

sendFd,recvFd 发送接收描述符

int sendFd(int pipeFd, int fd)
{
	struct msghdr msg;
	bzero(&msg, sizeof(msg));
	struct iovec iov[2];
	char buf1[10] = "hello";
	iov[0].iov_base = buf1;
	iov[0].iov_len = 5;
	char buf2[10] = "world";
	iov[1].iov_base = buf2;
	iov[1].iov_len = 5;
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	struct cmsghdr *cmsg;
	int cmsgLen = CMSG_LEN(sizeof(int));
	cmsg = (struct cmsghdr *)calloc(1, cmsgLen);
	cmsg->cmsg_len = cmsgLen;
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	*(int *)CMSG_DATA(cmsg) = fd; //要把传递的描述符告诉内核
	msg.msg_control = cmsg;
	msg.msg_controllen = cmsgLen;
	int ret;
	ret = sendmsg(pipeFd, &msg, 0);
	ERROR_CHECK(ret, -1, "sendmsg");
	return 0;
}

int recvFd(int pipeFd, int *fd)
{
	struct msghdr msg;
	bzero(&msg, sizeof(msg));
	struct iovec iov[2];
	char buf1[10] = "hello";
	iov[0].iov_base = buf1;
	iov[0].iov_len = 5;
	char buf2[10] = "world";
	iov[1].iov_base = buf2;
	iov[1].iov_len = 5;
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	struct cmsghdr *cmsg;
	int cmsgLen = CMSG_LEN(sizeof(int));
	cmsg = (struct cmsghdr *)calloc(1, cmsgLen);
	cmsg->cmsg_len = cmsgLen;
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	msg.msg_control = cmsg;
	msg.msg_controllen = cmsgLen;
	int ret;
	ret = recvmsg(pipeFd, &msg, 0);
	ERROR_CHECK(ret, -1, "recvmsg");
	*fd = *(int *)CMSG_DATA(cmsg);
	return 0;
}

初始化socket,并开启监听封装

int tcpInit(int *sfd,char* ip,char* port)
{
    int socketFd=socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(socketFd,-1,"socket");
    struct sockaddr_in serAddr;
    bzero(&serAddr,sizeof(serAddr));
    serAddr.sin_family=AF_INET;
    serAddr.sin_port=htons(atoi(port));
    serAddr.sin_addr.s_addr=inet_addr(ip);
    int ret;
    ret=bind(socketFd,(struct sockaddr*)&serAddr,sizeof(serAddr));
    ERROR_CHECK(ret,-1,"bind");
    listen(socketFd,10);
    *sfd=socketFd;
    return 0;
}

注册监听

int epollInAdd(int epfd, int fd)
{
	struct epoll_event event;
	event.events = EPOLLIN;
	event.data.fd = fd;
	int ret;
	ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
	ERROR_CHECK(ret, -1, "epoll_ctl");
	return 0;
}

创建子进程,初始化数据结构

//创建子进程,初始化数据结构
int makeChild(process_data_t *p, int processNum)
{
	int i;
	pid_t pid;
	//管道,用于父子之间进程通信
	int fds[2];
	int ret;
	for(i = 0; i < processNum; i++)
	{
		//流管道,全双工
		ret = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
		ERROR_CHECK(ret, -1, "socketpair");
		pid = fork();
		
		//子进程,子进程创建好以后执行 childHandle
		if(0 == pid)
		{
			close(fds[0]);

			childHandle(fds[1]);
		}
		
		//父进程,创建子进程后,记录子进程信息
		//关闭管道写端
		close(fds[1]);
		//子进程pid
		p[i].pid = pid;
		//存储每个子进程的管道对端
		p[i].pipeFd = fds[0];
		p[i].busy = 0;

	}
	return 0;
}

int childHandle(int pipeFd)
{
	int newFd;
	char finishFlag;
	while(1)
	{
		//接收任务,没有任务时,子进程睡觉
		recvFd(pipeFd, &newFd); 
		//给客户端发文件
		tranFile(newFd);
		//关闭连接
		close(newFd);
		//子进程通知父进程完成任务了
		write(pipeFd, &finishFlag, 1);
	}
}

进程池主函数

//传参 IP地址,端口号,进程数
int main(int argc, char* argv[])
{
	if(argc != 4)
	{
		printf("./prtcess_poll_server ip port process_num\n");
		return -1;
	}

	//得到进程数
	int processNum = atoi(argv[3]);
	
	process_data_t *pData = (process_data_t*)calloc(processNum, sizeof(process_data_t));
	
	//创建子进程
	makeChild(pData, processNum);
	int i;

#ifdef DEBUG
	for(i = 0; i < processNum; i++)
	{
		printf("pid = %d, pidFd = %d\n", pData[i].pid, pData[i].pipeFd);
	}
#endif
	
	//初始化socket,并开启监听
	int socketFd;
	tcpInit(&socketFd, argv[1], argv[2]);
	
	int epfd = epoll_create(1);
	struct epoll_event *evs;
	evs = (struct epoll_event*)calloc(processNum + 1, sizeof(struct epoll_event));
	
	//把socketFd添加到epoll中监听
	epollInAdd(epfd, socketFd);
	
	//监听管道读端
	for(i = 0; i < processNum; i++)
	{
		//添加管道读端到epoll中,当子进程非忙碌时,写管道
		//父进程就知道子进程完成任务,可以再次分配任务
		//注册监听每一个子进程的管道对端
		epollInAdd(epfd, pData[i].pipeFd);
	}
	
	int readyFdCount, newFd, j;
	char noBusyflag;
	while(1)
	{
		readyFdCount = epoll_wait(epfd, evs, processNum + 1, -1);
		for(i = 0; i < readyFdCount; ++i)
		{
			if(evs[i].data.fd == socketFd)
			{
				//接收客户端请求
				newFd = accept(socketFd, NULL, NULL);
				
				//找非忙碌的子进程
				for(j = 0; j < processNum; j++)
				{
					if(0 == pData[j].busy)
					{
						//把任务发给对应的子进程(发送描述符)
						sendFd(pData[j].pipeFd, newFd);
						//子进程标识为忙碌
						pData[j].busy = 1;
						printf("%d pid is busy\n", pData[j].pid);
						break;
					}
				}
				close(newFd);
			}
			for(j = 0; j < processNum; j++)
			{
				if(evs[i].data.fd == pData[j].pipeFd)
				{
					//受到子进程的通知
					read(pData[j].pipeFd, &noBusyflag, 1);
					//子进程设置为非忙碌
					pData[j].busy = 0;
					printf("%d pid is not busy\n", pData[j].pid);
					break;
				}
			}
		}
	}

	return 0;
}

客户端

int main(int argc, char* argv[])
{
    ARGS_CHECK(argc, 3);
    int socketFd = socket(AF_INET, SOCK_STREAM, 0);
    ERROR_CHECK(socketFd, -1, "socket");
    struct sockaddr_in serAddr;
    bzero(&serAddr, sizeof(serAddr));
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(atoi(argv[2]));
    serAddr.sin_addr.s_addr = inet_addr(argv[1]);
    int ret;
    ret = connect(socketFd, (struct sockaddr*)&serAddr, sizeof(serAddr));
    ERROR_CHECK(ret, -1, "connect");
	int fd;
	int dataLen;
	char buf[1000] = {0};
	recv(socketFd, &dataLen, 4, 0);
	//接收文件名
	recv(socketFd, buf, dataLen, 0);
	printf("%s\n", buf);
	if(0 != strcmp(buf, "file"))
	{
		return -1;
	}
	fd = open(buf, O_CREAT|O_RDWR, 0666);
	ERROR_CHECK(fd, -1, "open");
	while(1)
	{
		recv(socketFd, &dataLen, 4, 0);
		if(dataLen > 0)
		{
			recv(socketFd, buf, dataLen, 0);
			write(fd, buf, dataLen);
		}else{
			break;
		}
	}
	close(fd);
	close(socketFd);
}

使用两个窗口分别启动 服务器(进程池) 和 客户端

在服务器目录下创建一个文件 file 里边写入一点字符

服务器(进程池)输入: ./server + 本地IP地址 + 打算启动的子进程数
客户端输入: ./client + 本地IP地址

服务器打印:

7785 pid is busy
7785 pid is not busy、

然后卡住等待

客户端打印:

file

并且收到服务器发来的 file 文件

你可能感兴趣的:(Linux_网络编程_7.进程池_01进程池概述,进程池初步搭建步骤(小文件传输实现))