4.select,poll,epoll

select对所有连接进行遍历,取出有事件产生的连接描述符,判断每个描述符是否在读或写的描述符集合中。

在调用select函数之前需要将描述符集合进行备份,因为select函数内部会改变描述符集合的内容。

fd_set通过位图来标识连接描述符,bitmap的大小由FD_SETSIZE宏来设置,默认1024个。

#include <serversocket.h>
#include <clientsocket.h>
#include <sys/select.h>
#include <iostream>

using namespace std;

int main(){
    ServerSocket ssocket;
    if(ssocket.fd() < 0){
        ssocket.close();
        exit(1);
    }

    char host[] = "127.0.0.1";
    int port = 9000;
    if(!ssocket.bind(host, port)){
        ssocket.close();
        exit(1);
    }
    if(!ssocket.listen(128)){
        ssocket.close();
        exit(1);
    }

    fd_set readfds;
    fd_set writefds;
    FD_ZERO(&readfds);
    FD_ZERO(&writefds);
    FD_SET(ssocket.fd(), &readfds);

    int maxfd = ssocket.fd();
    while(true){
        struct timeval timeout;
        timeout.tv_sec = 10;
        timeout.tv_usec = 0;

        fd_set tmpfds = readfds;        //复制描述符集合
        fd_set tmpfds1 = writefds;
        int infds = ::select(maxfd + 1, &tmpfds, &tmpfds1, NULL, &timeout);
        if(infds < 0){
            perror("select error:");
            break;
        }
        else if(infds == 0){
            cout << "select timeout" << endl;
        }

        for(int eventfd = 0; eventfd <= maxfd; eventfd++){
            if(FD_ISSET(eventfd, &tmpfds) == 0){
                continue;
            }
            if(eventfd == ssocket.fd()){
                int cfd = ssocket.accept();
                if(cfd < 0){
                    continue;
                }
                FD_SET(cfd, &readfds);
                FD_SET(cfd, &writefds);
                if(maxfd < cfd) maxfd = cfd;
            }
            else{
                ClientSocket csocket(eventfd);
                char readbuf[1024];
                memset(readbuf, 0, sizeof(readbuf));
                if(csocket.recv(readbuf, sizeof(readbuf)) <= 0){
                    csocket.close();
                    FD_CLR(eventfd, &readfds);
                    FD_CLR(eventfd, &writefds);
                    if(eventfd == maxfd){
                        for(int ii = maxfd; ii > 0; ii--){
                            if(FD_ISSET(ii, &readfds)){
                                maxfd = ii; 
                                break;
                            }
                        }
                    }
                }
                else{
                    cout << "Recieved: " << readbuf << endl;
                }

            }
        }
        for(int eventfd = 0; eventfd <= maxfd; eventfd++){
            if(FD_ISSET(eventfd, &tmpfds1) == 0){
                continue;
            }
            ClientSocket csocket(eventfd);
            char sendbuf[] = "This is message from server";
            if(csocket.send(sendbuf, sizeof(sendbuf)) <= 0){
                csocket.close();
                FD_CLR(eventfd, &readfds);
                FD_CLR(eventfd, &writefds);
                if(eventfd == maxfd){
                    for(int ii = maxfd; ii > 0; ii--){
                        if(FD_ISSET(ii, &writefds)){
                            maxfd = ii;
                            break;
                        }
                    }
                }
            }
        }
    }
    
    ssocket.close();
    return 0;
}

poll模型:使用结构体pollfd数组存放需要监听的socket。

读事件设置为POLLIN,写事件为POLLOUT。

将所有fd初始化位-1,poll自动忽略为-1的描述符。

在程序中poll的数据结构是数组,拷贝进内核后变成列表,依旧采用遍历的方法。

poll的事件:
POLLIN、POLLOUT、POLLOUT
POLLRDNORM 数据可读 POLLRDBAND 优先级带数据可读 POLLPRI 高优先级可读数据 
POLLWRNORM 数据可写 POLLWRBAND 优先级带数据可写 POLLHUP 发生挂起
POLLNVAL 描述字不是一个打开的文件
#include <serversocket.h>
#include <clientsocket.h>
#include <poll.h>
#include <iostream>

using namespace std;

int main(){
    ServerSocket ssocket;
    if(ssocket.fd() < 0){
        ssocket.close();
        exit(1);
    }

    char host[] = "127.0.0.1";
    int port = 9000;
    if(!ssocket.bind(host, port)){
        ssocket.close();
        exit(1);
    }
    if(!ssocket.listen(128)){
        ssocket.close();
        exit(1);
    }

    pollfd fds[1024];
    for(int ii=0; ii<1024; ii++){
        fds[ii].fd = -1;
    }
    fds[ssocket.fd()].fd = ssocket.fd();
    fds[ssocket.fd()].events = POLLIN;
    
    int maxfd = ssocket.fd();
    while(true){
        int infds = ::poll(fds, maxfd + 1, 10000);
        if(infds < 0){
            perror("poll error:");
            break;
        }
        else if(infds == 0){
            cout << "poll timeout" << endl;
            continue;
        }

        for(int eventfd=0; eventfd<=maxfd; eventfd++){
            if(fds[eventfd].fd < 0) continue;

            if((fds[eventfd].revents & POLLIN)==0) continue;

            if(eventfd == ssocket.fd()){
                int cfd = ssocket.accept();
                if(cfd < 0){
                    continue;
                }

                fds[cfd].fd = cfd;
                fds[cfd].events = POLLIN;

                if(maxfd < cfd) maxfd = cfd;
            }
            else{
                char recvbuf[1024];
                memset(recvbuf, 0, sizeof(recvbuf));
                ClientSocket csocket(eventfd);

                if(csocket.recv(recvbuf, sizeof(recvbuf)) <= 0){
                    csocket.close();
                    fds[eventfd].fd = -1;
                    if(eventfd == maxfd){
                        for(int ii = maxfd; ii > 0; ii--){
                            if(fds[ii].fd != -1){
                                maxfd = ii;
                                break;
                            }
                        }
                    }
                }
                else{
                    cout << recvbuf << endl;
                    char sendbuf[] = "Server recieved your message!";
                    csocket.send(sendbuf, sizeof(sendbuf));
                }
            }
        }
    }

    ssocket.close();
    return 0;
}

epoll:仅将发生事件的描述符从内核复制给用户。

epoll使用一颗红黑树存储要监听的文件描述符,只需要将文件描述符从用户态拷贝到内核态一次。没有最大文件描述符数量的限制。

发生变化的文件描述符作为数组返回。

epoll边沿触发ET和水平触发LT:默认为LT

LT模式:

读事件:若接收缓冲区中有数据、没读完,下次调用epoll_wait时立即触发读事件

写事件:若写缓冲区未满,下次调用epoll_wait时立即触发写事件

ET模式:

读事件:epoll_wait触发读事件后,不管程序有没有处理,都不会再触发,直到有新的数据到达

写事件:epoll_wait触发写事件后,若发送缓冲区没有满,不会触发写事件,只有当发送缓冲区由满变为不满时才再次触发

EPOLL_CTL_ADD EPOLL_CTL_DEL EPOLL_CTL_MOD //修改
#include <serversocket.h>
#include <clientsocket.h>
#include <sys/epoll.h>
#include <iostream>

using namespace std;

int main(){
    ServerSocket ssocket;
    if(ssocket.fd() < 0){
        ssocket.close();
        exit(1);
    }

    char host[] = "127.0.0.1";
    int port = 9000;
    if(!ssocket.bind(host, port)){
        ssocket.close();
        exit(1);
    }
    if(!ssocket.listen(128)){
        ssocket.close();
        exit(1);
    }

    int epollfd = epoll_create(1);

    epoll_event ev;
    ev.data.fd = ssocket.fd();
    ev.events = EPOLLIN;

    epoll_ctl(epollfd, EPOLL_CTL_ADD, ssocket.fd(), &ev);
    
    epoll_event evs[10];

    while(true){
        int infds = ::epoll_wait(epollfd, evs, 10, -1);
        if(infds < 0){
            perror("epoll error:");
            break;
        }
        else if(infds == 0){
            cout << "epoll timeout" << endl;
            continue;
        }

        for(int ii = 0; ii < infds; ii++){
            if(evs[ii].data.fd == ssocket.fd()){
                int cfd = ssocket.accept();
                if(cfd < 0){
                    continue;
                }

                ev.data.fd = cfd;
                ev.events = EPOLLIN;
                epoll_ctl(epollfd, EPOLL_CTL_ADD, cfd, &ev);
            }
            else{
                ClientSocket csocket(evs[ii].data.fd);
                char recvbuf[1024];
                if(csocket.recv(recvbuf, sizeof(recvbuf)) <= 0){
                    csocket.close();
                    epoll_ctl(epollfd, EPOLL_CTL_DEL, csocket.fd(), NULL);
                }
                else{
                    cout << recvbuf << endl;
                    char sendbuf[] = "Server recieved your message!";
                    //因为是非阻塞io,这里需要处理发送缓冲区满的情况
                    csocket.send(sendbuf, sizeof(sendbuf));
                }
            }
        }
    }
    ssocket.close();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值