使用epoll实现聊天服务

一、概述

  1.epoll函数及相关结构体介绍

多路IO-epoll
    将检测文件描述符的变化委托给内核去处理, 然后内核将发生变化的文件描述符对应的事件返回给应用程序.
    (通俗点讲就是我们不用关心文件描述符的变化了,内核帮我们干了,并且内核把那些有变化的具体的文件描述符都会返回回来)

    函数介绍:
    int epoll_create(int size);
    函数说明: 创建一个树根
    参数说明:
            size: 最大节点数, 此参数在linux 2.6.8已被忽略, 但必须传递一个大于0的数.
    返回值:
            成功: 返回一个大于0的文件描述符, 代表整个树的树根.
            失败: 返回-1, 并设置errno值.

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    函数说明: 将要监听的节点在epoll树上添加, 删除和修改
    参数说明:
            epfd: epoll树根
            op:
            EPOLL_CTL_ADD: 添加事件节点到树上
            EPOLL_CTL_DEL: 从树上删除事件节点
            EPOLL_CTL_MOD: 修改树上对应的事件节点
            fd: 事件节点对应的文件描述符
            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 events */
                           epoll_data_t data;        /* User data variable */
                       };
                        event.events常用的有:
                         EPOLLIN: 读事件
                         EPOLLOUT: 写事件
                         EPOLLERR: 错误事件
                         EPOLLET: 边缘触发模式
                         event.data.fd: 要监控的事件对应的文件描述符


    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    函数说明:等待内核返回事件发生
    参数说明:
            epfd: epoll树根
            events: 传出参数, 其实是一个事件结构体数组
            maxevents: 数组大小
            timeout:
            -1: 表示永久阻塞
            0: 立即返回
            >0: 表示超时等待事件
    返回值:
            成功: 返回发生事件的个数
            失败: 若timeout=0, 没有事件发生则返回; 返回-1, 设置errno值, 


    epoll_wait的events是一个传出参数, 调用epoll_ctl传递给内核什么值, 当epoll_wait返回的时候, 内核就传回什么值,不会对struct event的结构体变量的值做任何修改.

 

  2.案例:使用epoll编写一个简单的聊天服务器,要求:客户端发送什么,服务端就回复什么,如果服务端发送的是消息字母则转换为大写。

二、代码示例

//EPOLL高并发服务器编写(只有Linux系统支持epoll)
#include <sys/epoll.h>
#include <ctype.h>
#include "wrap.h"

int main(){
    //0.定义变量
    int ret;
    int n;
    int i;
    int k;
    int nready;
    int lfd;
    int cfd;
    int sockfd;
    char buf[1024];
    socklen_t socklen;
    struct sockaddr_in svraddr;
    struct epoll_event ev;
    struct epoll_event events[1024];

    //1.创建socket
    lfd = Socket(AF_INET,SOCK_STREAM,0);

    //2.绑定端口
    int opt;
    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));//设置端口复用
    svraddr.sin_family = AF_INET;
    svraddr.sin_port = htons(8888);
    svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    Bind(lfd,(struct sockaddr *)&svraddr,sizeof(struct sockaddr_in));

    //3.监听
    Listen(lfd,128);

    //4.创建一棵树
    int epfd = epoll_create(1024);
    if(epfd<0){
        perror("create epoll error");
        return -1;
    }

    //5.将监听文件描述符上树
    ev.data.fd = lfd;
    ev.events = EPOLLIN;
    epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);

    //6.while(1)中epoll_wait监控文件描述符变化
    while(1){
        nready = epoll_wait(epfd,events,1024,-1);
        if(nready<0){
            perror("epoll_wait error");
            if(errno=EINTR){//如果是信号中断就不视为错误
                continue;
            }
            break;
        }
        for(i=0;i<nready;i++){
            //7.判断是监听文件描述符还是通讯文件描述符
            sockfd = events[i].data.fd;
            if(sockfd==lfd){//8.如果是监听文件描述符调用Accept创建通讯文件描述符并上树
                cfd = Accept(lfd,NULL,NULL);
                ev.data.fd = cfd;
                ev.events = EPOLLIN;
                epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
                continue;
            }

            //9.如果是通讯文件描述符,就进行读写操作
            memset(buf,0x00,sizeof(buf));
            n = Read(sockfd,buf,sizeof(buf));
            if(n<0){
                printf("n=[%d],buf=[%s]\n",n,buf);
                close(sockfd);
                //将sockfd对应的事件节点从epoll树上删除
                epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL);
            }else{
                printf("n=[%d],buf=[%s]\n",n,buf);
                for(k=0;k<n;k++){
                    buf[k] = toupper(buf[k]);
                }
                Write(sockfd,buf,n);
            }
        }

    }
    
    
    
    //10.最后关闭监听文件描述符
    close(epfd);//关闭epoll根文件描述符
    close(lfd);
    return 0;
}

 

posted on 2021-12-15 10:43  飘杨......  阅读(14)  评论(0编辑  收藏  举报