关于poll实现Server可能出现的问题
本文最后更新于 231 天前,其中的信息可能已经有所发展或是发生改变。

今天花了很多的功夫在排错上面,主要是不明白问什么数据在哪里出错。因为这里已经将问题修改,贴一下修改过后的代码

#include <iostream>
#include "wrap.h"

int main() {
    int i;
    int sockfd;
    int lfd = Socket(AF_INET, SOCK_STREAM, 0);
    char clt_str[INET_ADDRSTRLEN];
    int clt_port;
    int cfd;
    int maxfd = 0;
    struct pollfd pfd[1024];
    struct sockaddr_in serv_addr, clt_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    memset(&clt_addr, 0, sizeof(serv_addr));
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    char buf[BUFSIZ];
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    socklen_t clt_socklen;
    Bind(lfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
    Listen(lfd, BACK_LOG);
    pfd[0].fd = lfd;
    pfd[0].events = POLL_IN;
    pfd[0].revents = 0;
    int pfd_len = sizeof(pfd) / sizeof(pfd[0]);
    for (i = 1; i < pfd_len; ++i) {
        pfd[i].fd = -1;
    }
    maxfd = 0; // poll内部的有效元素的最大元素下标.
    int nReady = 0;
    while (true) {
        nReady = poll(pfd, maxfd + 1, -1);
        if (pfd[0].revents == POLL_IN) {
            // 找到对应
            clt_socklen = sizeof(clt_addr);
            cfd = Accept(lfd, (struct sockaddr *) &clt_addr, &clt_socklen);
            // 输出客户端信息
            clt_port = ntohs(clt_addr.sin_port);
            inet_ntop(AF_INET, &clt_addr.sin_addr, clt_str, INET_ADDRSTRLEN);
            std::cout << "Client Port: " << clt_port << "  Client IP: " << clt_str << std::endl;

            for (i = 1; i < pfd_len; ++i) {
                if (pfd[i].fd < 0) {
                    pfd[i].fd = cfd;
                    pfd[i].events = POLL_IN;    //注意!!! 重点
                    //======================================================
                    //逻辑上应当在这里修改pfd,如果你写在外面,可能就会导致很大的问题.
                    break;
                }
            }
            maxfd = std::max(maxfd, i);
            if (--nReady <= 0) {
                continue; // 代表仅有监听,那么继续监听,不处理客户端事件.
            }
            try {
                if (i >= pfd_len) {
                    // 存满了
                    throw "Too many Clients";
                }
            } catch (const char *msg) {
                std::cout << msg << std::endl;
                exit(1);
            }

        }
        //要注意 监听事件与客户端事件的分离,防止嵌套.

        for (i = 1; i <= maxfd ; ++i) {
            if((sockfd = pfd[i].fd) < 0) {
                continue;
            }
            if (pfd[i].revents & POLL_IN) {
                // 进行相关读写操作
                ssize_t n = read(sockfd, buf, sizeof(buf));
                if (n < 0) {
                    if (errno == ECONNRESET) {
                        // 连接被重置
                        std::cerr << "Client[" << i << "] close the connection" << std::endl;
                        close(sockfd);
                    } else {
                        std::cerr << "Read Error" << std::endl;
                        exit(1);
                    }

                } else if (n == 0) {
                    std::cout << "Client close the connection" << std::endl;
                    close(sockfd);
                    pfd[i].fd = -1;
                    // i == maxfd 可能要进行相应处理.
                    if (i == maxfd) {
                        while (maxfd > 0 && pfd[maxfd].fd == -1) --maxfd;
                    }
                    break;
                } else if (n > 0) {
                    for (int k = 0; k < n; ++k) {
                        buf[k] = toupper(buf[k]);
                    }
                    Write(STDOUT_FILENO, buf, n);
                    Write(sockfd, buf, n);
                }
            }
            if (--nReady <= 0) {
                break;
            }
        }
    }
    close(lfd);
    return 0;
}

其中wrap.h封装了socket及read,write一系列函数,避免繁琐的错误处理

关键在于,写的时候的,

  • 监听事件与客户端事件的分离
  • pfd[i].events = POLL_IN;的处理位置
    • 在这里我必须要承认,我对网上的教程正确度抱有期望确实太高了,有些时候我就看了半截就跑了,在后面代码反而会进行修改,其实应该是我的问题吧,应该看完。不过其实按自己的写法,写就好了,顶多后面参考一下代码逻辑。对。
  • 感觉最恼火的就是,i的作用域范围,教程内部,直接定义i在初始位置,本身就不合理,因为要通过i进行控制多个循环,而我们只需要额外在定义一个变量例如k来控制循环就好了,让变量出去,也不好排错,这也是我们需要注意的。作用域导致的非编译问题,而是逻辑问题。

See you~
坦诚漫谈,祝你拥有美好的一天。
上一篇
下一篇