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;
}
相关推荐
滴水之功12 分钟前
VMware OpenWrt怎么桥接模式联网
linux·openwrt
ldinvicible31 分钟前
How to run Flutter on an Embedded Device
linux
YRr YRr1 小时前
解决Ubuntu 20.04上编译OpenCV 3.2时遇到的stdlib.h缺失错误
linux·opencv·ubuntu
认真学习的小雅兰.1 小时前
如何在Ubuntu上利用Docker和Cpolar实现Excalidraw公网访问高效绘图——“cpolar内网穿透”
linux·ubuntu·docker
zhou周大哥2 小时前
linux 安装 ffmpeg 视频转换
linux·运维·服务器
不想起昵称9292 小时前
Linux SHELL脚本中的变量与运算
linux
loong_XL2 小时前
服务器ip:port服务用nginx 域名代理
服务器·tcp/ip·nginx
夕泠爱吃糖2 小时前
C++中如何实现序列化和反序列化?
服务器·数据库·c++
the丶only3 小时前
单点登录平台Casdoor搭建与使用,集成gitlab同步创建删除账号
linux·运维·服务器·docker·gitlab