linux网络编程-epoll多路复用

多路复用的高级之处在于,它能同时等待多个文件描述符,而这些文件描述符(套接
字描述符)其中的任意一个进入读就绪状态,epoll_wait()函数就可以返回。

采用select的实现参考:https://blog.csdn.net/xiyangxiaoguo/article/details/107211182

IO 多路技术一般在下面这些情况中被使用:

(1)当一个客户端需要同时处理多个文件描述符的输入输出操作的时候(一般来说是
标准的输入输出和网络套接字), I/O 多路复用技术将会有机会得到使用。

(2)当程序需要同时进行多个套接字的操作的时候。

(3)如果一个 TCP 服务器程序同时处理正在侦听网络连接的套接字和已经连接好的套
接字。

(4)如果一个服务器程序同时使用 TCP 和UDP 协议。

(5)如果一个服务器同时使用多种服务并且每种服务可能使用不同的协议(比如inetd
就是这样的)。

I/O 多路服用技术并不只局限与网络程序应用上。几乎所有的程序都可以找到应用I/O
多路复用的地方。

 该测试代码仅包含:

 client.cpp -->client

server.cpp -->server

在server中使用epoll来进行多路复用实现在单个线程内对多个socket进行io的监控和使用。

client.cpp

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

int main()
{
    std::string server_ip="127.0.0.1";
    unsigned short int server_port=10010;

    int sockfd;
    sockaddr_in server_addr;
    
    sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd==-1)
    {
        printf("客户端创建sockfd失败\n");
        return -1;
    }
    memset(&server_addr,0,sizeof(sockaddr_in));
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(server_port);
    server_addr.sin_addr.s_addr = inet_addr(server_ip.c_str());

    //connect
    if(connect(sockfd,(sockaddr*)(&server_addr),sizeof(sockaddr))==-1)
    {
        printf("failed to connect to server\n");
        return -1;
    }

    //向服务端发送消息
    std::string msg="xageg";
    for(int i=0;i<10;i++)
    {
        int nbytes=msg.size();
        printf("即将发送%d个字节\n",nbytes);
        int nw=write(sockfd,msg.c_str(),nbytes);
        if(nw!=nbytes)
        {
            printf("写入socket失败\n");
        }
        sleep(1);
    }


    //close
    close(sockfd);
    return 0;
}

server.cpp

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <ctype.h>
#include <sys/epoll.h>	//epoll头文件
#include <fcntl.h>

#define MAX_EVENTS 100
int main()
{
    unsigned short int port=10010;
    sockaddr_in server_addr;
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd==-1)
    {
        printf("server failed to create the socket\n");
        return -1;
    }
    memset(&server_addr,0,sizeof(sockaddr_in));
    server_addr.sin_family=AF_INET;
    server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    server_addr.sin_port=htons(port);

    //bind
    if(bind(sockfd,(sockaddr*)(&server_addr),sizeof(sockaddr))==-1)
    {
        printf("server failed to bind the socket\n");
        fprintf(stderr,"Socket error:%s\n",strerror(errno));
        return -1;
    }
    //listen
    if(listen(sockfd,5)==-1)
    {
        printf("server failed to listen the socket\n");
        return -1;
    }

    //创建epoll实例
    //epoll_create1(),如果flags为0,epoll_create1()和删除了过时size参数的epoll_create()相同。
    //如果flags中包含以下值就有不同的表现:EPOLL_CLOEXEC
    //在文件描述符上面设置执行时关闭(FD_CLOEXEC)标志描述符。
    int epollfd=epoll_create1(EPOLL_CLOEXEC);
    if(epollfd<0)
    {
        printf("failed to create epoll fd\n");
        return -1;
    }
    //将监听的端口的socket对应的文件描述符添加到epoll事件列表中
    epoll_event epevent;
    epevent.events=EPOLLIN;
    epevent.data.fd=sockfd;

    //该系统调用对文件描述符epollfd引用的epoll实例执行控制操作。它要求操作op对目标文件描述符fd执行。  
    //op参数的有效值为:
    // EPOLL_CTL_ADD:在文件描述符epfd所引用的epoll实例上注册目标文件描述符fd,并将事件事件与内部文件链接到fd。
    // EPOLL_CTL_MOD:更改与目标文件描述符fd相关联的事件事件。
    // EPOLL_CTL_DEL:从epfd引用的epoll实例中删除(注销)目标文件描述符fd。该事件将被忽略,并且可以为NULL  
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &epevent) == -1) 
    {
        printf("epoll_ctl: listen_sock");
        exit(EXIT_FAILURE);
    }

    epoll_event events[MAX_EVENTS];
    //接受客户端连接
    int sockfd_accepted;
    sockaddr_in client_addr;
    socklen_t sin_size=sizeof(sockaddr_in);
    while (true)
    {
        //epoll_wait阻塞线程,等待事件发生
        int nc=epoll_wait(epollfd,events,MAX_EVENTS,-1);
        if(nc==-1)
        {
            printf("epoll_wait error\n");
            continue;
        }
        printf("...\n");
        for(int n=0;n<nc;n++)
        {
            if(events[n].data.fd==sockfd)
            {
                //新的连接加入
                sockfd_accepted=accept(sockfd,(sockaddr*)&client_addr,&sin_size);
                if(sockfd_accepted==-1)
                {
                    printf("accept error\n");
                }
                else
                {
                    // int flags = fcntl(sockfd_accepted, F_GETFL, 0); 
                    // fcntl(sockfd_accepted, F_SETFL, flags | O_NONBLOCK);
                    printf("new connection from :%d\n",client_addr.sin_port);
                    epevent.events=EPOLLIN | EPOLLET;//设置为边缘触发
                    epevent.data.fd=sockfd_accepted;
                    //将该新的文件描述符加入到epoll事件的监听列表中
                    if(epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd_accepted,&epevent)==-1)
                    {
                        printf("epoll_ctl error\n");
                    }
                }
            }
            else
            {
                printf("do other things...\n");
                //接受客户端发送来的消息
                char msg[1024];
                int nsize=read(events[n].data.fd,msg,1023);
                if(nsize>0)
                {
                    printf("recieve some msg from %d:\n",events[n].data.fd);
                    msg[nsize]='\0';
                    printf("%s\n",msg);
                }
                else if(nsize==0)
                {//连接已经断开
                    printf("client %d disconnected!\n",events[n].data.fd);
                    epoll_ctl(epollfd,EPOLL_CTL_DEL,events[n].data.fd,NULL);
                    close(events[n].data.fd);
                }
                else
                {
                    printf("read %d error!",events[n].data.fd);
                }
            }
        }
    }

    close(sockfd);
    close(epollfd);
    return 0;
}

 通过shell运行多个clients进行测试

start_clients.sh 

#!/bin/bash
  
for i in {1..10}
do
        ./client &
        sleep 1
done


版权声明:本文为xiyangxiaoguo原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。