epoll EPOLLONESHOT,非阻塞,子线程处理客户端事件

epoll EPOLLONESHOT,非阻塞,子线程处理客户端事件

非阻塞

(1)默认情况下,服务器在accept等待客户端连接时,会发生阻塞。直到客户端发生连接才不阻塞。当客户端同时有多个连接请求的时候,想通过**一次**epoll_wait的返回,就循环处理到所有的连接请求。这样会导致循环处理完所有连接请求后,会阻塞在accept,无法运行到epoll_wait继续进行监听。所以需要将服务器描述符lfd设置非阻塞。通过EAGAIN错误表明所有连接事件已经处理完了就退出循环,进行下次监听。
(2)同样在默认情况下,read函数在读完所有数据同样也会阻塞等待数据发送过来。如果想通过一次监听事件就处理完所有数据,但又希望在读完数据后不会阻塞。就需要将对应的客户端描述符设置为非阻塞。同样通过EAGAIN进行判断。

EPOLLONESHOT

ev.events=EPOLLIN|EPOLLONESHOT
如果将监听的事件设置为EPOLLONESHOT,表明该ev对应的文件描述符事件只能被epoll_wait监听到一次。如果需要再次被监听到需要重新进行注册修改: epoll_ctl(efd,EPOLL_CTL_MOD,fd,&ev)。
问题提出:为什么要有这个参数呢?
已如下代码为例,如下代码采用的是主线程负责监听事件的发生。而子线程进行客户端事件的处理。假如子线程在处理一个客户端时,该客户端据没办法短时间内处理完。那么主线程会一直监听到该客户端有读事件发生。这样epoll_wait会一直返回该客户端事件,并且将其重复放入消息描述符list中。影响效率。如果设置了EPOLLONESHOT,就会只监听到该客户端事件一次,等待将该客户端数据全部处理完后,再通过epoll_ctl(efd,EPOLL_CTL_MOD,fd,&ev)。重新注册,就可以进行下次监听。

补充:ev.events=EPOLLIN|EPOLLET
边沿触发也可以处理同样的问题,如此设置。当客户端每次发生一次读事件时,**才会触发一次**epoll_wait(),不考虑有没有读完所有数据。避免了客户端只要有数据,会一直触发epoll_wait的问题。

子线程处理客户端事件

如果只有一个进程负责客户端的连接和客户端数据的处理,这样会影响服务器的效率。在处理客户端数据时,不能处理客户端的连接。反之,一样。
所以,如下代码,主线程负责监听客户端连接,子线程负责处理客户端数据。这样服务器就能高效的进行工作。
主线程将需要处理的客户端描述符放入list sock消息队列,子线程从中取出进行事件处理。多线程之间操作全局消息队列。要注意线程间同步问题,进行加锁。
在处理完一个客户端所有数据后,如果该客户端还没退出,注意需要再重新挂载一下。这样才能对该客户端进行再一次监听

示例代码

#include<stdio.h>
#include<sys/epoll.h>
#include<netinet/in.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<iostream>
#include<arpa/inet.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
#include<list>
using namespace std;

list<int> socks;//所有有消息的socket文件描述符,由子线程进行处理
pthread_mutex_t mutex;
pthread_cond_t cond;
int quit;//线程退出的标记 1退出
int efd;

void setNonblock(int fd){//设置文件描述符非阻塞

    int flags=fcntl(fd,F_GETFL);
    flags|=O_NONBLOCK;
    fcntl(fd,F_SETFL,flags);
}

void *thread_func(void *ptr){

    while(1){//条件信号为不可靠信号,加上循环,一个信号可能需要处理多个客户端事件
        //条件变量阻塞,有信号来表示要读客户端了。
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex);
        pthread_mutex_unlock(&mutex);
        if(quit&&socks.size()==0)//主线程要求退出,并且任务处理都已经完成了
            break;

        while(1){//一个循环处理一个客户端事件
            if(socks.size()==0) //没有事件可以处理
                break;
            pthread_mutex_lock(&mutex);
            int fd=*socks.begin();
            socks.pop_front();
            pthread_mutex_unlock(&mutex);

            while(1){//循环客户端的所有数据
                char buf[1024];
                int n=read(fd,buf,sizeof(buf));
                if(n>0){
                    printf("%s\n",buf);
                }else if(n==0){//数据读完,并且对方客户端已经断开连接。
                    close(fd);
                    break; 
                }else{
                    if(errno==EAGAIN){//非阻塞状态下,数据读完了。
                        struct epoll_event ev;
                        ev.events=EPOLLIN|EPOLLONESHOT;
                        ev.data.fd=fd;
                        epoll_ctl(efd,EPOLL_CTL_MOD,fd,&ev);//重新挂载一下事件
                        break;
                    }
                    close(fd);//发生真正错误断开连接
                    break;
                }
            }
        }    
    }

}
int main(int argc,char *argv[]){
    int lfd,cfd;

    pthread_mutex_init(&mutex,NULL);
    pthread_cond_init(&cond,NULL);

    pthread_t tid;
    pthread_create(&tid,NULL,thread_func,NULL);//线程处理客户端事件
    lfd=socket(AF_INET,SOCK_STREAM,0);
    if(lfd<0){
        perror("socket");
        return -1;
    }
    struct sockaddr_in servaddr;
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_port=htons(6666);
    servaddr.sin_addr.s_addr=inet_addr("0.0.0.0");

    bind(lfd,(struct sockaddr*)&servaddr,sizeof(servaddr));

    listen(lfd,250);

    efd=epoll_create(1024);

    setNonblock(lfd);//设置监听为非阻塞
    struct epoll_event ev;
    ev.events=EPOLLIN;
    ev.data.fd=lfd;
    int ret=epoll_ctl(efd,EPOLL_CTL_ADD,lfd,&ev);
    if(ret<0){
        perror("epoll_ctl");
        return -1;
    }
    struct epoll_event events[10];//存放监听到事件的集合
    while(1){
        ret=epoll_wait(efd,events,10,2000);//等待监听
        int i;
        if(ret<=0){
            if(ret==0||errno==EINTR)//监听被打断
                continue;
            return -1;
        }
        for(i=0;i<ret;i++){//处理监听事件
            int newfd=events[i].data.fd;
            if(newfd==lfd){//监听到服务器事件,处理完所有请求连接
                while(1){
                    cfd=accept(lfd,NULL,NULL);
                   if(cfd<0){
                        if(errno==EAGAIN)
                            break;
                        exit(1);
                    }
                    setNonblock(cfd);
                    ev.data.fd=cfd;
                    ev.events=EPOLLIN|EPOLLONESHOT;
                    epoll_ctl(efd,EPOLL_CTL_ADD,cfd,&ev);//挂载客户端读事件
                }
            }
            else{//监听到客户端读事件,发信号给线程
                pthread_mutex_lock(&mutex);
                socks.push_back(newfd);
                pthread_mutex_unlock(&mutex);

                pthread_cond_signal(&cond);
            }
        }
    }
    quit=1;
    pthread_join(tid,NULL);

    return 0;
}





















/* TCP监听socket的文件描述符,并返回 */ int init_listen_fd(unsigned short port) { /* 监听socket */ int listen_sockfd; /* 监听地址 */ struct sockaddr_in listen_addr; /* 初始化 */ memset(&listen_addr, 0, sizeof(listen_addr)); /* 存储临时变量用于debug */ int temp_result; /* 创建监听socket */ listen_sockfd = socket(AF_INET, SOCK_STREAM, 0); handle_error("socket", listen_sockfd); /* 设置端口复用 */ int opt = -1; int ret = setsockopt(listen_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt); handle_error("setsockopt", listen_sockfd); /* 绑定端口和IP */ listen_addr.sin_family = AF_INET; listen_addr.sin_addr.s_addr = INADDR_ANY; listen_addr.sin_port = htons(port); /* 绑定地址 */ temp_result = bind(listen_sockfd, (struct sockaddr *)&listen_addr, sizeof(listen_addr)); handle_error("bind", temp_result); printf("bind success\n"); /* 进入监听模式 */ if (listen(listen_sockfd, 128) < 0) { perror("listen failed"); close(listen_sockfd); exit(EXIT_FAILURE); } printf("listen success\n"); return listen_sockfd; } /* 启动threads */ int threads_run(int listen_sockfd) { /* 存储临时变量用于debug */ int temp_result; /* 用于存储客户端地址 */ struct sockaddr_in client_addr; memset(&client_addr, 0, sizeof(client_addr)); /* 接收client连接 */ socklen_t client_addr_len = sizeof(client_addr); while (1) { /* 子线程, 用于从客户端读取数据*/ pthread_t pid; int *client_fd_ptr = (int *)malloc(sizeof(int)); int client_fd = accept(listen_sockfd, (struct sockaddr *)&client_addr, &client_addr_len); *client_fd_ptr = client_fd; if (client_fd < 0) { perror("accept failed"); close(listen_sockfd); exit(EXIT_FAILURE); } printf("a client from ip:%s at port %d, socket %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), client_fd); /* 调用子线程接收客户端数据, 并发送响应*/ if ((pthread_create(&pid, NULL, recv_http_request, client_fd_ptr)) != 0) { perror("pthread_create"); pthread_exit(&pid); } /* 使子线程处于detached状态,使其终止时自动回收资源,同时不阻塞线程 */ // pthread_join(pid, NULL); pthread_detach(pid); } printf("close socket"); close(listen_sockfd); return 0; } /* 接收http请求, cfd表示连接socket, epfd表示epoll树 */ void *recv_http_request(void *arg) { printf("进入recv\n"); int client_fd = *(int *)arg; /* 用于接收recv的返回值 */ int len = 0; /* 接收客户端请求数据 */ char buf[4096] = {0}; len = recv(client_fd, buf, sizeof(buf), 0); printf("len%d\n", len); parse_request(buf, (int *)arg); free(arg); printf("退出 recv\n"); close(client_fd); } /* 解析HTTP的请求行,并调用send_http_response */ int parse_request(const char *message, int *conn_fd) { /* 解析请求行 */ /* 存储客户端请求方法,如GET/POST */ char method[12]; char path[1024]; /* 处理客户端请求的静态资源(目录或文件) */ char *filename = NULL; char version[1024]; printf("获取方法和路径\n"); sscanf(message, "%s %s %s", method, path, version); printf("请求内容: \nmethod:%s, path:%s\n\n", method, path); /* 不区分大小写的比较,如果不等于0,则返回-1(目前只接收get方法) */ if (strcasecmp(method, "get") != 0) { return -1; } /* 处理客户端请求的静态资源(目录或文件),因为当前获得的/xxx/1.jpg是相对于工作路径的,所以需要转换为./xxx/1.jpg或者xxx/1.jpg */ if (0 == strcmp(path, "/")) { /* 如果get当前的工作目录 */ filename = "./"; } else { /* get工作目录中的一个资源 */ filename = path + 1; } send_http_response(conn_fd, filename); return 0; } /* 发送http响应报文, 包括 响应头 响应文件数据等 */ void send_http_response(int *sockfd, const char *file_path) { /* stat函数的传出采纳数 */ struct stat file_stat; /* 用于读取文件和发送数据的缓冲区 */ char buffer[(int)pow(2, 17)]; /* 用于接收read返回的数据长度 */ ssize_t bytes_read; if (stat(file_path, &file_stat) != 0) { /* 文件不存在或无法获取状态 */ /* 这里应该发送一个404 NotFound响应,但为了简化仅关闭socket */ close(*sockfd); return; } int fd = open(file_path, O_RDONLY); if (-1 == fd) { /* 打开文件失败 */ /* 这里应该发送一个500 Internal Server Error响应,但同样为了简化仅关闭socket */ close(*sockfd); return; } printf("打开文件%s\n", file_path); const char *mime_type = get_mime_type(file_path); /* 发送HTTP响应头 */ char response_header[2048]; snprintf(response_header, sizeof(response_header), "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "Content-Length: %ld\r\n" "Connection: keep-alive\r\n" "\r\n", mime_type, file_stat.st_size); send(*sockfd, response_header, strlen(response_header), 0); /* 循环读取并发送文件内容 */ while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) { send(*sockfd, buffer, bytes_read, 0); } printf("%s\n", bytes_read); /* 关闭文件描述符 */ close(fd); } /* MIME类型映射, 获取发送给客户端文件名称的后缀名, 以生成对应Content-Type参数 */ const char *get_mime_type(const char *filename) { /* 接收strrchr返回参数,存储文件后缀名 */ const char *dot = NULL; /* strrchr获取指定字符的最后一次出现 */ dot = strrchr(filename, '.'); /* 根据文件后缀名生成对应Content-Type参数 */ if (NULL == dot) { return "application/octet-stream"; } if ((0 == strcmp(dot, ".html")) || (0 == strcmp(dot, ".htm"))) { return "text/html"; } else if (0 == strcmp(dot, ".css")) { return "text/css"; } else if ((0 == strcmp(dot, ".png")) ||\ (0 == strcmp(dot, ".jpg")) ||\ (0 == strcmp(dot, ".jpeg"))) { return "image/jpeg"; } return "application/octet-stream"; }
最新发布
08-15
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值