epoll实战:如何构建一个echo服务器

发布于:2025-04-05 ⋅ 阅读:(55) ⋅ 点赞:(0)

前言:
推荐大家阅读一手 详解epoll
重点了解 epoll本质是在操作系统注册了一个事件表,然后将只需要查询"活跃文件操作表"


服务器

无需多言,直接套用固定格式。

  int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    errif(sockfd == -1, "socket create error");

    struct sockaddr_in serv_addr;
    bzero(&serv_addr,sizeof(serv_addr));
    serv_addr.sin_addr.s_addr=INADDR_ANY;
    serv_addr.sin_port=htons(8080);
    serv_addr.sin_family=AF_INET;

    errif(bind(sockfd,(sockaddr*)&serv_addr,sizeof(serv_addr))==-1,"socket bind error");
    errif(listen(sockfd,SOMAXCONN)==-1,"socket listen error");

说一下 bzero是初始化,将结构体化填充0字符,然后监听和绑定

//epoll 部分
int epfd=epoll_createl(0);//事件表
struct epoll_event events[1024],ev;//等待队列
bzero(&ev,sizeof(ev));
ev.data.fd=sockfd;
ev.events=EPOLLIN|EPOLLET
setnoblock(fd);
epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,-1)//添加数组 简单的实现了一个可读和ET触发监控

ET为边缘触发,即是只报告一次!
直接部分简单的实现了 一个将sockfd添加到epoll维护的事件表上。

//连接部分 
int nfds=epoll_wait(epfd,events,1024,-1);//等待就绪数组
        errif(nfds==-1,"epoll wait error");
        //查询
        for(int i=0; i<nfds;i++) {
            //连接
            if(events[i].data.fd==sockfd) {
                struct sockaddr_in clnt_addr;
                bzero(&clnt_addr,sizeof(clnt_addr));
                socklen_t clnt_addr_len=sizeof(clnt_addr);
                int clnt_sockfd=accept(sockfd,(sockaddr*)&clnt_addr,&clnt_addr_len);
                errif(clnt_sockfd==-1,"socket accept error");
                printf("new client fd %d! IP: %s Port: %d\n", clnt_sockfd, inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port));

                //注册
                struct epoll_event ev;
                bzero(&ev,sizeof(ev));
                ev.data.fd=clnt_sockfd;
                ev.events=EPOLLIN|EPOLLET;
                setnoblock(clnt_sockfd);
                epoll_ctl(epfd,EPOLL_CTL_ADD,clnt_sockfd,&ev);
            }

就如上面那样 先通过epoll_wait 中的等待队列 找出就绪队列,
然后开始"轮询",检测到就绪队列中sockfd有"动作"(这个是tcp/ip),就进行连接,然后上同将客户端的sockfd添加进来。

//业务部分
  else if (events[i].events&EPOLLIN) {
                while(true)
                {
                    //读取
                    char buf[1024];
                    bzero(&buf,sizeof(buf));

                    size_t read_bytes=read(events[i].data.fd,buf,sizeof(buf));
                    if(read_bytes>0) {
                        printf("Receive from client %d : %s\n",events[i].data.fd,buf);
                        write(events[i].data.fd,buf,sizeof(buf));
                    }
                    else if(read_bytes==-1&&errno == EINTR) {客户端正常中断、继续读取
                        printf("continue\n");
                        continue;
                    }
                    else if(read_bytes==-1&& ((errno == EAGAIN) || (errno == EWOULDBLOCK))) {//非阻塞IO,这个条件表示数据全部读取完毕
                        printf("finish reading once, errno: %d\n", errno);
                        break;
                    }
                    else if(read_bytes == 0){  //EOF,客户端断开连接
                        printf("EOF, client fd %d disconnected\n", events[i].data.fd);
                        close(events[i].data.fd);   //关闭socket会自动将文件描述符从epoll树上移除
                        break;
                    }
                }
            }

检测到可读事件 ,然后进行操作。
简单讲一下阻塞和非阻塞

本质上就是一个异步和非异步的问题,只不过阻塞和非阻塞强调的是io部分的异步操作。在非阻塞模式下,I/O 操作不会等待操作完成,而是立即返回。如果数据尚未准备好,返回值会表明操作未完成(如返回 -1 并设置 errno 为 EAGAIN 或 EWOULDBLOC.为啥还有设置其他两个条件。

客户端

#include<sys/unistd.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include <cstdio>
#include <strings.h>
#include<cstring>

void errif(bool condition,const char* errmsg) {
    if(condition) {
        perror(errmsg);
        exit(EXIT_FAILURE);
    }
}
int main() {
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    errif(sockfd==-1,"create socket error");

    struct sockaddr_in clnt_addr;
    clnt_addr.sin_family=AF_INET;
    clnt_addr.sin_port=htons(8080);
    clnt_addr.sin_addr.s_addr=inet_addr("127.0.0.1");

    errif(connect(sockfd,(sockaddr*)&clnt_addr,sizeof(clnt_addr))==-1,"socket connect error");

    while(true) {
        char buf[1024];
        bzero(&buf,sizeof(buf));

        printf("Enter message: ");
        scanf("%s",buf);

        size_t write_bytes=write(sockfd,buf,sizeof(buf));
        if (write_bytes == -1) {
            printf("Socket already disconnected, can't write any more!\n");
            break;
        }

        ssize_t read_bytes = read(sockfd, buf, sizeof(buf));
        if (read_bytes > 0) {
            printf("Message from server: %s\n", buf);
        } else if (read_bytes == 0) {
            printf("Server socket disconnected!\n");
            break;
        } else {
            printf("Socket read error\n");
            errif(true, "socket read error");
            return 1;
        }

    close(sockfd);

    }
}

客户端基本上一样,建议参考一下


网站公告

今日签到

点亮在社区的每一天
去签到