进程池与线程池---实现服务器

1.池的概念
由于服务器的硬件资源“充裕”,那么提高服务器性能的一个很直接的方法就是以空间换时间,即“浪费”服务器的硬件资源,以换取其运行效率。这就是池的概念。池是一组资源的集合,这组资源在服务器启动之初就完全被创建并初始化,这称为静态资源分配。当服务器进入正式运行阶段,即开始处理客户请求的时候,如果它需要相关的资源,就可以直接从池中获取,无需动态分配。很显然,直接从池中取得所需资源比动态分配资源的速度要快得多,因为分配系统资源的系统调用都是很耗时的。当服务器处理完一个客户连接后,可以把相关的资源放回池中,无需执行系统调用来释放资源。从最终效果来看,池相当于服务器管理系统资源的应用设施,它避免了服务器对内核的频繁访问。

2.池可以分为多种,常见的有内存池、进程池、线程池和连接池。

  • 内存池

内存池是一种内存分配方式。通常我们习惯直接使用new、malloc等系统调用申请分配内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。

内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。

  • 进程池和线程池

进程池和线程池相似,所以这里我们以进程池为例进行介绍。如没有特殊声明,下面对进程池的描述也适用于线程池。

进程池是由服务器预先创建的一组子进程,这些子进程的数目在 3~10 个之间(当然这只是典型情况)。线程池中的线程数量应该和 CPU 数量差不多。

进程池中的所有子进程都运行着相同的代码,并具有相同的属性,比如优先级、 PGID 等。

当有新的任务来到时,主进程将通过某种方式选择进程池中的某一个子进程来为之服务。相比于动态创建子进程,选择一个已经存在的子进程的代价显得小得多。至于主进程选择哪个子进程来为新任务服务,则有两种方法:

1)主进程使用某种算法来主动选择子进程。最简单、最常用的算法是随机算法和 Round Robin (轮流算法)。

2)主进程和所有子进程通过一个共享的工作队列来同步,子进程都睡眠在该工作队列上。当有新的任务到来时,主进程将任务添加到工作队列中。这将唤醒正在等待任务的子进程,不过只有一个子进程将获得新任务的“接管权”,它可以从工作队列中取出任务并执行之,而其他子进程将继续睡眠在工作队列上。

当选择好子进程后,主进程还需要使用某种通知机制来告诉目标子进程有新任务需要处理,并传递必要的数据。最简单的方式是,在父进程和子进程之间预先建立好一条管道,然后通过管道来实现所有的进程间通信。在父线程和子线程之间传递数据就要简单得多,因为我们可以把这些数据定义为全局,那么它们本身就是被所有线程共享的。

这里写图片描述

3.线程池主要用于:

1)需要大量的线程来完成任务,且完成任务的时间比较短。 比如WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大。但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

2)对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

3)接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。

4.多进程池的服务

每次请求都生成新进程其实必要性并不大,大部分并发服务器处理的每秒并发量一般最多就在几百左右,因此一般几个或者十几个进程循环提供服务就可以hold住,为了减少每次请求建立新进程的成本,我们的前辈又发明了多进程池(prefork)的模式,预先生成若干进程来处理请求。

见过两种多进程池的实现,一种是父进程只管listen,子进程对每个请求accept。另一种是父进程负责accept,然后把accept后得到的confd句柄传递给子进程。

这里我先说下第一种实现:

进程池tcp_server.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/wait.h>
#include<string.h>
#include<time.h>
#define MAX_CONNECTION 2

int main(int argc, char** argv){
    int fd = -1;
    time_t ticks;
    pid_t pid; 
    pid_t pids[10];//后面将会一次产生十个子进程,存放每个子进程的id 
    int port = 99999;//指定端口号为99999

    //父进程创建监听套接字
    fd = socket(AF_INET,SOCK_STREAM,0);

   //绑定套接字与ip地址和端口号
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    int size = sizeof(struct sockaddr);

    bind(fd,(struct sockaddr*)&addr,size);

    //父进程监听客户端的请求
    listen(fd,MAX_CONNECTION);

    //创建子进程
    int i;
    for(i = 0; i< 10;i++){

        pid = fork();
        if(pid > 0){                                                                                         continue;//父进程继续循环创建子进程
        }

        //子进程处理客户端的请求
        pids[i] = pid;
        struct sockaddr_in client_addr;
        while (1){
            memset(&client_addr, 0, sizeof (client_addr));
            char buf[1024];
            memset(&buf,0,sizeof(buf));

           //创建new_socket来处理客户端的请求
            int client_fd = accept(fd, (struct sockaddr *) &client_addr, &size);
            close(fd);//关闭监听文件(不必要的文件)
            ticks = time (0);

            //打印时间
            snprintf(buf, sizeof(buf), "%s", ctime(&ticks));

           //将buf里的内容写到网络中
            write(client_fd, buf, sizeof(buf));
            sleep(3600);

            //客户端请求结束,关闭文件
            close(client_fd);
        }
    }
    close(fd);
    //等待回收子进程
    for(i = 0; i< 10;i++){
        int status;
        if(pids[i] < 0){
            continue;
        }
        waitpid(pids[i],&status,0);
    }
    return 0;
}

对应的 tcp_client

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>

void static Usage(const char* proc)
{
    printf("Usage: %s [local_ip] [local_port]\n",proc);
}


//./tcp_clinet  servet_ip  servet_port
int main(int argc,char* argv[])
{
    if(argc!=3){
        Usage(argv[1]);
        exit(1);
    }

    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0 ){
        perror("socket");
        exit(2);
    }
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[2]));
    server.sin_addr.s_addr = inet_addr(argv[1]);

    if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0){
        perror("connect");
        exit(3);
    }

    char buf[1024];
    while(1){
        printf("Please Enter$");
        fflush(stdout);

        ssize_t s = read(0,buf,sizeof(buf)-1);
        if(s>0){
            buf[s-1] = 0;
            write(sock,buf,strlen(buf));
            s = read(sock,buf,sizeof(buf)-1);
            if(s>0){
                buf[s] = 0;
                printf("servetr echo#:%s\n",buf);
        }
    }


    return 0;
    }
}

测试结果:
这里写图片描述

这里的0表示本地地址

显示进程
这里写图片描述

可以看见有两个进程

5.多线程池服务

没看懂。。。呜呜呜
线程池epoll

#include <pthread.h> 
#include <string.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <stdio.h> 
#include <fcntl.h>

#include <arpa/inet.h> 
#include <sys/epoll.h> 
#include <sys/errno.h>
#include <sys/socket.h> 

#define THREAD_MAX 20 
#define LISTEN_MAX 20 
#define SERVER_IP "127.0.0.1" 

typedef struct {
    char ip4[128]; 
    int port; 
    int fd; 
} LISTEN_INFO; 

//服务器参数 
static LISTEN_INFO s_listens[LISTEN_MAX]; 

//线程池参数 
static unsigned int s_thread_para[THREAD_MAX][8];//线程参数 
static pthread_t s_tid[THREAD_MAX];//线程ID 
pthread_mutex_t s_mutex[THREAD_MAX];//线程锁 

//私有函数 
static int init_thread_pool(void);//初始化数据
static int init_listen4(char *ip4, int port, int max_link); //初始化监听

//线程函数 
void * test_server4(unsigned int thread_para[]);

//设置文件描述符为NonBlock
bool setNonBlock(int fd)
{
    int flags = fcntl(fd, F_GETFL, 0);
    flags |= O_NONBLOCK;
    if(-1 == fcntl(fd, F_SETFL, flags))
        return false;
    return true;
}

int main(int argc, char *argv[])//客户端驱动 
{ 
    //临时变量 
    int i, j, rc; 

    int sock_listen; //监听套接字 
    int sock_cli; //客户端连接 
    int listen_index; 

    int epfd; 
    int nfds; 
    struct epoll_event ev; 
    struct epoll_event events[LISTEN_MAX];

    socklen_t addrlen; //地址信息长度 
    struct sockaddr_in addr4; //IPv4地址结构 

    //线程池初始化 
    rc = init_thread_pool(); 
    if (0 != rc) exit(-1); 

    //初始化服务监听 
    for(i = 0; i < LISTEN_MAX; i++) { 
        sprintf(s_listens[i].ip4, "%s", SERVER_IP); 
        s_listens[i].port = 40000 + i; 
        //创建监听 
        rc = init_listen4(s_listens[i].ip4, s_listens[i].port, 64); 
        if (0 > rc) { 
            fprintf(stderr, "无法创建服务器监听于%s:%d\r\n", s_listens[i].ip4, s_listens[i].port); 
            exit(-1); 
        } else {
            fprintf(stdout, "已创建服务器监听于%s:%d\r\n", s_listens[i].ip4, s_listens[i].port);  
        } 
        s_listens[i].fd = rc; 
    } 

    //设置集合 
    epfd = epoll_create(8192); 
    for (i = 0; i < LISTEN_MAX; i++) { 
        //加入epoll事件集合 
        ev.events = EPOLLIN | EPOLLET;
        ev.data.u32 = i;//记录listen数组下标 
        if (epoll_ctl(epfd, EPOLL_CTL_ADD, s_listens[i].fd, &ev) < 0) { 
            fprintf(stderr, "向epoll集合添加套接字失败(fd =%d)\r\n", rc); 
            exit(-1); 
        } 
    } 

    //服务循环 
    for( ; ; ) { 
        //等待epoll事件 
        nfds = epoll_wait(epfd, events, LISTEN_MAX, -1); 
        //处理epoll事件 
        for(i = 0; i < nfds; i++) { 
            //接收客户端连接 
            listen_index = events[i].data.u32; 
            sock_listen = s_listens[listen_index].fd; 
            addrlen = sizeof(struct sockaddr_in); 
            bzero(&addr4, addrlen); 

            sock_cli = accept(sock_listen, (struct sockaddr *)&addr4, &addrlen); 
            if(0 > sock_cli) { 
                fprintf(stderr, "接收客户端连接失败\n"); 
                continue; 
            } else {
                char *myIP = inet_ntoa(addr4.sin_addr);
                printf("accept a connection from %s...\n", myIP); 
            } 

            setNonBlock(sock_cli);
            //查询空闲线程对 
            for(j = 0; j < THREAD_MAX; j++) { 
                if (0 == s_thread_para[j][0]) break; 
            } 
            if (j >= THREAD_MAX) { 
                fprintf(stderr, "线程池已满, 连接将被放弃\r\n"); 
                shutdown(sock_cli, SHUT_RDWR); 
                close(sock_cli); 
                continue; 
            } 
            //复制有关参数 
            s_thread_para[j][0] = 1;//设置活动标志为"活动" 
            s_thread_para[j][1] = sock_cli;//客户端连接 
            s_thread_para[j][2] = listen_index;//服务索引 
            //线程解锁 
            pthread_mutex_unlock(s_mutex + j); 
        }//end of for(i;;) 
    }//end of for(;;) 

    exit(0); 
} 

static int init_thread_pool(void) 
{ 
    int i, rc; 

    //初始化线程池参数 
    for(i = 0; i < THREAD_MAX; i++) { 
        s_thread_para[i][0] = 0;//设置线程占用标志为"空闲" 
        s_thread_para[i][7] = i;//线程池索引 
        pthread_mutex_lock(s_mutex + i);// 这个地方为什么要加锁?不加锁创建监听有时会不成功 
    } 

    //创建线程池 
    for(i = 0; i < THREAD_MAX; i++) { 
        rc = pthread_create(s_tid + i, 0, (void* (*)(void *))test_server4, (void *)(s_thread_para[i])); 
        if (0 != rc) { 
            fprintf(stderr, "线程创建失败\n"); 
            return(-1); 
        } 
    } 

    //成功返回 
    return(0); 
} 

static int init_listen4(char *ip4, int port, int max_link) 
{ 
    //临时变量 
    int sock_listen4; 
    struct sockaddr_in addr4; 
    unsigned int optval; 
    struct linger optval1; 

    //初始化数据结构 
    bzero(&addr4, sizeof(addr4)); 
    //inet_pton将点分十进制IP转换为整数
    inet_pton(AF_INET, ip4, &(addr4.sin_addr)); 
    addr4.sin_family = AF_INET; 
    //htons将无符号short从主机字节序(x86:Big-Endian)转换为网络字节序
    addr4.sin_port = htons(port); 

    //创建流类型的SOCKET 
    sock_listen4 = socket(AF_INET, SOCK_STREAM, 0); 
    if (0 > sock_listen4) {
        fprintf(stderr, "创建socket异常, sock_listen4:%d\n", sock_listen4);
        perror("创建socket异常");
        return(-1); 
    }

    //设置SO_REUSEADDR选项(服务器快速重起) 
    optval = 0x1; 
    setsockopt(sock_listen4, SOL_SOCKET, SO_REUSEADDR, &optval, 4); 

    //设置SO_LINGER选项(防范CLOSE_WAIT挂住所有套接字) 
    optval1.l_onoff = 1; 
    optval1.l_linger = 60; 
    setsockopt(sock_listen4, SOL_SOCKET, SO_LINGER, &optval1, sizeof(struct linger)); 

    if (0 > bind(sock_listen4, (struct sockaddr *)&addr4, sizeof(addr4))) { 
        fprintf(stderr, "bind socket异常, sock_listen4:%d\n", sock_listen4);
        perror("bind socket异常");
        close(sock_listen4);
        return(-1); 
    } 

    if (0 > listen(sock_listen4, max_link)) { 
        fprintf(stderr, "listen socket异常, sock_listen4:%d\n", sock_listen4);
        perror("listen socket异常");
        close(sock_listen4); 
        return(-1); 
    } 

    return (sock_listen4); 
} 

void * test_server4(unsigned int thread_para[]) 
{ 
    //临时变量 
    int sock_cli; //客户端连接 
    int pool_index; //线程池索引 
    int listen_index; //监听索引 

    char buff[32768]; //传输缓冲区 
    int i, j, len; 
    char *p; 

    //线程脱离创建者 
    pthread_detach(pthread_self()); 
    pool_index = thread_para[7]; 

wait_unlock: 
    pthread_mutex_lock(s_mutex + pool_index);//等待线程解锁 

    //线程变量内容复制 
    sock_cli = thread_para[1];//客户端连接 
    listen_index = thread_para[2];//监听索引 

    //接收请求 
    len = recv(sock_cli, buff, sizeof(buff), MSG_NOSIGNAL); 
    printf("%s\n", buff);

    //构造响应 
    p = buff; 
    //HTTP头 
    p += sprintf(p, "HTTP/1.1 200 OK\r\n"); 
    p += sprintf(p, "Content-Type: text/html\r\n"); 
    p += sprintf(p, "Connection: closed\r\n\r\n"); 
    //页面 
    p += sprintf(p, "<html>\r\n<head>\r\n"); 
    p += sprintf(p, "<meta content=\"text/html; charset=UTF-8\" http-equiv=\"Content-Type\">\r\n"); 
    p += sprintf(p, "</head>\r\n"); 
    p += sprintf(p, "<body style=\"background-color: rgb(229, 229, 229);\">\r\n"); 

    p += sprintf(p, "<center>\r\n"); 
    p += sprintf(p, "<H3>连接状态</H3>\r\n"); 
    p += sprintf(p, "<p>服务器地址 %s:%d</p>\r\n", s_listens[listen_index].ip4, s_listens[listen_index].port); 
    j = 0; 
    for(i = 0; i < THREAD_MAX; i++) { 
        if (0 != s_thread_para[i][0]) j++; 
    } 
    p += sprintf(p, "<H3>线程池状态</H3>\r\n"); 
    p += sprintf(p, "<p>线程池总数 %d 活动线程总数 %d</p>\r\n", THREAD_MAX, j); 
    p += sprintf(p, "</center></body></html>\r\n"); 
    len = p - buff; 

    //发送响应 
    send(sock_cli, buff, len, MSG_NOSIGNAL); 
    memset(buff, 0, 32768);

    //释放连接 
    shutdown(sock_cli, SHUT_RDWR); 
    close(sock_cli); 

    //线程任务结束 
    thread_para[0] = 0;//设置线程占用标志为"空闲" 
    goto wait_unlock; 

    pthread_exit(NULL); 
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值