epoll学习

来源:互联网 发布:人工智能李开复百度云 编辑:程序博客网 时间:2024/06/08 20:01

学习参考书:https://book.douban.com/subject/24722611/
epoll是什么?
一个提供多路复用的系统调用

学习要点
参考:http://blog.csdn.net/yz764127031/article/details/72459158
(1)理解关键字:同步异步,非阻塞阻塞

(2)设置socket为非阻塞,然后利用多路复用检测socket是否可读事件,可读epoll_waiter获取,则通过遍历对相应文件描述符执行相应函数

(3)学习reactor范式,异步事件驱动框架,通过事件触发回调函数

epoll的使用
(1)创建句柄
(2)注册事件
(3)等待事件触发

epoll实例
C语言编写的聊天室实例

用到的知识点有:
epoll
管道
fork
socket基本API

(1)抽象出一个简单的库
提供三个操作:
设置socket为非阻塞
在句柄中添加事件处理函数和socket描述符
向客户端广播聊天信息

#ifndef UTILITY_H_INCLUDED#define UTILITY_H_INCLUDED#include <iostream>#include <list>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/epoll.h>#include <fcntl.h>#include <errno.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <string.h>using namespace std;// clients_list save all the clients's socketlist<int> clients_list;/**********************   macro defintion **************************/// server ip#define SERVER_IP "127.0.0.1"// server port#define SERVER_PORT 8888//epoll size#define EPOLL_SIZE 5000//message buffer size#define BUF_SIZE 0xFFFF#define SERVER_WELCOME "Welcome you join  to the chat room! Your chat ID is: Client #%d"#define SERVER_MESSAGE "ClientID %d say >> %s"// exit#define EXIT "EXIT"#define CAUTION "There is only one int the char room!"/**********************   some function **************************//**  * @param sockfd: socket descriptor  * @return 0**/int setnonblocking(int sockfd){    fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)| O_NONBLOCK);    return 0;}/**  * @param epollfd: epoll handle  * @param fd: socket descriptor  * @param enable_et : enable_et = true, epoll use ET; otherwise LT**/void addfd( int epollfd, int fd, bool enable_et ){    struct epoll_event ev;    ev.data.fd = fd;    ev.events = EPOLLIN;    if( enable_et )        ev.events = EPOLLIN | EPOLLET;    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);    setnonblocking(fd);    printf("fd added to epoll!\n\n");}/**  * @param clientfd: socket descriptor  * @return : len**/int sendBroadcastmessage(int clientfd){    // buf[BUF_SIZE] receive new chat message    // message[BUF_SIZE] save format message    char buf[BUF_SIZE], message[BUF_SIZE];    bzero(buf, BUF_SIZE);    bzero(message, BUF_SIZE);    // receive message    printf("read from client(clientID = %d)\n", clientfd);    int len = recv(clientfd, buf, BUF_SIZE, 0);    if(len == 0)  // len = 0 means the client closed connection    {        close(clientfd);        clients_list.remove(clientfd); //server remove the client        printf("ClientID = %d closed.\n now there are %d client in the char room\n", clientfd, (int)clients_list.size());    }    else  //broadcast message     {        if(clients_list.size() == 1) { // this means There is only one int the char room            send(clientfd, CAUTION, strlen(CAUTION), 0);            return len;        }        // format message to broadcast        sprintf(message, SERVER_MESSAGE, clientfd, buf);        list<int>::iterator it;        for(it = clients_list.begin(); it != clients_list.end(); ++it) {           if(*it != clientfd){                if( send(*it, message, BUF_SIZE, 0) < 0 ) { perror("error"); exit(-1);}           }        }    }    return len;}#endif // UTILITY_H_INCLUDED

(2)服务器端编写

#include "utility.h"int main(int argc, char *argv[]){    //服务器IP + port    struct sockaddr_in serverAddr;    serverAddr.sin_family = PF_INET;    serverAddr.sin_port = htons(SERVER_PORT);    serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);    //创建监听socket    int listener = socket(PF_INET, SOCK_STREAM, 0);    if(listener < 0) { perror("listener"); exit(-1);}    printf("listen socket created \n");    //绑定地址    if( bind(listener, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {        perror("bind error");        exit(-1);    }    //监听    int ret = listen(listener, 5);    if(ret < 0) { perror("listen error"); exit(-1);}    printf("Start to listen: %s\n", SERVER_IP);    //在内核中创建事件表    int epfd = epoll_create(EPOLL_SIZE);    if(epfd < 0) { perror("epfd error"); exit(-1);}    printf("epoll created, epollfd = %d\n", epfd);    static struct epoll_event events[EPOLL_SIZE];    //往内核事件表里添加事件    addfd(epfd, listener, true);    //主循环    while(1)    {        //epoll_events_count表示就绪事件的数目        int epoll_events_count = epoll_wait(epfd, events, EPOLL_SIZE, -1);        if(epoll_events_count < 0) {            perror("epoll failure");            break;        }        printf("epoll_events_count = %d\n", epoll_events_count);        //处理这epoll_events_count个就绪事件        for(int i = 0; i < epoll_events_count; ++i)        {            int sockfd = events[i].data.fd;            //新用户连接            if(sockfd == listener)            {                struct sockaddr_in client_address;                socklen_t client_addrLength = sizeof(struct sockaddr_in);                int clientfd = accept( listener, ( struct sockaddr* )&client_address, &client_addrLength );                printf("client connection from: %s : % d(IP : port), clientfd = %d \n",                inet_ntoa(client_address.sin_addr),                ntohs(client_address.sin_port),                clientfd);                addfd(epfd, clientfd, true);                // 服务端用list保存用户连接                clients_list.push_back(clientfd);                printf("Add new clientfd = %d to epoll\n", clientfd);                printf("Now there are %d clients int the chat room\n", (int)clients_list.size());                // 服务端发送欢迎信息                  printf("welcome message\n");                                char message[BUF_SIZE];                bzero(message, BUF_SIZE);                sprintf(message, SERVER_WELCOME, clientfd);                int ret = send(clientfd, message, BUF_SIZE, 0);                if(ret < 0) { perror("send error"); exit(-1); }            }            //处理用户发来的消息,并广播,使其他用户收到信息            else             {                   int ret = sendBroadcastmessage(sockfd);                if(ret < 0) { perror("error");exit(-1); }            }        }    }    close(listener); //关闭socket    close(epfd);    //关闭内核    return 0;}

(3)客户端编写

#include "utility.h"int main(int argc, char *argv[]){    //用户连接的服务器 IP + port    struct sockaddr_in serverAddr;    serverAddr.sin_family = PF_INET;    serverAddr.sin_port = htons(SERVER_PORT);    serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);    // 创建socket    int sock = socket(PF_INET, SOCK_STREAM, 0);    if(sock < 0) { perror("sock error"); exit(-1); }    // 连接服务端    if(connect(sock, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {        perror("connect error");        exit(-1);    }    // 创建管道,其中fd[0]用于父进程读,fd[1]用于子进程写    int pipe_fd[2];    if(pipe(pipe_fd) < 0) { perror("pipe error"); exit(-1); }    // 创建epoll    int epfd = epoll_create(EPOLL_SIZE);    if(epfd < 0) { perror("epfd error"); exit(-1); }    static struct epoll_event events[2];     //将sock和管道读端描述符都添加到内核事件表中    addfd(epfd, sock, true);    addfd(epfd, pipe_fd[0], true);    // 表示客户端是否正常工作    bool isClientwork = true;    // 聊天信息缓冲区    char message[BUF_SIZE];    // Fork    int pid = fork();    if(pid < 0) { perror("fork error"); exit(-1); }    else if(pid == 0)      // 子进程    {        //子进程负责写入管道,因此先关闭读端        close(pipe_fd[0]);         printf("Please input 'exit' to exit the chat room\n");        while(isClientwork){            bzero(&message, BUF_SIZE);            fgets(message, BUF_SIZE, stdin);            // 客户输出exit,退出            if(strncasecmp(message, EXIT, strlen(EXIT)) == 0){                isClientwork = 0;            }            // 子进程将信息写入管道            else {                if( write(pipe_fd[1], message, strlen(message) - 1 ) < 0 )                 { perror("fork error"); exit(-1); }            }        }    }    else  //pid > 0 父进程    {        //父进程负责读管道数据,因此先关闭写端        close(pipe_fd[1]);         // 主循环(epoll_wait)        while(isClientwork) {            int epoll_events_count = epoll_wait( epfd, events, 2, -1 );            //处理就绪事件            for(int i = 0; i < epoll_events_count ; ++i)            {                bzero(&message, BUF_SIZE);                //服务端发来消息                if(events[i].data.fd == sock)                {                    //接受服务端消息                    int ret = recv(sock, message, BUF_SIZE, 0);                    // ret= 0 服务端关闭                    if(ret == 0) {                        printf("Server closed connection: %d\n", sock);                        close(sock);                        isClientwork = 0;                    }                    else printf("%s\n", message);                }                //子进程写入事件发生,父进程处理并发送服务端                else {                     //父进程从管道中读取数据                    int ret = read(events[i].data.fd, message, BUF_SIZE);                    // ret = 0                    if(ret == 0) isClientwork = 0;                    else{   // 将信息发送给服务端                      send(sock, message, BUF_SIZE, 0);                    }                }            }//for        }//while    }    if(pid){       //关闭父进程和sock        close(pipe_fd[0]);        close(sock);    }else{        //关闭子进程        close(pipe_fd[1]);    }    return 0;}
原创粉丝点击