线程池,就是在服务器启动的时候就已经创建好。在服务器中采用多线程并发处理客户端请求的时候,当客户端连接服务器发送请求的时候,这时如果服务器已经创建好了几个线程,那么就不需要在要使用线程的时候才去动态创建,这时只需要让线程池中的某一个空闲着的线程来处理该客户端请求即可。
模拟内核中线程池的实现
直接上代码。。。。。。
服务器端:ser.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <assert.h>
#include <signal.h>
#define MAX 10
int fd[MAX] = {0};//存放accept得到的客户端套接字clifd。每次线程池中的空闲着的子线程都到这个数组中找一个clifd进行处理客户端的请求。
sem_t sem;
pthread_mutex_t mutex;
void initfd()
{
int i=0;
for(;i<MAX;i++)
{
fd[i] = -1;
}
}
void addfd(int c)
{
int i=0;
for(;i<MAX;i++)
{
if(-1 == fd[i])
{
fd[i] = c;
return;
}
}
printf("fd is full\n");
}
int getfd()
{
int i=0;
for(;i<MAX;i++)
{
if(-1 != fd[i])
{
int c = fd[i];
fd[i] = -1;
return c;
}
}
return -1;
}
void* pthread_fun(void *arg)
{
while(1)
{
sem_wait(&sem);
pthread_mutex_lock(&mutex);
int c = getfd();
assert(c!=-1);
pthread_mutex_unlock(&mutex);
if(c==-1)
{
continue;
}
while(1)
{
char buff[128] = {0};
int res = recv(c,buff,127,0);
assert(res != -1);
if(0 == res)
{
printf("one client offline\n");
break;
}
printf("%s\n",buff);
send(c,"ok",2,0);
}
}
}
void sig_fun(int arg)
{
pthread_mutex_destroy(&mutex);
exit(0);
}
int main()
{
signal(SIGINT,sig_fun);
initfd();
sem_init(&sem,0,0);
pthread_mutex_init(&mutex,NULL);
pthread_t id;
int i=0;
for(;i<3;i++)
{
pthread_create(&id,NULL,pthread_fun,NULL);
}
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -1);
listen(sockfd,5);
while(1)
{
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
continue;
}
pthread_mutex_lock(&mutex);
addfd(c);
pthread_mutex_unlock(&mutex);
sem_post(&sem);
}
return 0;
}
有客户端请求的时候,主线程就accept接受客户端的请求,并将得到的客户端套接字存储在一个数组中,当线程池中的哪个线程空闲着,就去竞争得到这个客户端套接字,并对客户端的请求进行处理。 如果所有的子线程都在忙着处理其他的客户端,那么新连接上的客户端就先在这个数组中等待,当有子线程处理完了一个线程后,再来处理新连接的客户端请求。
客户端:cli.c 简单的和服务器进行收发数据的沟通。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in caddr;
memset(&caddr,0,sizeof(caddr));
caddr.sin_family = AF_INET;
caddr.sin_port = htons(6000);
caddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = connect(sockfd,(struct sockaddr*)&caddr,sizeof(caddr));
if(-1 == res)
{
printf("connect error\n");
}
while(1)
{
char buff[128] = {0};
fgets(buff,127,stdin);
buff[strlen(buff)-1] = '\0';
if(strcmp(buff,"end") == 0)
{
close(sockfd);
break;
}
send(sockfd,buff,strlen(buff),0);
memset(buff,0,128);
recv(sockfd,buff,127,0);
printf("%s\n",buff);
}
return 0;
}
上述程序只是简单的说明了一下线程池的概念。 显然按照上述服务器的处理方式,效率很低,因为每个线程在处理一个客户端的时候,若这个客户端一直没有下线,那么这个子线程就干不了其它的事情了。当有多个客户端连接服务器的时候,会导致很多客户端都得不到及时的处理。
解决服务器效率低的方法,可以用libevent将所有客户端的请求都绑定为一个事件监听起来。采用半同步半异步的方式让子线程去处理事件,这样可以提高效率。后续提供这种方法的实现。