linux学习之高并发服务器篇(二)

高并发服务器1.线程池并发服务器两种模型:
  • 预先创建阻塞于accept多线程,使用互斥锁上锁保护accept(减少了每次创建线程的开销)
  • 预先创建多线程,由主线程调用accept
  •   线程池3.多路I/O转接服务器三种模型性能分析select模型select 
    复制代码
    #include <sys/select.h>
    /* According to earlier standards */
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    int select(int nfds, fd_set *readfds, fd_set *writefds,
        fd_set *exceptfds, struct timeval *timeout);
    
    nfds: 监控的文件描述符集里最大文件描述符加1(因为是计数的从0开始),因为此参数会告诉内核检测前多少个文件描述符的状态
    readfds:监控有读数据到达文件描述符集合,传入传出参数
    writefds:监控写数据到达文件描述符集合,传入传出参数
    exceptfds:监控异常发生达文件描述符集合, 如带外数据到达异常,传入传出参数
    timeout:定时阻塞监控时间,3种情况
        1.NULL,永远等下去
        2.设置timeval,等待固定时间
        3.设置timeval里时间均为0,检查描述字后立即返回,轮询
    
    struct timeval {
              long tv_sec; /* seconds */
              long tv_usec; /* microseconds */
          };
    void FD_CLR(int fd, fd_set *set); 把文件描述符集合里fd清0
    int FD_ISSET(int fd, fd_set *set); 测试文件描述符集合里fd是否置1
    void FD_SET(int fd, fd_set *set); 把文件描述符集合里fd位置1
    void FD_ZERO(fd_set *set); 把文件描述符集合里所有位清0
    复制代码
    sever
    复制代码
    复制代码
    client
    复制代码
    /* client.c */
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <netinet/in.h>
    #include "wrap.h"
    #define MAXLINE 80
    #define SERV_PORT 8000
    int main(int argc, char *argv[])
    {
        struct sockaddr_in servaddr;
        char buf[MAXLINE];
        int sockfd, n;
        sockfd = Socket(AF_INET, SOCK_STREAM, 0);
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
        servaddr.sin_port = htons(SERV_PORT);
        Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
        while (fgets(buf, MAXLINE, stdin) != NULL) {
            Write(sockfd, buf, strlen(buf));
            n = Read(sockfd, buf, MAXLINE);
            if (n == 0)
                printf("the other side has been closed.n");
            else
                Write(STDOUT_FILENO, buf, n);
        }
        Close(sockfd);
        return 0;
    }
    复制代码
     pselect 给出pselect原型,此模型用的不多
    复制代码
    #include <sys/select.h>
    int pselect(int nfds, fd_set *readfds, fd_set *writefds,
        fd_set *exceptfds, const struct timespec *timeout,
        const sigset_t *sigmask);
    struct timespec {
        long tv_sec; /* seconds */
        long tv_nsec; /* nanoseconds */
    };
    用sigmask替代当前进程的阻塞信号集,调用返回后还原原有阻塞信号集
    复制代码
    poll
    复制代码
    #include <poll.h>
    int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    struct pollfd {
        int fd; /* 文件描述符 */
        short events; /* 监控的事件 */
        short revents; /* 监控事件中满足条件返回的事件    传出参数*/
    }; //第一个参数是一个结构体数组,nfds指结构体数组有几个元素 
    POLLIN普通或带外优先数据可读, 即POLLRDNORM | POLLRDBAND
    POLLRDNORM - 数据可读
    POLLRDBAND - 优先级带数据可读
    POLLPRI 高优先级可读数据
    POLLOUT普通或带外数据可写
    POLLWRNORM - 数据可写
    POLLWRBAND - 优先级带数据可写
    POLLERR 发生错误
    POLLHUP 发生挂起
    POLLNVAL 描述字不是一个打开的文件
    nfds 监控数组中有多少文件描述符需要被监控
    timeout 毫秒级等待
    - 1:阻塞等,#define INFTIM - 1 Linux中没有定义此宏
    0:立即返回,不阻塞进程
    >0:等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值
    复制代码
      如果不再监控某个文件描述符时,可以把pollfd中,fd设置为-1,poll不再监控此pollfd,下次返回时,把revents设置为0。   ppoll GNU定义了ppoll(非POSIX标准),可以支持设置信号屏蔽字,大家可参考poll模型自行实现C/S。
    #define _GNU_SOURCE /* See feature_test_macros(7) */
    #include <poll.h>
    int ppoll(struct pollfd *fds, nfds_t nfds,
    const struct timespec *timeout_ts, const sigset_t *sigmask);
    复制代码
    复制代码
    复制代码
    /* client.c */
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <netinet/in.h>
    #include "wrap.h"
    #define MAXLINE 80
    #define SERV_PORT 8000
    int main(int argc, char *argv[])
    {
        struct sockaddr_in servaddr;
        char buf[MAXLINE];
        int sockfd, n;
        sockfd = Socket(AF_INET, SOCK_STREAM, 0);
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
        servaddr.sin_port = htons(SERV_PORT);
        Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
        while (fgets(buf, MAXLINE, stdin) != NULL) {
            Write(sockfd, buf, strlen(buf));
            n = Read(sockfd, buf, MAXLINE);
            if (n == 0)
                printf("the other side has been closed.n");
            else
                Write(STDOUT_FILENO, buf, n);
        }
        Close(sockfd);
        return 0;
    }
    复制代码
    epoll      epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。目前epell是linux大规模并发网络程序中的热门首选模型。epoll       epoll是将就绪的文件描述符放在了一个队列中。不用像poll一样整个遍历了。   epoll除了提供select/poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epollepoll_wait/epoll_pwait的调用,提高应用程序效率。   一个进程打开大数目的socket描述符
    cat /proc/sys/fs/file-max
      设置最大打开文件描述符限制
    sudo vi /etc/security/limits.conf
    写入以下配置,soft软限制,hard硬限制
    * soft nofile 65536
    * hard nofile 100000
    epoll API 1.创建一个epoll句柄,参数size用来告诉内核监听的文件描述符个数,跟内存大小有关 #include(创建一颗树,并告诉这棵树最多能插入多少个节点,返回一个文件描述符,通过文件描述符找打整个树)
    int epoll_create(int size)
    size:告诉内核监听的数目
    2.控制某个epoll监控的文件描述符上的事件:注册、修改、删除。(在这棵树上插入删除一个节点)
    复制代码
    #include <sys/epoll.h>
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
    epfd:为epoll_creat的句柄
    op:表示动作,用3个宏来表示:
    EPOLL_CTL_ADD(注册新的fd到epfd),
    EPOLL_CTL_MOD(修改已经注册的fd的监听事件),
    EPOLL_CTL_DEL(从epfd删除一个fd);
    event:告诉内核需要监听的事件
    struct epoll_event {
        __uint32_t events; /* Epoll events */
        epoll_data_t data; /* User data variable */
    };
    EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
    EPOLLOUT:表示对应的文件描述符可以写
    EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
    EPOLLERR:表示对应的文件描述符发生错误
    EPOLLHUP:表示对应的文件描述符被挂断;
    EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来
    说的
    EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需
    要再次把这个socket加入到EPOLL队列里
    复制代码
    3.等待所监控文件描述符上有事件的产生,类似于select()调用。(阻塞等待,当有就绪的文件是,将就绪的文件描述符拷贝到一个数组上)
    复制代码
    #include <sys/epoll.h>
    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
        events:用来从内核得到事件的集合,
        maxevents:告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
        timeout:是超时时间
            - 1:阻塞
            0:立即返回,非阻塞
            >0:指定微秒
        返回值:成功返回有多少文件描述符就绪,时间到时返回0,出错返回 - 1
    复制代码
    server
    复制代码
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <sys/epoll.h>
    #include <errno.h>
    #include "wrap.h"
    #define MAXLINE 80
    #define SERV_PORT 8000
    #define OPEN_MAX 1024 //自己指定,不要超过默认系统默认的文件描述符的上界
    int main(int argc, char *argv[])
    {
        int i, j, maxi, listenfd, connfd, sockfd;
        int nready, efd, res;
        ssize_t n;
        char buf[MAXLINE], str[INET_ADDRSTRLEN];
        socklen_t clilen;
        int client[OPEN_MAX];
        struct sockaddr_in cliaddr, servaddr;
        struct epoll_event tep, ep[OPEN_MAX];
        listenfd = Socket(AF_INET, SOCK_STREAM, 0);
        bzero(&servaddr, sizeof(servaddr));
            servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(SERV_PORT);
        Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
        Listen(listenfd, 20);
        for (i = 0; i < OPEN_MAX; i++)
            client[i] = -1;
        maxi = -1; 
        efd = epoll_create(OPEN_MAX);//创建一棵树,这棵树最多容纳1024的结点,返回一个文件描述符 
        if (efd == -1)
            perr_exit("epoll_create"); 
        tep.events = EPOLLIN; tep.data.fd = listenfd;
        res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
        if (res == -1)
            perr_exit("epoll_ctl");
        for (;;) {
            nready = epoll_wait(efd, ep, OPEN_MAX, -1); /* 阻塞监听 */
            if (nready == -1)
                perr_exit("epoll_wait");
            for (i = 0; i < nready; i++) {
                if (!(ep[i].events & EPOLLIN))
                    continue;
                if (ep[i].data.fd == listenfd) {
                    clilen = sizeof(cliaddr);
                    connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
                    printf("received from %s at PORT %d
                        n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
                    for (j = 0; j < OPEN_MAX; j++)
                    if (client[j] < 0) {
                        client[j] = connfd; /* save descriptor */
                        break;
                    }
                    if (j == OPEN_MAX)
                        perr_exit("too many clients");
                    if (j > maxi)
                        maxi = j; /* max index in client[] array */
                    tep.events = EPOLLIN; tep.data.fd = connfd;
                    res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
                    if (res == -1)
                        perr_exit("epoll_ctl");
                }
                else {
                    sockfd = ep[i].data.fd;
                    n = Read(sockfd, buf, MAXLINE);
                    if (n == 0) {
                        for (j = 0; j <= maxi; j++) {
                            if (client[j] == sockfd) {
                                    client[j] = -1;
                                break;
                            }
                        }
                        res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
                        if (res == -1)
                            perr_exit("epoll_ctl");
                        Close(sockfd);
                        printf("client[%d] closed connectionn", j);
                    }
                    else {
                        for (j = 0; j < n; j++)
                            buf[j] = toupper(buf[j]);
                        Writen(sockfd, buf, n);
                    }
                }
            }
        }
        close(listenfd);
        close(efd);
        return 0;
    }
    复制代码
    client
    复制代码
    /* client.c */
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <netinet/in.h>
    #include "wrap.h"
    #define MAXLINE 80
    #define SERV_PORT 8000
    int main(int argc, char *argv[])
    {
        struct sockaddr_in servaddr;
        char buf[MAXLINE];
        int sockfd, n;
        sockfd = Socket(AF_INET, SOCK_STREAM, 0);
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
        servaddr.sin_port = htons(SERV_PORT);
        Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
        while (fgets(buf, MAXLINE, stdin) != NULL) {
                Write(sockfd, buf, strlen(buf));
            n = Read(sockfd, buf, MAXLINE);
            if (n == 0)
                printf("the other side has been closed.n");
            else
                Write(STDOUT_FILENO, buf, n);
        }
        Close(sockfd);
        return 0;
    }
    复制代码

    相关内容推荐