epoll解决socket并发问题

epoll了解

select会在第一个与最大文件描述符之间挨个遍历一遍,会造成时间和cpu的浪费,而epoll则解决了这一问题,通过先在注册表中注册的方式,使得每次都只用遍历已有的文件描述符,会节省cpu资源。

struct epoll_event

结构体epoll_event被用于注册所感兴趣的事件和回传所发生待处理的事件,定义如下:

typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;//保存触发事件的某个文件描述符相关的数据

struct epoll_event
{
__uint32_t events; /* epoll event */
epoll_data_t data; /* User data variable */
};

其中events表示感兴趣的事件和被触发的事件,可能的取值为:

EPOLLIN:表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数可读;
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: ET的epoll工作模式;

具体函数

1、epoll_create函数
函数声明:int epoll_create(int size)

功能:该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大范围;

2、epoll_ctl函数
函数声明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
功能:用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。

epfd:由epoll_create生成的epoll专用的文件描述符;
op:要进行的操作,EPOLL_CTL_ADD注册、EPOLL_CTL_MOD修改、EPOLL_CTL_DEL删除;
fd:关联的文件描述符;
event:指向epoll_event的指针;

成功:0;失败:-1

3、epoll_wait函数
函数声明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)

功能:该函数用于轮询I/O事件的发生;

epfd:由epoll_create生成的epoll专用的文件描述符;

epoll_event:用于回传代处理事件的数组;

maxevents:每次能处理的事件数;

timeout:等待I/O事件发生的超时值;

成功:返回发生的事件数;失败:-1

整体思路

客服端请求连接后,调用epoll_create函数创造一个epoll句柄,然后初始化epoll事件结构体,并将产生的server_sockfd注册进epoll句柄,接着调用epoll_create 函数实现对该文件描述符上的事件的控制,最后调用epoll_wait实现等待下次事件的发生,如果有事件发生,按注册号进行。

函数实现

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

#define BUFFER_SIZE 40
#define MAX_EVENTS 10

int main(int argc, char * argv[])   
{
    int server_sockfd;// 服务器端套接字   
    int connect_fd;// 客户端套接字   
    int len;   
    struct sockaddr_in my_addr;   // 服务器网络地址结构体   
    struct sockaddr_in remote_addr; // 客户端网络地址结构体   
    int sin_size;   
    char buf[BUFFER_SIZE];
    char sendbuf[BUFFER_SIZE];  // 数据传送的缓冲区   
    memset(&my_addr,0,sizeof(my_addr)); // 数据初始化--清零   
    my_addr.sin_family=AF_INET; // 设置为IP通信   
    my_addr.sin_addr.s_addr=INADDR_ANY;// 服务器IP地址--允许连接到所有本地地址上   
    my_addr.sin_port=htons(9998); // 服务器端口号   
    // 创建服务器端套接字--IPv4协议,面向连接通信,TCP协议
    if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)   
    {     
        perror("socket");   
        return 1;   
    }   
    // 将套接字绑定到服务器的网络地址上
    if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)   
    {   
        perror("bind");   
        return 1;   
    }   
    // 监听连接请求--监听队列长度为5 
    listen(server_sockfd,5);   
    sin_size=sizeof(struct sockaddr_in); 

    int epoll_fd;
    epoll_fd=epoll_create(MAX_EVENTS);//创建一个epoll句柄
    if(epoll_fd==-1)
    {
        perror("epoll_create failed");
        exit(EXIT_FAILURE);
    }
    struct epoll_event ev;   // epoll事件结构体 内涵两个数据events与data
    struct epoll_event events[MAX_EVENTS];// 事件监听队列
    ev.events=EPOLLIN;
    ev.data.fd=server_sockfd;//结构体内嵌联合体
    // 向epoll注册server_sockfd监听事件,加入到epoll的监听队列中
    if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,server_sockfd,&ev)==-1)
    {
        perror("epll_ctl:server_sockfd register failed");
        exit(EXIT_FAILURE);
    }
    int nfds;// epoll监听事件发生的个数
    // 循环接受客户端请求    
    while(1)
    {
        // 等待事件发生
        nfds=epoll_wait(epoll_fd,events,MAX_EVENTS,-1);
        if(nfds==-1)
        {
            perror("start epoll_wait failed");
            exit(EXIT_FAILURE);
        }
        int i;
        for(i=0;i<nfds;i++)
        {
            // 客户端有新的连接请求
            if(events[i].data.fd==server_sockfd)
            {
                // 等待客户端连接请求到达,调用accept函数
                if((connect_fd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0)
                {   
                    perror("accept  failed");   
                    exit(EXIT_FAILURE);
                }
                // 向epoll注册connect_fd监听事件
                ev.events=EPOLLIN;
                ev.data.fd=connect_fd;
                if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,connect_fd,&ev)==-1)
                {
                    perror("epoll_ctl:connect_fd register failed");
                    exit(EXIT_FAILURE);
                }
                printf("accept client %s\n",inet_ntoa(remote_addr.sin_addr));
            }
            // 客户端有数据发送过来
            else
            {
                len=recv(connect_fd,buf,BUFFER_SIZE,0);
                if(len<0)
                {
                    perror("receive from client failed");
                    exit(EXIT_FAILURE);
                }
                printf("receive from client:%s",buf);
                send(connect_fd,"I have received your message.\n",30,0);
                memset(buf, 0, sizeof(buf));   
                fgets(sendbuf, 4096, stdin);
                send(connect_fd, sendbuf, strlen(sendbuf),0);

            }
        }
    }
    return 0;   
}  

猜你喜欢

转载自blog.csdn.net/xn6666/article/details/80352057