select,epoll 多路IO复用

select,epoll 多路IO复用

select和epoll是最常用的两种多路IO复用技术,select是跨平台的函数,而epoll是linux独有的。
select特点:
1.监听的文件描述符有限制,最多只能监听1024个描述符.
2.在监听到客户端事件发生时,处理每个事件,都需要循环遍历,才能确认是哪个客户端产生读事件。效率比较低。
3.适合并发连接数较少的服务器。
相关函数:

 int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
 void FD_CLR(int fd, fd_set *set);
 int  FD_ISSET(int fd, fd_set *set);
 void FD_SET(int fd, fd_set *set);
 void FD_ZERO(fd_set *set);

epoll特点:
1.只要内存足够,能监听足够多的文件描述符,由自己设定
2.当监听到客户端事件发生时,会将监听到的时间存在数组中,并且该数组存有客户端fd的信息,无需循环遍历确认。
3.适合高并发的服务器
相关函数:

int epoll_create(int size);
    size:只要大于0即可
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    op:
        EPOLL_CTL_ADD 添加监听描述符   
        EPOLL_CTL_MOD 修改监听描述符   
        EPOLL_CTL_DEL 删除监听描述符
    typedef union epoll_data {
             void        *ptr;
             int         fd;
         } epoll_data_t;
    struct epoll_event {
             uint32_t     events;      /* Epoll events */
             epoll_data_t data;        /* User data variable */
         };
    events:
        EPOLLIN 读事件
        EPOLLOUT 写事件 
        EPOLLERR 错误事件
        EPOLLET 边沿触发
        EPOLLONESHOT 一次触发(注意:触发完如果还想被监听到,需要重新mod一下监听描述符)

示例代码:

先贴上自定义分装的sock.h 函数

select服务器模型:

#include<arpa/inet.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <unistd.h>


int create_server(unsigned short port, const char* ip, int backlog)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd < 0)
    {
        perror("socket");
        return fd;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip); // 监听的ip地址

    int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0)
    {
        perror("bind");
        return -2;
    }

    listen(fd, backlog);

    return fd;
}

int doAccept(int fd, struct sockaddr* addr, socklen_t* addrlen){
    while(1)
    {
        int newfd = accept(fd, addr, addrlen);
        if(newfd < 0 && errno == EINTR)
        {
            continue;
        }

        return newfd;
    }
    return -1;
}

int main()
{
    int server = create_server(9988, "0.0.0.0", 250);
    int maxfd = server;
    list<int> clients; // 保存所有的客户连接socket

    while(1)
    {
        fd_set set;
        struct timeval tv;

        maxfd = server;
        FD_ZERO(&set);
        FD_SET(server, &set);
        for(list<int>::iterator it = clients.begin(); it!=clients.end(); ++it)
        {
            FD_SET(*it, &set);
            if(*it > maxfd) maxfd = *it; // 寻找最大的文件描述符
        }

        tv.tv_sec = 2;
        tv.tv_usec = 0;

        int ret = select(maxfd+1, &set, NULL, NULL, &tv);
        if(ret > 0)
        {
            if(FD_ISSET(server, &set))
            {
                // 下一次监听时,需要将它放入集合
                int newfd = doAccept(server, NULL, NULL);
                clients.push_back(newfd);
            }

            for(list<int>::iterator it = clients.begin(); it!=clients.end(); )
            {
                int newfd = *it;
                if(FD_ISSET(newfd, &set))
                {
                    char buf[1024];
                    ret = read(newfd, buf, sizeof(buf));
                    if(ret > 0)
                    {
                        printf("%s, fd=%d\n", buf, newfd);
                    }
                    else if(ret < 0 && errno == EINTR)
                    {
                        //被打断暂时不处理
                    }
                    else // ==0 或者 <0并且错误吗不是EINTR
                    {
                        // 当对方关闭socket,或者socket有问题时,应该关闭对应的socket
                        close(newfd);
                        it = clients.erase(it);
                        continue;
                    }
                }
                it++;
            }
        }
    }
}

epoll服务器模型

#include<arpa/inet.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <unistd.h>
#include <sys/epoll.h>
//封装epoll_ctl函数
void epoll_add(int epollfd, int fd, int events)
{
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = events;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, server, &ev);
}

int create_server(unsigned short port, const char* ip, int backlog)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd < 0)
    {
        perror("socket");
        return fd;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip); // 监听的ip地址

    int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0)
    {
        perror("bind");
        return -2;
    }

    listen(fd, backlog);

    return fd;
}

int main()
{
    int server = create_server(9988, "0.0.0.0", 250);

    // epollfd也是一个fd,那能不能把epollfd放到另外一个epollfd中去
    int epollfd = epoll_create(512);
    epoll_add(epollfd, server, EPOLLIN);

    struct epoll_event ev_out[8];

    while(1)
    {
        int ret = epoll_wait(epollfd, ev_out, 8, 2000);
        if(ret > 0)//监听事件发生
        {
            int i;
            for(i=0; i<ret; ++i)
            {
                int fd = ev_out[i].data.fd;
                if(fd == server)
                {
                    int newfd = accept(fd, NULL, NULL);

                    epoll_add(epollfd, newfd, EPOLLIN);
                }
                else //  
                {
                    char buf[1024];
                    int r = read(fd, buf, sizeof(buf));
                    if(r > 0)
                    {
                        printf("%s\n", buf);
                    }
                    else if(r < 0 && errno == EINTR)
                    {

                    }
                    else // r == 0 || (r< 0 && errno != EINTR)
                    {
                        close(fd); 
                    }
                }
            }
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值