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;
}
相关推荐
古希腊数通小白(ip在学)3 小时前
stp拓扑变化分类
运维·服务器·网络·智能路由器
Muxiyale4 小时前
使用spring发送邮件,部署ECS服务器
java·服务器·spring
l1x1n05 小时前
Vim 编辑器常用操作详解(新手快速上手指南)
linux·编辑器·vim
FreeBuf_6 小时前
微软365 PDF导出功能存在本地文件包含漏洞,可泄露敏感服务器数据
服务器·microsoft·pdf
lixzest6 小时前
C++ Lambda 表达式详解
服务器·开发语言·c++·算法
ajassi20006 小时前
开源 python 应用 开发(三)python语法介绍
linux·python·开源·自动化
o不ok!6 小时前
Linux面试问题-软件测试
linux·运维·服务器
DaxiaLeeSuper7 小时前
Prometheus+Grafana+node_exporter监控linux服务器资源的方案
linux·grafana·prometheus
尽兴-8 小时前
如何将多个.sql文件合并成一个:Windows和Linux/Mac详细指南
linux·数据库·windows·sql·macos
kfepiza8 小时前
Netplan 中 bridges、bonds、ethernets、vlans 之间的关系 笔记250711
linux·tcp/ip·shell