epoll多路IO复用、高并发模型

/*
 * @Author: kerwang
 * @Date: 2021-01-17 19:44:33
 * @LastEditTime: 2021-01-18 20:43:11
 * @LastEditors: Please set LastEditors
 * @Description: 多路IO复用,SOCKET并发服务器设计,epoll实现
 * @FilePath: /vscodespace/home/wang/socket/epoll.c
 */
#include <stdio.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <ctype.h>
#include <arpa/inet.h>


/**
 * @funcation: int epoll_create(int size);
 * @description: 创建epoll句柄,底层采用红黑树结构存储后续的文件描述符
 * @param :size表示大小,告诉内核帮我创建多大的epoll模型,即将来监听多少个文件描述符
 * @return :返回一个文件描述符,该文教描述符指向一个红黑树的树根
 */

/**
 * @funcation: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
 * @description: 对底层的这可红黑树进行什么样的控制
 * @param :
 *          int epfd:epoll_create()函数的返回值
 *          op:进行什么样的控制(常用)
 *              EPOLL_CTL_ADD:增加/添加
 *              EPOLL_CTL_MOD:修改
 *              EPOLL_CTL_DEL:删除
 *         fd:文件描述符,对那个文件描述付进行操作
 *         struct epoll_event *:
 *             struct epoll_event{
 *             uint32_t     events;      /* Epoll events */
/*            epoll_data_t data;        /* User data variable */
/*           } :告诉内需需要监听的事件
 *          events:①EPOLLIN:读事件、②EPOLLOUT:写事件、③EPOLLERR:socket文件描述符出错
 * 
 *          typedef union epoll_data {
 *               void        *ptr;
 *               int          fd;   文件描述符,跟第二个参数fd对应
 *               uint32_t    u32;
 *               int64_t     u64;
 *         } epoll_data_t;
 *
 * @return :成功返回0,失败返回-1并设置errno
 * 
 ******/

/**
 * @funcation:
 *  int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
 * @description: 等待哪个文件描述符发生事件
 * @param:
 *      epfd:红黑树的树根,即int epoll_create(int size)函数的返回值
struct epoll_event*:这个参数是一个struct epoll_event类型的数组,和epoll_ctl函数的第二个参数区别是epoll_ctl
    参数传递的是struct epoll_event的变量的地址,而在这个函数中是一个数组,是传出参数。传出满足事件的文件描述
    符放入到这个数组中、
    maxevents:该数组的容量/大小
    timeout:超时,-1:表示阻塞。0:立即返回。>0表示等待多少ms后返回
 * @return :成功返回有多少个文件描述符发生满足的事件,失败-1
 */
/*************************************以上三个函数更详细的查看Linux man手册******************************************/


#define SERVER_PORT 8000    //服务器端口
#define OPEN_MAX 1000       //最大能打开的文件描述符个数
#define IPBUF_LEN 128       //存放客户端IP地址的缓存区的大小

void PrintClientInfo(struct sockaddr_in info,int count,int flag);   //打印客户端的信息
void perror_exit(const char *str);                                  //错误处理函数

int main(int argc, char const *argv[])
{
    //建立监听套接字
    int listenfd = socket(AF_INET,SOCK_STREAM,0);
    if(listenfd<0){
        perror_exit("socker create error");
    }
    //设置端口复用
    int opt = 1;
    int temp = setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    if(temp <0){
        perror_exit("setsockopt error");
    }
    //服务器自身的IP和端口,建立连接上的客户端的ip和端口
    struct sockaddr_in server_addr,client_addr;
    int clientAddrLen;
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family=AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    //将IP和端口绑定
    temp =  bind(listenfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    if(temp<0)
    {
        perror_exit("bind error");
    }
    //设置同时建立连接的最大上限
    temp = listen(listenfd,20);
    if(temp<0){
        perror_exit("listen error");
    }
    /********创建epoll模型*********/
    int efdroot;    /*epoll底层红黑树的根节点*/
    int nready;     /*该变量存储有几个socket发生了事件(读、写、error)*/
    /*
    * eptep:设置监听哪个文件描述的什么事件的变量
    * epbuf:存储的事那些文件描述符socket满足监听事件的缓冲区
    */
    struct epoll_event eptep,epbuf[OPEN_MAX];
    
    /*创建epoll句柄*/
    efdroot = epoll_create(OPEN_MAX);
    if(efdroot<0){
        perror_exit("epoll_create error");
    }
    /*指定listenfd为读事件,即listenfd读事件来表示有新连接*/
    eptep.data.fd = listenfd;
    eptep.events = EPOLLIN;
    /*将监听套接字的读事件加入到监听集合(底层红黑树)中*/
    temp = epoll_ctl(efdroot,EPOLL_CTL_ADD,listenfd,&eptep);
    if(temp<0){
        perror_exit("epoll_ctl error");
    }
    /*客户端建立连接后新的fd,以及统计客户端的数量,和客户端交互数据的缓存区*/
    int connfd;
    int count = 0;
    char dataBuf[BUFSIZ];
    while(1)
    {
        /*阻塞等待efdroot集合(即监听socket的集合)中有满足监听条件(socket可读/error/可写)的事件发生。
        如果满足监听条件的事件发生,将这些描述符放入到epbuf缓冲区中,同时返回共有几个socket描述符发生了事件*/
        nready = epoll_wait(efdroot,epbuf,OPEN_MAX,-1);
        if(nready<0){
            perror_exit("epoll_wait error");
        }
        for(int i = 0; i< nready; i++)
        {
            if(epbuf[i].data.fd == listenfd) //有新连接
            {
                bzero(&client_addr,sizeof(client_addr));
                clientAddrLen = sizeof(client_addr);
                //这里accept不会再阻塞,能进入到这个if条件里说明一定有新的client请求建立链接
                connfd = accept(listenfd,(struct sockaddr *)&client_addr,&clientAddrLen);
                if(connfd<0){
                    perror_exit("accept error");
                }
                PrintClientInfo(client_addr,++count,1);
                /*将新建立的客户端connfd文件描述符添加到监听集合中,监听集合为efdroot为根节点的红黑树中*/
                eptep.events = EPOLLIN;
                eptep.data.fd = connfd;
                /*将建立连接后的socket添加到监听集合中*/ 
                temp = epoll_ctl(efdroot,EPOLL_CTL_ADD,connfd,&eptep);
                if(temp<0){
                    perror_exit("epoll_ctl error");
                }

            }
            else    //其他socket文件描述符中有数据到来
            {
                //定义临时读写fd
                int rwfd = epbuf[i].data.fd;
                temp = read(rwfd,dataBuf,sizeof(dataBuf));
                //读到0说明客户端断开链接
                if(temp == 0)
                {
                    //将该文件描述符符从监听集合中删除(底层红黑二叉树中剔除)
                    int rec = epoll_ctl(efdroot,EPOLL_CTL_DEL,rwfd,NULL);
                    if(rec<0){
                        perror_exit("close epoll_ctl");
                    }
                    close(rwfd);
                    PrintClientInfo(client_addr,--count,0); //这里的client_addr并不是已经关闭的客户端的ip和port,这里只是站位参数
                }
                //小于0说明读出错
                else if(temp<0) 
                {
                    perror("read error");
                    int rec = epoll_ctl(efdroot,EPOLL_CTL_DEL,rwfd,NULL);
                    if(rec<0){
                        perror_exit("close epoll_ctl");
                    }
                    close(rwfd);
                    PrintClientInfo(client_addr,--count,0); //这里的client_addr并不是已经关闭的客户端的ip和port,这里只是站位参数
                    printf("read 出错,服务器以主动断开上面的连接\n");
                }
                //读到数据,处理数据,回写给客户端
                else
                {
                    for(int j = 0; j<temp; j++)
                    {
                        //处理数据,将小写转换为大写
                        dataBuf[j] = toupper(dataBuf[j]);
                    }
                    //回写给客户端
                    write(rwfd,dataBuf,temp);
   
                }  
            }
            
        }

    }
    close(efdroot);
    close(listenfd);
    return 0;
}
//打印新建立连接客户端的ip和端口
void PrintClientInfo(struct sockaddr_in info,int count,int flag)
{
    char IPbuf[IPBUF_LEN];
    if(flag){
    printf("Welecome Client ip:%s\tPort:%d\tCount:%d\n",
        inet_ntop(AF_INET,&info.sin_addr,IPbuf,IPBUF_LEN),
        ntohs(info.sin_port),count); 
    }
    else
    {
        printf("Bye Count:%d\n",count); 
    }
     
}
//错误处理
void perror_exit(const char *str)
{
    perror(str);
    exit(-1);
}

在这里插入图片描述
参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值