poll2

本文深入探讨了网络编程中粘包问题的解决策略,包括如何通过合理处理读取操作避免数据丢失,以及利用忙等待机制优化IO事件监听,确保应用层与内核层有效交互,提升网络通信效率。

       

 

数据包 粘包 一个包 两次read

Read :可能并没有把confd 对应的缓冲区的数据读完,那么connfd仍然是活跃的,

我们应该将读到的数据保存在connfd的应用层的缓冲区(每一次都进行追加)。如何解析协议,我们让应用层的解析协议自己来解析

 

 忙等待 :

        假设客户端关注了socket的POLLOUT事件,而此时内核缓冲区有空闲,但是应用层却没数据可写,那么内核将会处于忙等待状态(busy waitting loop),一直发送POLLOUT事件。

        解决的方法是:我们要看应用层的缓冲区,如果应用层的缓冲区有数据发,那么我们应该关注POLLOUT事件,要不然就取消POLLOUT事件的关注。

        对于客户端在读数据时,我们也应该采用相应的方法,如果应用层的空间空闲时,我们就关注POLLIN事件,要不然就取消POLLIN事件。

 

请求量大并发时

            准备一个空闲的文件描述符(备胎)。遇到这种情况,先关闭这个空闲文件,获得一个文件描述符名额;再accept(2)拿到socket连接的文件描述符;随后立刻close(2),这样就优雅地断开了与客户端的连接;最后重新打开空闲文件,把“坑”填上,以备再次出现这种情况时使用。

 

code

#include <unistd.h> 
#include <sys/types.h> 
#include <fcntl.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 
#include <signal.h> 
#include <sys/wait.h> 
#include <poll.h> 
 
#include <stdlib.h> 
#include <stdio.h> 
#include <errno.h> 
#include <string.h> 
 
#include <vector> 
#include <iostream> 
 
#define ERR_EXIT(m) \ 
        do \ 
        { \ 
                perror(m); \ 
                exit(EXIT_FAILURE); \ 
        } while(0) 
 
typedef std::vector<struct pollfd> PollFdList; 
 
int main(void) 
{ 
    signal(SIGPIPE, SIG_IGN); 
    signal(SIGCHLD, SIG_IGN); 
 
//备胎fd 
    int idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC); 
    int listenfd; 
 
    //if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) 
    if ((listenfd = socket(PF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP)) < 0) 
        ERR_EXIT("socket"); 
 
    struct sockaddr_in servaddr; 
    memset(&servaddr, 0, sizeof(servaddr)); 
    servaddr.sin_family = AF_INET; 
    servaddr.sin_port = htons(5188); 
    servaddr.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, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) 
        ERR_EXIT("bind"); 
    if (listen(listenfd, SOMAXCONN) < 0) 
        ERR_EXIT("listen"); 
 
    struct pollfd pfd; 
    pfd.fd = listenfd; 
    pfd.events = POLLIN; 
 
    PollFdList pollfds; 
    pollfds.push_back(pfd); 
 
    int nready; 
 
    struct sockaddr_in peeraddr; 
    socklen_t peerlen; 
    int connfd; 
 
    while (1) 
    { 
        nready = poll(&*pollfds.begin(), pollfds.size(), -1); 
        if (nready == -1) 
        { 
            if (errno == EINTR) 
                continue; 
 
            ERR_EXIT("poll"); 
        } 
        if (nready == 0)    // nothing happended 
            continue; 
 
        if (pollfds[0].revents & POLLIN) 
        { 
            peerlen = sizeof(peeraddr); 
            connfd = accept4(listenfd, (struct sockaddr*)&peeraddr, 
                        &peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC); 
 
/*          if (connfd == -1) 
                ERR_EXIT("accept4"); 
*/
 
            // 这里备胎fd起到作用了 
            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 = POLLIN; 
            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()<<std::endl; 
        //std::cout<<nready<<std::endl; 
        for (PollFdList::iterator it=pollfds.begin()+1; 
            it != pollfds.end() && nready >0; ++it) 
        { 
                if (it->revents & POLLIN) 
                { 
                    --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 close"<<std::endl; 
                        it = pollfds.erase(it); 
                        --it; 
 
                        close(connfd); 
                        continue; 
                    } 
 
                    std::cout<<buf; 
                    //这里要根据业务看需不需要使用POLLOUT事件 
                    write(connfd, buf, strlen(buf)); 
 
                } 
        } 
    } 
 
    return 0; 
}



<think>我们正在解决一个编译链接错误:undefined reference to `__poll_chk' 这个错误通常发生在链接阶段,表示编译器找不到 `__poll_chk` 函数的实现。 根据经验,`__poll_chk` 是带有缓冲区溢出检查的 poll 函数(poll 函数的加强安全版本),通常在 glibc 中提供。 出现此错误的原因可能是: 1. 编译器选项启用了安全增强(如 `-D_FORTIFY_SOURCE`),但链接时没有指定对应的版本不正确。 2. 使用的 glibc 版本过旧,不支持 `__poll_chk` 函数。 3. 交叉编译时,目标平台的 glibc 版本不支持。 解决方法: 1. 确保系统安装了最新版本的 glibc(或至少支持 `__poll_chk` 的版本)。可以尝试更新系统。 2. 检查编译选项,如果使用了 `-D_FORTIFY_SOURCE`(通常设置为2),可以尝试降低安全级别或去掉该选项(但这不是推荐做法,因为会降低安全性)。 3. 检查链接的是否正确。确保链接了正确的数学(如果有必要)和其他依赖。有时需要显式链接一些,比如 `-lc`(虽然libc一般是自动链接的,但在某些情况下可能需要指定)。 但是,请注意:`__poll_chk` 是 libc 中的函数,通常通过包含头文件并正确链接 libc 就可以解决。如果自动链接失败,可以尝试手动链接 libc:`-lc`。 然而,在大多数情况下,这个错误可能是因为在编译时使用了 `-no-pie` 或其他与位置无关代码相关的选项,或者是因为使用了不兼容的编译器和版本。 另一种可能:在64位系统上编译32位程序,或者反之,导致不匹配。请确保编译目标与系统架构一致。 具体步骤: 步骤1:更新系统和glibc 对于基于Debian的系统:`sudo apt update && sudo apt upgrade` 对于基于RHEL的系统:`sudo yum update` 步骤2:检查编译选项 查看是否在编译命令中使用了 `-D_FORTIFY_SOURCE`,如果是,可以尝试暂时去掉它(比如将值设为0): 将 `-D_FORTIFY_SOURCE=2` 改为 `-D_FORTIFY_SOURCE=0` 或直接去掉。 注意:不推荐在生产环境中去掉安全加固,这应该是最后的手段。 步骤3:确保链接了libc 在链接命令末尾加上 `-lc`。 步骤4:检查架构兼容性 如果是32位程序,确保安装了32位的glibc(例如在64位Ubuntu上,安装`libc6-dev-i386`)。 编译32位程序时,可能需要添加 `-m32` 选项,并确保有对应的32。 步骤5:检查glibc版本 运行 `ldd --version` 查看当前使用的glibc版本。`__poll_chk` 是在glibc 2.15及以后版本中引入的(具体版本可能略有差异,但较新版本都支持)。如果版本过旧,需要升级。 步骤6:如果是在交叉编译环境中 确保交叉编译工具链的glibc版本支持 `__poll_chk`,并且路径正确。 如果以上步骤都不能解决,可能需要检查代码中是否直接调用了`__poll_chk`?通常我们不会直接调用它,而是调用`poll`,然后由编译器根据安全选项替换为`__poll_chk`。所以,如果代码中直接使用了`__poll_chk`,建议改为使用标准的`poll`函数。 示例:如果代码中有类似: __poll_chk(fds, nfds, timeout, bos); 应该改为: poll(fds, nfds, timeout); 最后,如果问题仍然存在,请提供编译命令和操作系统环境等信息,以便进一步排查。 生成相关问题: 1. 如何查看当前系统的glibc版本? 2. 在编译时如何指定安全加固选项(如 `-D_FORTIFY_SOURCE`)? 3. 什么是位置无关代码(PIE)?它如何影响链接? 4. 如何在64位系统上编译32位程序? 5. 如何安装32位的glibc开发?</think>### `undefined reference to '__poll_chk'` 错误解决方法 此错误发生在链接阶段,表示编译器找不到 `__poll_chk` 函数的实现。`__poll_chk` 是 glibc 提供的安全增强版 `poll` 函数,用于缓冲区溢出检查。以下是系统性的解决步骤: --- #### **1. 检查 glibc 版本** `__poll_chk` 需要较新的 glibc 支持(通常 ≥ 2.15): ```bash ldd --version # 查看 glibc 版本 ``` 若版本过旧: ```bash sudo apt update && sudo apt upgrade libc6 # Debian/Ubuntu sudo yum update glibc # RHEL/CentOS ``` --- #### **2. 调整安全编译选项** 错误常由 `-D_FORTIFY_SOURCE` 触发: - **临时禁用**(不推荐): 在编译命令中移除 `-D_FORTIFY_SOURCE=2` 或改为 `-D_FORTIFY_SOURCE=0`。 - **永久修复**: 更新 glibc 后重新启用该选项(安全加固重要)。 --- #### **3. 检查架构一致性** **混合 32/64 位编译**会导致不匹配: - 编译 32 位程序时,确保已安装 32: ```bash sudo apt install libc6-dev-i386 # Debian/Ubuntu ``` - 在编译命令中显式指定架构: ```bash gcc -m32 source.c -o output # 强制 32 位模式 gcc -m64 source.c -o output # 强制 64 位模式 ``` --- #### **4. 验证链接** 确保正确链接 libc: ```bash gcc source.c -o output -lc # 显式链接 libc ``` 检查其他依赖(如 `-lpthread`)是否完整。 --- #### **5. 交叉编译环境处理** 交叉编译时,确认工具链的 glibc 支持 `__poll_chk`: - 更新交叉编译工具链。 - 检查目标系统 glibc 版本是否≥2.15。 --- #### **6. 代码级检查** 避免直接调用 `__poll_chk`(它是 `poll` 的封装)。改用标准 `poll`: ```c #include <poll.h> // 使用标准 poll 而非 __poll_chk poll(fds, nfds, timeout); ``` --- #### **典型修复案例** ```bash # 更新 glibc 后重新编译(启用安全选项) gcc source.c -o output -D_FORTIFY_SOURCE=2 -lc ``` > **注意**:若问题持续,检查 `makefile` 中的编译选项,或尝试[最小化编译复现](https://stackoverflow.com/q/451374/939634)。 --- ### 相关问题 1. 如何查看 Linux 系统中已安装的 glibc 版本? 2. `-D_FORTIFY_SOURCE=2` 编译器选项有什么作用? 3. 混合编译 32 位和 64 位代码时常见错误有哪些? 4. 交叉编译工具链中如何更新 glibc? 5. 如何调试 `undefined reference` 类链接错误?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值