1. 使用poll或epoll创建echo服务器

1. 说明:

此篇博客主要记录一种客户端实现方式,和两种使用poll或者epoll分别创建echo服务器的方式,具体可看代码注释:

2. 相关代码:

2.1 echoClient.cpp
cpp 复制代码
#include <iostream>
#include <cstdio>
#include <cstring>

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

#define ERR_EXIT(m) \
        do {\
            perror(m); \
            exit(EXIT_FAILURE); \
        } while(0)


int main() {
    int sock;
    struct sockaddr_in svrAddr, localAddr;
    socklen_t addrlen = sizeof(sockaddr);
    

    sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock < 0)
        ERR_EXIT("socket");
    
    memset(&svrAddr, 0, sizeof(svrAddr));
    svrAddr.sin_family = AF_INET;
    svrAddr.sin_port = htons(8888);
    svrAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if (connect(sock, (sockaddr *)&svrAddr, sizeof(svrAddr)) < 0)
        ERR_EXIT("connect");
    
    if (getsockname(sock, (sockaddr *)&localAddr, &addrlen) < 0)
        ERR_EXIT("getsockname");

    std::cout << "ip = " << inet_ntoa(localAddr.sin_addr)
              << " port = " << ntohs(localAddr.sin_port) << std::endl;
    
    char sendBuf[1024] = {0};
    char recvBuf[1024] = {0};

    while (fgets(sendBuf, sizeof(sendBuf), stdin) != NULL)
    {
        write(sock, sendBuf, strlen(sendBuf));
        read(sock, recvBuf, sizeof(recvBuf));

        fputs(recvBuf, stdout);
        memset(sendBuf, 0, sizeof(sendBuf));
        memset(recvBuf, 0, sizeof(recvBuf));
    }

    close(sock);

    return 0;
}
2.2 echoServer_poll.cpp
cpp 复制代码
#include <iostream>
#include <vector>
#include <string>

#include <cstdio>
#include <cstring>

#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <unistd.h>

#include <signal.h>
#include <poll.h>
#include <sys/socket.h>

#define ERR_EXIT(m) \
        do {\
            perror(m); \
            exit(EXIT_FAILURE); \
        } while(0)

using PollfdList = std::vector<pollfd>;

int main()
{
    //忽略系统提示的一些错误信号
    /*
        signal(para1,para2)
            para1:信号类型
            para2:信号处理函数(可以自定义)
        讲解参考:https://blog.csdn.net/u013271656/article/details/114537411
    */
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);

    //创建一个监听套接字(非阻塞套接字)
    int listenfd;
    listenfd = socket(PF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
    if(listenfd < 0){
        ERR_EXIT("socket");
    }

    //设置地址
    struct sockaddr_in srvAddr;
    memset(&srvAddr,0,sizeof(srvAddr));//初始化
    srvAddr.sin_family = AF_INET;
    srvAddr.sin_port = htons(8888);
    srvAddr.sin_addr.s_addr = htonl(INADDR_ANY);

    //设置地址的重复利用
    int on = 1;
    if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0){
        ERR_EXIT("setsockopt");
    }

    //绑定
    if(bind(listenfd, (sockaddr *)&srvAddr, sizeof(srvAddr)) < 0){
        ERR_EXIT("bind");
    }

    //监听
    if(listen(listenfd, SOMAXCONN) < 0){
        ERR_EXIT("listen");
    }

    //使用poll并关注pollin事件
    struct pollfd pfd;
    pfd.fd = listenfd;
    pfd.events = POLLIN;
    
    //储存poll的描述符
    PollfdList pollfds;
    pollfds.push_back(pfd);

    int nready;
    struct sockaddr_in peerAddr;
    socklen_t peerlen;
    int connfd;
    int idlefd;//空闲描述符

    //循环处理
    while (true) {
        //取事件
        nready = poll(pollfds.data(), pollfds.size(), -1);
        if(nready == -1){
            if(errno == EINTR){
                continue;
            }
            ERR_EXIT("poll");
        }
        else if(nready == 0){
            continue;
        }
        //如果有POLLIN事件
        if(pollfds[0].revents & POLLIN){
            //接受
            peerlen = sizeof(peerAddr);
            connfd = ::accept4(listenfd, (sockaddr *)&peerAddr, &peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
            //剔除空闲连接
            if (connfd == -1) {
                if (errno == EMFILE) {
                    close(idlefd);
                    idlefd = accept(listenfd, NULL, NULL);
                    close(idlefd);
                    idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC);
                    continue;
                }
                else 
                    ERR_EXIT("accept4");
            }

            //加入到监听
            pfd.fd = connfd;
            pfd.events = POLL_IN;
            pfd.revents = 0;
            pollfds.push_back(pfd);
            --nready;

            //连接成功
            std::cout << "ip = " << inet_ntoa(peerAddr.sin_addr)
                      << " port = " << ntohs(peerAddr.sin_port) << std::endl;
            
            if (nready == 0)
                continue;
        }

        //std::cout << "pollfds size: " << pollfds.size() << std::endl;
        //std::cout << "nready fds: " << nready << std::endl;

        //遍历判断哪些套接字产生了事件
        for (auto it = pollfds.begin() + 1; it != pollfds.end() && nready > 0; ++it) {
            //如果是可读事件
            if (it->revents & POLL_IN) {
                --nready;
                connfd = it->fd;
                char buf[1024] = {0};
                //读取数据
                int ret = read(connfd, buf, 1024);
                if (ret == -1)
                    ERR_EXIT("read");
                if (ret == 0) {
                    std::cout << "client closed" << std::endl;
                    it = pollfds.erase(it);
                    --it;

                    close(connfd);
                    continue;
                }

                std::cout << buf << std::endl;
                //将接收的消息返回给客户端
                write(connfd, buf, strlen(buf));
            }
        }
    }
    return 0;
}
2.3 echoServer_epoll.cpp
cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

#include <cstdio>
#include <cstring>

#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <unistd.h>

#include <signal.h>
#include <sys/epoll.h>
#include <sys/socket.h>

#define ERR_EXIT(m) \
        do {\
            perror(m); \
            exit(EXIT_FAILURE); \
        } while(0)

using EventList = std::vector<epoll_event>;

int main()
{
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);

    //创建一个监听套接字(非阻塞套接字)
    int listenfd;
    listenfd = socket(PF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
    if(listenfd < 0){
        ERR_EXIT("socket");
    }

    //设置地址
    struct sockaddr_in srvAddr;
    memset(&srvAddr,0,sizeof(srvAddr));//初始化
    srvAddr.sin_family = AF_INET;
    srvAddr.sin_port = htons(8888);
    srvAddr.sin_addr.s_addr = htonl(INADDR_ANY);

    //设置地址的重复利用
    int on = 1;
    if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0){
        ERR_EXIT("setsockopt");
    }

    //绑定
    if(bind(listenfd, (sockaddr *)&srvAddr, sizeof(srvAddr)) < 0){
        ERR_EXIT("bind");
    }

    //监听
    if(listen(listenfd, SOMAXCONN) < 0){
        ERR_EXIT("listen");
    }

    std::vector<int> clients;
    int epollfd = epoll_create(EPOLL_CLOEXEC);

    //使用EPOLLIN并关注EPOLLIN事件
    struct epoll_event event;
    event.data.fd = listenfd;
    event.events = EPOLLIN;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event);
    
    //储存epoll的描述符
    EventList events(16);

    struct sockaddr_in peerAddr;
    socklen_t peerlen;
    int connfd;
    int idlefd;//空闲描述符

    int nready;
    //循环处理
    while (true) {
        //取事件
        nready = epoll_wait(epollfd, events.data(), static_cast<int>(events.size()), -1);
        if(nready == -1){
            if(errno == EINTR){
                continue;
            }
            ERR_EXIT("epoll_wait");
        }
        else if(nready == 0){
            continue;
        }
        if (static_cast<size_t>(nready) == events.size()) {
            events.resize(events.size() * 2);
        } 
            
        for (auto e : events) {
            if (e.data.fd == listenfd) {
                peerlen = sizeof(peerAddr);
                connfd = ::accept4(listenfd, (sockaddr *)&peerAddr, &peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC);

                if (connfd == -1) {
                    if (errno == EMFILE) {
                        close(idlefd);
                        idlefd = accept(listenfd, NULL, NULL);
                        close(idlefd);
                        idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC);
                        continue;
                    }
                    else 
                        ERR_EXIT("accept4");
                }

                clients.push_back(connfd);

                event.data.fd = connfd;
                event.events = EPOLLIN;
                epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &event);

                std::cout << " connection from ip = " << inet_ntoa(peerAddr.sin_addr)
                          << " port = " << ntohs(peerAddr.sin_port) << std::endl;
            }
            else if (e.events & EPOLLIN) {
                connfd = e.data.fd;
                char buf[1024] = {0};
                int ret = read(connfd, buf, 1024);
                if (ret == -1)
                    ERR_EXIT("read");
                if (ret == 0) {
                    std::cout << "client closed" << std::endl;
                    event = e; 
                    epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, &event);
                    clients.erase(std::remove(clients.begin(), clients.end(), connfd), clients.end());
                    close(connfd);
                    continue;
                }

                std::cout << "msg: " << buf << std::endl;
                write(connfd, buf, strlen(buf));
            }
        }
    }
    return 0;
}
相关推荐
AlfredZhao10 小时前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户97183563346616 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪17 小时前
linux 拷贝文件或目录到指定的位置
linux
大树881 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush41 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5201 天前
Linux 11 动态监控指令top
linux
小宇宙Zz1 天前
Maven依赖冲突
java·服务器·maven
不会C语言的男孩1 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
古城小栈1 天前
Unix 与 Linux 异同小叙
linux·服务器·unix