在前面的文章中我们是通过动态创建子进程(函数线程)来实现并发服务器的,这样做的缺点如下:
为了解决这些问题,我们使用了进程池和线程池这两种技术。
httpd的进程池:
我们可以清楚地看到,当我们开启httpd服务后,查看进程时,有8个父进程都是9863的子进程,这就是httpd创建的进程池。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static const int CONTROL_LEN = CMSG_LEN(sizeof(int));
/*文件描述符发送函数*/
void send_fd(int fd, int fd_to_send)
{
struct iovec iov[1];
struct msghdr msg;
char buf[0];
iov[0].iov_base = buf;
iov[0].iov_len = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
struct cmsghdr cm;
cm.cmsg_len = CONTROL_LEN;
cm.cmsg_level = SOL_SOCKET;
cm.cmsg_type = SCM_RIGHTS;
*(int *)CMSG_DATA(&cm) = fd_to_send;
msg.msg_control = &cm;
msg.msg_controllen = CONTROL_LEN;
sendmsg(fd, &msg, 0);
}
/*文件描述符接收函数*/
int recv_fd(int fd)
{
struct iovec iov[1];
struct msghdr msg;
char buf[0];
iov[0].iov_base = buf;
iov[0].iov_len = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
struct cmsghdr cm;
msg.msg_control = &cm;
msg.msg_controllen = CONTROL_LEN;
recvmsg(fd, &msg, 0);
int fd_to_read = *(int*)CMSG_DATA(&cm);
return fd_to_read;
}
int main()
{
int pipefd[2];
int res = socketpair(PF_UNIX, SOCK_DGRAM, 0, pipefd);//创建管道为发送文件描述符做准备
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
assert(sockfd != -1);
struct sockaddr_in ser, cli;
ser.sin_family = AF_INET;
ser.sin_port = htons(6500);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
res = bind(sockfd, (struct sockaddr*)&ser, sizeof(ser));
assert(res != -1);
listen(sockfd, 5);
int ret;
int i = 0;
/*接收连接前先创建进程池*/
for(; i < 3; i++)
{
ret = fork();
if(ret == 0)
{
break;
}
}
if(ret != 0)
{
close(pipefd[0]);//父进程关闭读通道
while(1)
{
socklen_t len = sizeof(cli);
int c = accept(sockfd, (struct sockaddr*)&cli, &len);
if(c == -1)
{
printf("accept error\n");
continue;
}
send_fd(pipefd[1], c);//将接收到的文件描述符写入管道,供子进程读取
close(c);
}
}
if(ret == 0)
{
while(1)
{
close(pipefd[1]);//子进程关闭写通道
int c = recv_fd(pipefd[0]);//读取管道中的文件描述符
while(1)
{
char recvbuff[128] = {0};
res = recv(c, recvbuff, 127, 0);
if(res <= 0)
{
printf("%ddisclient\n", c);
close(c);
break;
}
printf("%d: %s\n", c, recvbuff);
send(c, "OK", 2, 0);
}
}
}
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
pthread_mutex_t mutex;
int clilink[8];//已经被接收但还未被处理的连接请求
sem_t sem;
/*初始化连接数组*/
void InitCliLink()
{
int i = 0;
for(; i < 8; i++)
{
clilink[i] = -1;
}
}
/*插入新接收的连接,等待处理*/
int Insert(int c)
{
pthread_mutex_lock(&mutex);//插入的同时时不能获取,用锁控制
int i = 0;
for(; i < 8; i++)
{
if(clilink[i] == -1)
{
clilink[i] = c;
break;
}
}
pthread_mutex_unlock(&mutex);
if(i >= 8)
return -1;
return 0;
}
/*获取连接,进行处理*/
int GetCli()
{
pthread_mutex_lock(&mutex);//获取时不能插入
int i = 0;
int c = clilink[0];
for(; i < 7; i++)
{
clilink[i] = clilink[i+1];
}
pthread_mutex_unlock(&mutex);
return c;
}
void *pthread_fun(void *arg);
int main()
{
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
assert(sockfd != -1);
struct sockaddr_in ser, cli;
memset(&ser, 0, sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(6500);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd, (struct sockaddr*)&ser, sizeof(ser));
assert(res != -1);
listen(sockfd, 5);
/*创建线程池*/
int i = 0;
for(; i < 3; i++)
{
pthread_t id;
res = pthread_create(&id, NULL, pthread_fun, NULL);
}
InitCliLink();
sem_init(&sem, 0, 0);//初始化信号量值为0,使得函数线程阻塞
while(1)
{
int len = sizeof(cli);
int c = accept(sockfd, (struct sockaddr*)&cli, &len);
if(c < 0)
{
continue;
}
if(Insert(c) == -1)
{
close(c);
continue;
}
sem_post(&sem);//信号量+1,函数线程可以开始获取数组中等待的连接进行处理
}
}
/*线程函数*/
void *pthread_fun(void *arg)
{
while(1)
{
sem_wait(&sem);
int c = GetCli();
while(1)
{
char buff[128] = {0};
int n = recv(c, buff, 127, 0);
if(n <= 0)
{
close(c);
break;
}
printf("%d: %s\n",c, buff);
send(c, "ok", 2, 0);
}
}
}