学习linux系统服务器编程学习总结

03
五月
2021

linux 系统服务器编程学习总结

  • 前言
  • 第九章 io复用技术?
  • 一、select 系统调用
    • 1.select
    • 2.poll
    • 3.epoll
      • LT 和 ET 模式
  • 总结


前言

学习linux系统服务器编程学习总结,把要点写成记录,包括读书后感和自我实践的记录

提示:以下是本篇文章正文内容

第九章 io复用技术?

select , poll , epoll 技术

一、select 系统调用

1.select

函数select()的原型为:


         int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout);

     各参数含义为:

     nfds:整型变量,它比所有文件描述符集合中的最大值大1。使用select的时候必须计算最大值的文件描述符的值,将值通过nfds传入。

     readfds:这个文件描述符集合监视文件集合中的任何文件是否有数据可读,当select()函数返回的时候,readfds将清除其中不可读的文件描述符,只留下可读的文件描述符,即可以被recv()函数、read()函数等进行读数据的操作。

     writefds:这个文件描述符集合监视文件集合中的任何文件是否有数据可写,当select()函数返回的时候,writefds将清除其中不可写的文件描述符,只留下可写的文件描述符,即可以被send()函数、write()函数等进行写数据的操作。

     exceptfds:这个文件描述符集合监视文件集合中的任何文件是否发生错误,其实它可以用于其他的用途,例如,监视带外数据OOB,带外数据使用MSG_OOB标志发送到套接字上。当select()函数返回的时候,readfds将清除其中的其他文件描述符,只留下可读OOB数据。

     timeout:用来描述等待描述符就绪需要的事件。设置在select()函数所坚实的文件集合中的事件没有发生时,最长的等待时间,当超过此时间时,函数会返回。当超时时间为NULL时,表示阻塞操作,会一直等待,直到某个监视的文件集合中的某个文件描述符符合返回条件。当timeout的值为0时,select()会立即返回。timeout告知系统内核等待指定描述符中的任何一个就绪可花费多少时间。其timeval结构体用于指定这段时间的秒数和微秒数。
         FD_ZERO(fd_set* fdset):将指定的文件描述符集合清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于系统在分配内存空间后通常不作清空处理,所以结果是不可知的。

         FD_SET(fd_set* fdset):用于在文件描述符集合中增加一个新的文件描述符

         FD_CLR(fd_set* fdset):用于在文件描述符集合中删除一个文件描述符

         FD_ISSET(int fd,fd_set* fdset):用于检测指定的文件描述符是否在该文件描述符集合中

有个缺点,select 需要每次遍历所有的客户端文件标识符,无法精准知道谁有数据

static void recv_client_msg(fd_set *readfds)
{
    int i = 0, n = 0;
    int clifd;
    char buf[MAXLINE] = {0};
    for (i = 0;i <= s_srv_ctx->cli_cnt;i++) {
        clifd = s_srv_ctx->clifds[i];
        if (clifd < 0) {
            continue;
        }
        /*判断客户端套接字是否有数据*/
        if (FD_ISSET(clifd, readfds)) {
            //接收客户端发送的信息
            n = read(clifd, buf, MAXLINE);
            if (n <= 0) {
                /*n==0表示读取完成,客户都关闭套接字*/
                FD_CLR(clifd, &s_srv_ctx->allfds);
                close(clifd);
                s_srv_ctx->clifds[i] = -1;
                continue;
            }
            handle_client_msg(clifd, buf);
        }
    }
}

2.poll

代码如下(示例):

# include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);

poll没有最大文件描述符数量的限制

struct pollfd {

int fd;         /* 文件描述符 */
short events;         /* 等待的事件 */
short revents;       /* 实际发生了的事件 */
} ; 

3.epoll

epoll 把关心的文件描述符放在一个事件表里面
epoll 需要创建一个单独的文件描述符来管理唯一标识事件表


int epoll_create(int size);  //创建描述符号
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);   //注册关注描述符
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);  //等待需要处理的事件

  • int epoll_create(int size);
    创建返回一个epoll的句柄,
    size用来告诉内核这个监听的数目一共有多大。 (目前本质不起作用)

  • int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
    第一个参数是epoll_create()的返回值,
    第二个参数表示动作,用三个宏来表示:

    • EPOLL_CTL_ADD:注册新的fd到epfd中;
    • EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
    • EPOLL_CTL_DEL:从epfd中删除一个fd;
      第三个参数是关注的文件描述符号
      第四个参数市event结构体
 epoll_event oEvent;
    oEvent.events = EPOLLIN | EPOLLET;
    oEvent.data.fd = iSockFd;
    if (epoll_ctl(iEpollFd, EPOLL_CTL_ADD, iSockFd, &oEvent) < 0)
    {
        cerr << "fail to add listen fd to epoll, err: " << strerror(errno) << endl;
        return -1;
    }
  • int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  • 阻塞等待事件的产生,参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个
    maxevents的值不能大于创建epoll_create()时的size,
    参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。
  • 该函数返回需要处理的事件数目,如返回0表示已超时
while (true)
    {
        int iFdCnt = epoll_wait(iEpollFd, aoEvents, 1024, -1);
        if (iFdCnt < 0)
        {
            cerr << "epoll wait error, err: " << strerror(errno) << endl;
            return -1;
        }

        for (int i = 0; i < iFdCnt; i++)
        {
            if (aoEvents[i].data.fd == iSockFd)
            {
                sockaddr_in oClientAddr;
                socklen_t iAddrLen = sizeof(oClientAddr);
                int iAcceptFd = accept(iSockFd, (sockaddr *)&oClientAddr, &iAddrLen);
                if (iAcceptFd < 0)
                {
                    cerr << "fail to accpet, err: " << strerror(errno) << endl;
                    continue;
                }
                cout << "recv connection from " << inet_ntoa(oClientAddr.sin_addr) << ":" << ntohs(oClientAddr.sin_port) << endl;

                oEvent.events = EPOLLIN | EPOLLET;
                oEvent.data.fd = iAcceptFd;
                if (epoll_ctl(iEpollFd, EPOLL_CTL_ADD, iAcceptFd, &oEvent) < 0)
                {
                    close(iAcceptFd);
                    cerr << "fail to add fd to epoll, err: " << strerror(errno) << endl;
                    continue;
                }
            }
            else
            {
                int iCurFd = aoEvents[i].data.fd;
                ssize_t iRecvLen = recv(iCurFd, acRecvBuf, sizeof(acRecvBuf), 0);
                if (iRecvLen < 0)
                {
                    cerr << "fail to recv, close connection, err: " << strerror(errno) << endl;
                    if (epoll_ctl(iEpollFd, EPOLL_CTL_DEL, iCurFd, NULL) < 0)
                    {
                        cerr << "fail to del fd from epoll, err: " << strerror(errno) << endl;
                    }
                    close(iCurFd);
                    continue;
                }
                if (iRecvLen == 0)
                {
                    cout << "connection closed by client" << endl;
                    if (epoll_ctl(iEpollFd, EPOLL_CTL_DEL, iCurFd, NULL) < 0)
                    {
                        cerr << "fail to del fd from epoll, err: " << strerror(errno) << endl;
                    }
                    close(iCurFd);
                    continue;
                }
                cout << "recv data len: " << iRecvLen << endl;

                ssize_t iSendLen = send(iCurFd, acRecvBuf, iRecvLen, 0);
                if (iSendLen < 0)
                {
                    cerr << "fail to send, err: " << strerror(errno) << endl;
                    if (epoll_ctl(iEpollFd, EPOLL_CTL_DEL, iCurFd, NULL) < 0)
                    {
                        cerr << "fail to del fd from epoll, err: " << strerror(errno) << endl;
                    }
                    close(iCurFd);
                    break;
                }
                cout << "echo to client, len: " << iSendLen << endl;
            }
        }

LT 和 ET 模式

LT (水平模式)

  • 默认
  • 通知应用程序,可用不立即处理
  • 下次调用epoll_wait 还会通知

ET 边缘模式

  • 通知必须马上处理,不会再通知
  • 降低出发次数
  • 高效率
  • 必须非阻塞
  • 接受需要做while循环

总结

提示:这里对文章进行总结:
后面会逐渐更新我的文章

TAG

网友评论

共有访客发表了评论
请登录后再发布评论,和谐社会,请文明发言,谢谢合作! 立即登录 注册会员