io多路复用 select/poll/epoll 简介

一个服务器,有多个客户端访问。
可以每个请求都开启一个线程处理。
缺点:
多线程的弊端,CPU需要不停的切换上下文,过程繁琐,消耗资源多

单线程IO多路复用

在linux中每一个网络连接都可以看成是一个文件描述符,一切皆文件。

1.select

fdset是一个bitmap,每一位标志了对应fd是否有数据变化。select监听fdset,当有数据变化时,返回fdset。
四个常用宏:

  • FD_ZERO(&set);//将套接字集合清空
  • FD_SET(s, &set);//将给定的套接字添加到集合
  • FD_CLR(int ,fd_set*);//将一个给定的文件描述符从集合中删除
  • FD_ISSET(int ,fd_set*);//检测fd在fdset集合中的状态是否变化,当检测到变化时,返回真,否则返回假

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

#include    "unp.h"

void
str_cli(FILE *fp, int sockfd)
{
    int         maxfdp1, stdineof;
    fd_set      rset;//关键数据结构
    char        buf[MAXLINE];
    int     n;

    stdineof = 0;
    FD_ZERO(&rset);//清零
    for ( ; ; ) {
        if (stdineof == 0)
            FD_SET(fileno(fp), &rset);
        FD_SET(sockfd, &rset);//关联socket
        maxfdp1 = max(fileno(fp), sockfd) + 1;//最大的文件描述符 要加1
        Select(maxfdp1, &rset, NULL, NULL, NULL);

        if (FD_ISSET(sockfd, &rset)) {  /* socket is readable */
            if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
                if (stdineof == 1)
                    return;     /* normal termination */
                else
                    err_quit("str_cli: server terminated prematurely");
            }

            Write(fileno(stdout), buf, n);
        }

        if (FD_ISSET(fileno(fp), &rset)) {  /* input is readable */
            if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {
                stdineof = 1;
                Shutdown(sockfd, SHUT_WR);  /* send FIN */
                FD_CLR(fileno(fp), &rset);
                continue;
            }

            Writen(sockfd, buf, n);
        }
    }
}

缺点:

  1. 上限最多监听1024个文件描述符
  2. 每次轮询都要重置fdset
  3. 每次轮询都要从内核态拷贝数据到用户态
  4. O(n) 遍历复杂度
    FD_SETSIZE windows上限64,linux 1024 可调整,但有上限
    当有数据过来时
  • 会将fd相对的rset置位
  • select 返回

2.poll

特点:
引入了数据结构
struct pollfd{
int fd;
short events;//POLLIN读 POLLOUT写
shoer revents;
}

 /* include fig01 */
#include    "unp.h"
#include    <limits.h>      /* for OPEN_MAX */

int
main(int argc, char **argv)
{
    int                 i, maxi, listenfd, connfd, sockfd;
    int                 nready;
    ssize_t             n;
    char                buf[MAXLINE];
    socklen_t           clilen;
    struct pollfd       client[OPEN_MAX];
    struct sockaddr_in  cliaddr, servaddr;

    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, (SA *) &servaddr, sizeof(servaddr));

    Listen(listenfd, LISTENQ);

    client[0].fd = listenfd;
    client[0].events = POLLRDNORM;
    for (i = 1; i < OPEN_MAX; i++)
        client[i].fd = -1;      /* -1 indicates available entry */
    maxi = 0;                   /* max index into client[] array */
/* end fig01 */

/* include fig02 */
    for ( ; ; ) {
        nready = Poll(client, maxi+1, INFTIM);

        if (client[0].revents & POLLRDNORM) {   /* new client connection */
            clilen = sizeof(cliaddr);
            connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
#ifdef  NOTDEF
            printf("new client: %s\n", Sock_ntop((SA *) &cliaddr, clilen));
#endif

            for (i = 1; i < OPEN_MAX; i++)
                if (client[i].fd < 0) {
                    client[i].fd = connfd;  /* save descriptor */
                    break;
                }
            if (i == OPEN_MAX)
                err_quit("too many clients");

            client[i].events = POLLRDNORM;
            if (i > maxi)
                maxi = i;               /* max index in client[] array */

            if (--nready <= 0)
                continue;               /* no more readable descriptors */
        }

        for (i = 1; i <= maxi; i++) {   /* check all clients for data */
            if ( (sockfd = client[i].fd) < 0)
                continue;
            if (client[i].revents & (POLLRDNORM | POLLERR)) {
                if ( (n = read(sockfd, buf, MAXLINE)) < 0) {
                    if (errno == ECONNRESET) {
                            /* connection reset by client */
#ifdef  NOTDEF
                        printf("client[%d] aborted connection\n", i);
#endif
                        Close(sockfd);
                        client[i].fd = -1;
                    } else
                        err_sys("read error");
                } else if (n == 0) {
                        /* connection closed by client */
#ifdef  NOTDEF
                    printf("client[%d] closed connection\n", i);
#endif
                    Close(sockfd);
                    client[i].fd = -1;
                } else
                    Writen(sockfd, buf, n);

                if (--nready <= 0)
                    break;              /* no more readable descriptors */
            }
        }
    }
}
/* end fig02 */

当有数据到来时,会返回数据的数量
解决了select的缺点1,2

3.epoll

epoll_create
epoll_ctl 添加事件
epoll_wait

结构体
struct epoll_event

#include "intf.h"

#include "socket.h"
#include <algorithm>
#define MAXEVENTS 64

struct MSG
{
    string strBuff;
    SOCKET s;
};

// 采用epoll异步机制实现服务器
int main()
{
    // 创建socket
    string ip = "192.168.176.131";
    int port = 1122;
    TcpSocket tcp(AF_INET, SOCK_STREAM);
    tcp.Bind(ip, port);
    tcp.Listen(MAXEVENTS);

    // 设置socket为O_NONBLOCK
    int flags = fcntl(tcp.m_Sock, F_GETFL, 0);
    if(!(flags & O_NONBLOCK))
    {
        flags |= O_NONBLOCK;
    }
    int exflags = fcntl(tcp.m_Sock, F_SETFL, 0);
    if(exflags == -1)
    {
        cout << "fcntl F_SETFL o_NONBLOCK faild" << endl;
        return -1;
    }
    // 创建epoll,该参数在新版本的linux中已经没有作用了
    int epollftd = epoll_create(MAXEVENTS);
    if(epollftd == -1)
    {
        cout << "epoll create faild" << endl;
        return -1;
    }
    // 将服务区socket描述符添加到epoll
    epoll_event event;
    event.events = EPOLLIN | EPOLLET;
    event.data.fd = tcp.m_Sock;

    int ret = epoll_ctl(epollftd, EPOLL_CTL_ADD, tcp.m_Sock, &event);
    if(ret == -1)
    {
        cout << "epoll_ctrl EPOLL_CTL_ADD faild" << endl;
        return -1;
    }

    epoll_event *pEvents = (epoll_event *)calloc(MAXEVENTS, sizeof(epoll_event));
    if(!pEvents)
    {
        cout << "calloc epoll events faild" << endl;
        return -1;
    }
    // 事件处理
    while(1)
    {
        int nEventNum = epoll_wait(epollftd, pEvents, MAXEVENTS, 500);
        for(int i = 0; i < nEventNum; i ++)
        {
            // 新连接到来
            if(pEvents[i].data.fd == tcp.m_Sock)
            {
                cout << "准备接收客户端的连接" << endl;
                SOCKET client = tcp.Accept();
                cout << "有客户端连接" << endl;
                // 添加到队列中
                epoll_event ev;
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = client;
                epoll_ctl(epollftd, EPOLL_CTL_ADD, client, &ev);
            }
            else if(pEvents[i].events & EPOLLIN)
            {
                string strBuff = tcp.Recv(pEvents[i].data.fd);
                cout << "接收到客户端数据:" << strBuff << endl;
                if(strBuff == "EOF")
                {
                    cout << "客户端:" << pEvents[i].data.fd << "请求断开连接" << endl;
                } 
                
                string strSend = "";
                transform(strBuff.begin(), strBuff.end(), back_inserter(strSend), ::toupper);
               
                MSG msg;
                msg.strBuff = strSend;
                msg.s = pEvents[i].data.fd;

                epoll_event ev;
                ev.data.fd = pEvents[i].data.fd;
                ev.events = EPOLLOUT | EPOLLET;
                ev.data.ptr = (void *)&msg;
                epoll_ctl(epollftd, EPOLL_CTL_MOD, pEvents[i].data.fd, &ev);
            }
            else if(pEvents[i].events & EPOLLOUT)
            {
                MSG *pMsg = (MSG*)pEvents[i].data.ptr;
                cout << "向客户端输出信息:" << pMsg->strBuff << endl;
                int ret = tcp.Send(pMsg->s, pMsg->strBuff);
                event.data.fd = pMsg->s;
                event.events = EPOLLIN | EPOLLET;
                epoll_ctl(epollftd, EPOLL_CTL_MOD, pMsg->s, &event);
            }
            else
            {
                cout << "其它的处理" << endl;
            }
        }
    }
    
    return 0;
} 

epollserver.cpp

不在是从内核态到用户态的拷贝,而是两态共享这块区域
当有数据到来时,重新排列位置,有数据的放前面

应用:

  • redis
  • nginx
  • Java nio
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值