典型C/S模式___线程池实现

版权声明:私藏源代码是违反人性的罪恶行为!博客转载无需告知,学无止境。 https://blog.csdn.net/qq_41822235/article/details/84590691

一、多线程和线程池的区别

线程池 多线程
同时启动若干个线程,持续存在,时刻准备处理请求 来一个请求启动一个线程
响应时间 响应时间较长

二、线程池实现思路

  • 主线程生成客户端套接字(资源);函数线程使用客户端套接字(资源),没有可用资源时,函数线程必须阻塞。
  • 将客户端套接字放入队列和从队列中取出客户端套接字这两个动作必须一次全部完成,不能被打断,不能同时发生。比如说:主线程正向队列放入客户端套接字而函数线程正在从队列中取用客户端套接字,不能同时发生;又或者好几个函数线程都正从队列中取客户端套接字,不能同时发生,不然可能取得的套接字是同一个。 

通过空一个位置就可以实现不添加标记区分队空和队满的情况: rear始终指向空闲的位置。

 2.1 服务器端

#include<sys/socket.h> //socket()bind()listen()accept()recv()send()
#include<arpa/inet.h> //inet_addr()
#include<netinet/in.h> //htons()
#include<pthread.h> //pthread_create()
#include<assert.h> //assert()
#include<stdio.h> //printf()
#include<semaphore.h> //sem_init() sem_post() sem_wait()
#include<unistd.h> //close()
#include<string.h> //memset()
#define NUM 8    //队列大小为8个元素

pthread_mutex_t mutex; //互斥锁

typedef struct Queue
{
    int cliLink[NUM];
    int front;
    int rear;
}Queue;

Queue que; //存放客户端套接字的队列
sem_t sem;    //信号量,用于记录套接字资源数目

void initQue(); //初始化队列que
int insert(int c); //将客户端套接字放入队列que中
int getCli(); //从队列que中取出套接字
void *fun(void *arg); //线程回调函数

int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    assert(-1 != sockfd);

    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(-1 != res);

    listen(sockfd, 5); //维护5个连接请求

    //启动三3个线程并且持续存在处理连接请求
    for(int i = 0; i < 3; ++i)
    {
	    pthread_t tid;
	    pthread_create(&tid, NULL, fun, 0); //使用默认attri 不带参数
    }

    initQue(); //初始化队列que
    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); //v操作
    }

    close(sockfd); //关闭套接字
}

void initQue() //队列初始化 不含任何元素
{
    for(int i = 0; i < NUM; ++i)
	que.cliLink[i] = -1;
    que.front = que.rear = 0;
}

//将客户端套接字放入维护的队列中 成功返回0 失败返回-1
int insert(int c)
{
    int res;
    pthread_mutex_lock(&mutex);
    
    if(que.front != (que.rear+1)%NUM)
    {
	    que.cliLink[que.rear] = c;
	    que.rear = (que.rear+1) % NUM;
	    res = c;
    }else //队列已满
	{
        res = -1;
    }

    pthread_mutex_unlock(&mutex);
    return res == -1 ? -1 : 0; //队列已满 插入失败,队列未满,插入成功
}

//从队列中取用套接字描述符
int getCli()
{
    pthread_mutex_lock(&mutex);
    
    int c = que.cliLink[que.front];
    que.front = (que.front + 1) % NUM;

    pthread_mutex_unlock(&mutex);
    return c;
}

//回调函数格式固定
void *fun(void* arg)
{
    while(1)
    {
	    sem_wait(&sem); //惊群现象
	    int c = getCli();
	    while(1)
	    {
            char buf[128] = {'\0'};
	        int n = recv(c, buf, 127, 0);
	    
	        if(n <= 0 || 0 == strcmp(buf, "end"))
	        {
		        printf("client is terminated!\n");
		        close(c);
		        break;
	        }

	        printf("%d %s\n", c, buf);
	        send(c, "OK", 2, 0);
	    }
    }
}

2.2 客户端

#include<stdio.h> //printf()
#include<assert.h> //assert()
#include<sys/socket.h> //socket()connect()recv()send()
#include<netinet/in.h> //inet_addr()
#include<arpa/inet.h> //htons()
#include<string.h> //strlen()

int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    assert(-1 != sockfd);
    
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(6000);
    ser.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    int res = connect(sockfd, (struct sockaddr*)&ser, sizeof(ser));
    assert(-1 != res);

    while(1)
    {
	    printf("please input: ");
	    fflush(stdout);
	    char buf[128] = {'\0'};
	    scanf("%s", buf);

	    send(sockfd, buf, strlen(buf), 0);
	
	    char s2[128] = {'\0'};
	    recv(sockfd, s2, 127, 0);
	    printf("%d: %s\n", sockfd, s2);
	    
	    if(0 == strcmp(buf, "end"))
	    {
            close(sockfd);
	        break;
	    }
    }
}

2.3 运行结果

运行结果说明:线程池中常备三个函数线程用于处理连接请求,当第四个客户端的连接请求发送给服务器的时候,服务器会接收的,因为通过listen()函数设置的维护的连接数目为5,但是没有函数线程空闲下来处理新的客户端套接字。任何一个客户端结束,4号客户端马上就会有返回。

在3号客户端结束后,4号客户端立刻就有了返回。因为空闲的函数线程会一直去检查队列中是否存在套接字。这就是惊群现象。 

猜你喜欢

转载自blog.csdn.net/qq_41822235/article/details/84590691