今天我们来看一下线程池的概念。首先什么是池呢?作用是什么?
池:初始时,申请比刚开始要使用的资源大的多的资源空间。接下来使用时,直接从池中获取资源。
线程池:多线程存在如果客户端链接,创建一个新的线程,客户端关闭,释放线程。服务器更多时间消耗在创建线程、释放线程。对于业务逻辑的处理,就会较少。所以,我们可以用线程池来改善问题。
总的来说线程池就是在服务器运行初始时,创建n 个线程,将这个n 个线程用池管理起来,主线程负责监听套接字、接受客户连接。工作线程负责和客户端具体通讯。当有用户连接时,从线程池中选取一个线程为其服务,客户端关闭以后,服务器就将线程又放回到池中。
线程池的实现:(这里以三条为例)
1、主线程执行先创建3 条线程;
2、主线程等待客户连接,3 条函数线程因为信号量的P 操作阻塞运行;
3、主线程接受到客户连接后,通过信号量的V 操作通知一个函数线程和客户端通讯。
现在就有一个问题:主线程怎样将连接的文件描述符传递给函数线程??
答:全局数组作为等待函数线程处理的文件描述符的等待队列。
下面我们来看看代码实现:
ser.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <semaphore.h>
#define MAX 10
int fds[MAX];
sem_t sem;
void Init_fds()
{
int i = 0;
for(; i < MAX; ++i)
{
fds[i] = -1;
}
}
int Add_fds(int c)
{
int i = 0;
for(; i < MAX; ++i)
{
if(fds[i] == -1)
{
fds[i] = c;
return 1;
}
}
if(i == MAX)
{
return 0;
}
}
void Del_fds(int i)
{
for(; i < MAX - 1; ++i)
{
fds[i] = fds[i + 1];
if(fds[i+1] == -1)
{
break;
}
}
fds[MAX - 1] = -1;
}
int Get_fds()
{
pthread_mutex_lock(&mutex);
int i = 0;
for(; i < MAX; ++i)
{
if(fds[i] != -1)
{
int c = fds[i];
Del_fds(i);
return c;
}
}
}
void *pthread_fun(void *arg)
{
while(1)
{
sem_wait(&sem);
int c = Get_fds();
if(c == -1)
{
continue;
}
while(1)
{
char buff[128] = {0};
int n = recv(c, buff, 127, 0);
if(n <= 0)
{
close(c);
break;
}
printf("cli: %d ; buff: %s\n", c, buff);
send(c, "OK", 2, 0);
}
}
}
int main(int argc, char *argv[])
{
Init_fds();
sem_init(&sem, 0, 0);
int i = 0;
for(; i < 3; ++i)
{
pthread_t id;
int res = pthread_create(&id, NULL, pthread_fun, NULL);
assert(res == 0);
}
int sockfd = socket(AF_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(6000);
ser.sin_addr.s_addr = inet_addr("192.168.1.120");
int res = bind(sockfd, (struct sockaddr *)&ser, sizeof(ser));
assert(res != -1);
listen(sockfd, 5);
while(1)
{
socklen_t len = sizeof(cli);
int c = accept(sockfd, (struct sockaddr*)&cli, &len);
if(c < 0)
{
printf("error\n");
continue;
}
if(!Add_fds(c))
{
send(c, "please wait...", strlen("please wait..."), 0);
close(c);
continue;
}
sem_post(&sem);
}
return 0;
}
cli.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
void main()
{
int sockfd = socket(AF_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("192.168.1.120");
int res = connect(sockfd, (struct sockaddr*)&ser, sizeof(ser));
assert(res != -1);
while(1)
{
printf("please input: ");
fflush(stdout);
char buff[128] = {0};
fgets(buff, 128, stdin);
if(strncmp(buff, "end", 3) == 0)
{
close(sockfd);
break;
}
send(sockfd, buff, strlen(buff) - 1, 0);
memset(buff, 0, 128);
recv(sockfd, buff, 127, 0);
printf("%s\n", buff);
}
}
执行结果:
可以看到我们可以开启多个客户端,并且服务器可以连接并且进行数据接受发送,不过当开启第四个客户端时,服务器端并没有显示发送的数据,因为我们这里三个函数线程全部被占用,没有多余的来给第四个客户端服务,所以它就一直阻塞在recv,不过只要结束一个客户端,第四个客户端发送的数据便可以显示。
当我输入“end”结束掉第一个客户端,可以看到第四个客户端的数据便显示出来了。
不过我们这个程序还是不安全的,万一当你在get的时候进行了add,这个时候有可能会造成错误。所以我们做出如下更改:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <semaphore.h>
#define MAX 10
int fds[MAX];
sem_t sem;
pthread_mutex_t mutex;
void Init_fds()
{
int i=0;
for(;i<MAX;i++)
{
fds[i]=-1;
}
}
int Add_fds(int c)
{
pthread_mutex_lock(&mutex);
int i=0;
for(;i<MAX;i++)
{
if(fds[i]==-1)
{
fds[i]=c;
pthread_mutex_unlock(&mutex);
return 1;
}
}
if(i==MAX)
{
pthread_mutex_unlock(&mutex);
return 0;
}
}
void Del_fds(int i)
{
for(;i<MAX-1;i++)
{
fds[i]=fds[i+1];
if(fds[i+1]==-1)
{
break;
}
}
fds[MAX-1]=-1;
}
int Get_fds()
{
pthread_mutex_lock(&mutex);
int i=0,c=-1;
for(;i<MAX;i++)
{
if(fds[i]!=-1)
{
c=fds[i];
Del_fds(i);
break;
}
}
pthread_mutex_unlock(&mutex);
return c;
}
void *pthread_fun(void *arg)
{
while(1)
{
sem_wait(&sem);
int c=Get_fds();
if(c==-1)
{
printf("Not Found\n");
continue;
}
while(1)
{
char buff[128]={0};
int n=recv(c,buff,127,0);
if(n<=0)
{
close(c);
break;
}
printf("buff::%s\n",buff);
send(c,"OK",2,0);
}
}
}
int main()
{
Init_fds();
sem_init(&sem,0,0);
pthread_mutex_init(&mutex,NULL);
int i=0;
for(;i<3;i++)
{
pthread_t id;
int res=pthread_create(&id,NULL,pthread_fun,NULL);
assert(res==0);
}
int sockfd=socket(AF_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(6000);
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);
while(1)
{
socklen_t len=sizeof(cli);
int c=accept(sockfd,(struct sockaddr*)&cli,&len);
if(c<0)
{
printf("errror\n");
continue;
}
if(!Add_fds(c))
{
send(c,"please wait...",strlen("please wait..."),0);
close(c);
continue;
}
sem_post(&sem);
}
return 0;
}
客户端代码不变。