IO复用 |
Cobb 594 1

select缺点 1.单个进程能够监视的文件描述符数量存在最大限制,默认是1024, 在1linux内核头文件中通过宏来定义的 #define一FD_ SETSIZE 1024,可以修改这个数值,但是由于select采用轮询的方式 扫描文件描述符集合,文件描述符越多,性能越差

2、内核空间和用户空间存在内存拷贝, select需要复制大量的句柄数据结构, 开销比较大

3、select返回的是含有整个句柄的数组, 应用程序需要遍历整个数组才能发现哪些句柄有事件发生

4、select的触发方式是LT水平触发, 应用程序如果对一一个已经就绪的文件描述符没有处理完10数据, 那么每次select调用还是会返回将这些未处理完数据的文件描述符通知进程

IO复用 |

select

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <sys/select.h>
#include <vector>
#include <errno.h>
#include <signal.h>

using namespace std;

void handler(int) {}

int main()
{
    signal(SIGINT, handler);

    // 创建socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0)
    {
        perror("socket err");
        exit(-1);
    }

    // 设置socket选择,网络地址重用  ip+port timewait 也是可以重新绑定的
    // setsocketopt
    int on = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on)) < 0)
    {
        perror("setsockopt err");
        exit(-1);
    }

    struct sockaddr_in s;
    s.sin_family = AF_INET;
    s.sin_port = htons(8080);
    s.sin_addr.s_addr = inet_addr("0.0.0.0");

    // 绑定socket到指定的网络地址上
    if (bind(listenfd, (struct sockaddr*)&s, sizeof(s)) < 0)
    {
        perror("bind err");
        exit(-1);
    }

    // 进入监听状态
    if (listen(listenfd, 1024) < 0)
    {
        perror("listen err.");
        exit(-1);
    }

    fd_set readfds;  // 可读套接字集合
    vector<int> fds{0, listenfd};  // 存储了所有的套接字

    for (;;)
    {
        // 套接字集合清空
        FD_ZERO(&readfds); 
        // 把需要监听的套接字添加到可读集合里面
        int maxfd = 1; 
        for (int fd : fds)
        {
            FD_SET(fd, &readfds);
            // 更新maxfd,找最大值,创建位图数组
            if (fd >= maxfd)
            {
                maxfd = fd + 1;
            }
        }
        // 记录当前这一轮监听的套接字的总数
        int size = fds.size();

        // select默认工作在阻塞模式
        int ret = select(maxfd, &readfds, NULL, NULL, NULL);
        if (ret < 0)
        {
            // 产生错误,或者是被信号打断 errno:EINTR
            if (errno == EINTR)
            {
                cout << "select被信号中断!" << endl;
                continue;
            }
            perror("select err.");
            exit(0);
        }

        /*
        select函数返回以后,发生事件的fd所在的readfds位图集合中,相应的位被改成1了
        0: 用户的标准输入
        listenfd: 有新用户连接
        clientfd:已连接用户的可读事件
        */
        // 判断fd:0
        if (FD_ISSET(0, &readfds))
        {
            char buf[1024] = {0};
            cin.getline(buf, 1024);
            for (int i = 2; i < fds.size(); i++)
            {
                send(fds[i], buf, strlen(buf), 0);
            }
        }

        if (FD_ISSET(listenfd, &readfds))
        {
            // 有新用户连接了
            struct sockaddr_in c;
            socklen_t clen = sizeof(c);
            int clientfd = accept(listenfd, (struct sockaddr*)&c, &clen);
            if (clientfd < 0)
            {
                perror("accept err");
            }
            else
            {
                fds.push_back(clientfd);
            }
        }

        // 循环遍历剩余的套接字  clientfd
        for (int i = 2; i < size; i++)
        {
            if (FD_ISSET(fds[i], &readfds))
            {
                char buf[1024] = {0};
                if ( 0 == recv(fds[i], buf, 1024, 0))
                {
                    cout << fds[i] << " quit!" << endl;
                    fds.erase(fds.begin()+i);
                    size--;
                    continue;
                }
                send(fds[i], buf, strlen(buf), 0);
            }
        }
    }

    // 关闭所有的套接字 vector
    for (int i = 1; i < fds.size(); i++)
    {
        close(fds[i]);
    }
}

poll

IO复用 |

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <poll.h>
#include <vector>
#include <errno.h>
#include <signal.h>

using namespace std;

void handler(int) {}

int main()
{
    signal(SIGINT, handler);

    // 创建socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0)
    {
        perror("socket err");
        exit(-1);
    }

    // 设置socket选择,网络地址重用  ip+port timewait 也是可以重新绑定的
    // setsocketopt
    int on = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on)) < 0)
    {
        perror("setsockopt err");
        exit(-1);
    }

    struct sockaddr_in s;
    s.sin_family = AF_INET;
    s.sin_port = htons(8080);
    s.sin_addr.s_addr = inet_addr("0.0.0.0");

    // 绑定socket到指定的网络地址上
    if (bind(listenfd, (struct sockaddr*)&s, sizeof(s)) < 0)
    {
        perror("bind err");
        exit(-1);
    }

    // 进入监听状态
    if (listen(listenfd, 1024) < 0)
    {
        perror("listen err.");
        exit(-1);
    }

    // fd: events: revents:
    vector<pollfd> pollfds;

    pollfd pfd;
    pfd.fd = 0;
    pfd.events = POLLIN;
    pollfds.push_back(pfd);

    pfd.fd = listenfd;
    pfd.events = POLLIN;
    pollfds.push_back(pfd);

    for (;;)
    {
        for (int i = 0; i < pollfds.size(); i++)
        {
            pollfds[i].revents = 0; // 重置0
        }

        // poll默认工作在阻塞模式
        int ret = poll(&pollfds[0], pollfds.size(), -1);
        if (ret < 0)
        {
            // 产生错误,或者是被信号打断 errno:EINTR
            if (errno == EINTR)
            {
                cout << "poll被信号中断!" << endl;
                continue;
            }
            perror("poll err.");
            exit(0);
        }

        if (pollfds[0].fd == 0 && (pollfds[0].revents & POLLIN)) // fd:0
        {
            char buf[1024] = {0};
            cin.getline(buf, 1024);
            for (int i = 2; i < pollfds.size(); i++)
            {
                send(pollfds[i].fd, buf, strlen(buf), 0);
            }
        }

        if (pollfds[1].fd == listenfd && ((pollfds[1].revents & POLLIN))) // fd:listenfd
        {
            // 有新用户连接了
            struct sockaddr_in c;
            socklen_t clen = sizeof(c);
            int clientfd = accept(listenfd, (struct sockaddr*)&c, &clen);
            if (clientfd < 0)
            {
                perror("accept err");
            }
            else
            {
                pfd.fd = clientfd;
                pfd.events = POLLIN;
                pollfds.push_back(pfd);
            }
        }

        for (int i = 2; i < pollfds.size(); i++)
        {
            if (pollfds[i].revents & POLLIN) // 有可读事件发生
            {
                char buf[1024] = {0};
                if ( 0 == recv(pollfds[i].fd, buf, 1024, 0))
                {
                    // cout << pollfds[i].fd << " quit!" << endl;
                    // fds.erase(fds.begin()+i);
                    // size--;
                    // continue;
                }
                send(pollfds[i].fd, buf, strlen(buf), 0);
            }
        }
    }
}

epoll

IO复用 |

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <vector>
#include <errno.h>
#include <signal.h>
#include <sys/epoll.h>

using namespace std;

const int EPOLL_EVENT_SIZE = 1024;
const int BUF_SIZE = 10;

void handler(int) {}

// 默认都是LT模式,水平触发
void LT(int epollfd, int fd)
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN;

    // 向epoll内核事件表中添加需要监听的fd及其event事件
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) < 0)
    {
        perror("epoll_ctl err");
    }
}

// epoll可以选择工作在ET模式  边沿触发
void ET(int epollfd, int fd)
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET; // 打开了fd的ET模式

    // 向epoll内核事件表中添加需要监听的fd及其event事件
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) < 0)
    {
        perror("epoll_ctl err");
    }
}

int main()
{
    signal(SIGINT, handler);

    // 创建socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0)
    {
        perror("socket err");
        exit(-1);
    }

    // 设置socket选择,网络地址重用  ip+port timewait 也是可以重新绑定的
    // setsocketopt
    int on = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on)) < 0)
    {
        perror("setsockopt err");
        exit(-1);
    }

    struct sockaddr_in s;
    s.sin_family = AF_INET;
    s.sin_port = htons(8080);
    s.sin_addr.s_addr = inet_addr("0.0.0.0");

    // 绑定socket到指定的网络地址上
    if (bind(listenfd, (struct sockaddr*)&s, sizeof(s)) < 0)
    {
        perror("bind err");
        exit(-1);
    }

    // 进入监听状态
    if (listen(listenfd, 1024) < 0)
    {
        perror("listen err.");
        exit(-1);
    }

    // 创建epoll实例,即epoll的内核事件表(专门用来监听fd以及发生的event事件)
    int epollfd = epoll_create(10);
    if (epollfd < 0)
    {
        perror("epoll_create err");
        exit(0);
    }

    // 通过epoll_ctl向内核事件表添加fd以及事件,增加到红黑树中(效率高)
    LT(epollfd, 0);
    LT(epollfd, listenfd);

    epoll_event events[EPOLL_EVENT_SIZE];
    vector<int> clientfds;
    for (;;)
    {
        int num = epoll_wait(epollfd, events, EPOLL_EVENT_SIZE, -1);
        if (num < 0)
        {
            perror("epoll_wait err");
            break;
        }

        for (int i = 0; i < num; i++)
        {
            int fd = events[i].data.fd;
            if (fd == 0) // 获取用户的标准输入
            {
                char buf[1024] = {0};
                cin.getline(buf, 1024);
                for (int i = 0; i < clientfds.size(); i++)
                {
                    send(clientfds[i], buf, strlen(buf), 0);
                }
            }
            else if (fd == listenfd) // 有新用户连接
            {
                // 有新用户连接了
                struct sockaddr_in c;
                socklen_t clen = sizeof(c);
                int clientfd = accept(listenfd, (struct sockaddr*)&c, &clen);
                if (clientfd < 0)
                {
                    perror("accept err");
                }
                else
                {
                    clientfds.push_back(clientfd);
                    // clientfd添加到epoll的内核事件表中
                    LT(epollfd, clientfd);
                }
            }
            else
            {
                // 已连接用户的读事件
                char buf[BUF_SIZE] = {0};
                recv(fd, buf, BUF_SIZE, 0);
                send(fd, buf, strlen(buf), 0);
            }
        }
    }
}
评论区

索引目录