C++Linux网络编程:poll模型和简单使用

poll模型

poll模型和select模型类似,都是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者,需要使用头文件poll.h

#include <poll.h>
int poll(struct pollfd* fds, nfds_t nfds, int timeout);

在select中使用的是fd_set结构体,而在此处的是pollfd和nfds_t,timeout的作用和select的一样,用于指定poll的超时值:当timeout的值为-1时,poll调用将会永远阻塞,直到某个事件发生;当timeout为0时,poll调用将立即返回
poll的返回值也和select一致,表示就绪文件描述符的总数。

pollfd结构体

struct pollfd{
	int fd; // 文件描述符
	short int events; // 注册的事件
	short int revents; // 实际发生的事件,由内核填充
};
  • fd指定文件描述符
  • events成员告诉poll监听fd上的哪些事件,它是一系列事件的按位或
  • revents成员由内核修改,以通知应用程序fd上实际发生了哪些事件

poll支持以下事件类型:

事件描述是否可作为输入是否可作为输出
POLLIN数据(包括普通数据和优先数据)可读
POLLRNDORM普通数据可读
POLLRDBAND优先级带数据可读
POLLPRI高优先级数据可读,比如TCP带外数据
POLLOUT数据(包括普通数据和优先数据)可写
POLLWRNORM普通数据可写
POLLWRBAND优先级带数据可写
POLLRDHUPTCP连接被对方关闭,或者对方关闭了写操作。它由GNU引入
POLLERR错误
POLLHUP挂起。比如管道的写端被关闭后,读端描述符上将收到POLLHUP事件
POLLNVAL文件描述符没有打开

在表中提到了很多事件,但是Linux中没有完全支持它们
通常,应用程序需要根据recv调用的返回值来区分socket上接受到的是有效数据还是对方关闭连接的请求,并做相应的处理。

不过,自Linux内核2.6.17开始,GNU为poll系统调用增加了一个POLLRDHUP事件,它在socket上接收到对方关闭连接的请求后触发。这为我们区分recv接受到的数据是有效数据还是对方关闭连接的请求提供了一种更简单的方式。
但使用POLLRDHUP事件时,我们需要在代码最开始处定义_GNU_SOURCE

nfds_t的定义

typedef unsigned long int nfds_t;

该参数用来指定被监听事件集合fds的大小

一个简单的poll服务器

这个服务器是个简单的echo服务器:

#include <iostream>
#include <vector>
#include <sys/socket.h>
#include <poll.h>
#include <algorithm>
#include <arpa/inet.h>
#include <assert.h>
#include <vector>
#include <unistd.h>

using namespace std;

int main(int argc, char* argv[]){
    if(argc != 3){
        cerr << "格式为 ip port" << endl;
        return 1;
    }

    int serverSocket, clientSocket;
    /*
        这个头文件在in.h中(实际上我们调用的是inet/in.h
        而inet/in.h被包含在头文件arpa/inet。h中了
    */
    struct sockaddr_in serverAddr{}, clientAddr{};
    socklen_t clientAddrLen;

    // 创建监听套接字
    serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    assert(serverSocket != -1);

    serverAddr.sin_family = AF_INET;
    /*
        htons的英文意思是host to network shrot
        用于将16位无符号短整型(port)的主机字节序转换为网络字节序
    */
    serverAddr.sin_port = htons(stoi(argv[2])); 
    /*
        在调用 inet_pton 函数时,需要将 &(address.sin_addr) 作为参数传递,而不是 &(address.sin_addr.s_addr)
        struct sockaddr_in 结构体中的 sin_addr 字段是一个 struct in_addr 类型的结构体
        它包含了 IP 地址的二进制表示形式。in_addr 结构体中的 s_addr 字段实际上就是一个无符号整数类型(uint32_t)
        用来存储 IP 地址的二进制形式。
    */
    inet_pton(AF_INET, argv[1], &(serverAddr.sin_addr));

    // 绑定套接字到地址和端口
    int ret = bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
    assert(ret != -1);

    ret = listen(serverSocket, 5);
    assert(ret != -1);

    // 监听的文件描述符队列
    /*
        创建一个pollfd类型的空向量
        并且将之前创建的serverSocket加入到其中进行监听
        POLLIN是我们所监视的事件类型,这点笔记中有写

        为什么需要监视serverSocket?
        答:这是为了实现服务器的异步IO,通过监视serverSocket及时检测到下面两种情况:
            1. 当有新的客户端连接请求到达时,我们希望能够立即进行处理。
                通过监视serverSocket上的POLLIN事件,可以检测到是否有客户端尝试建立连接
            2. 当serverSocket上出现其他错误情况(如连接断开或发生错误)时,我们也希望能及时进行处理
                通过监视serverSocket上的激长时间,如POLLHUP或POLLERR,可以检测到这些错误情况
    */
    vector<pollfd> fds;
    fds.push_back({serverSocket, POLLIN});

    while(true){
        int numRead = poll(fds.data(), fds.size(), -1);
        if(numRead < 0){
            cerr << "poll error";
            return 1;
        }

        // 
        for(auto &fd : fds){
            /*
                确保有新的连接
                但是不是很理解为什么是一直监听serverSocket
            */
            if(fd.fd == serverSocket && fd.revents & POLLIN){
                // 有新连接
                clientAddrLen = sizeof(clientAddr);
                clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);
                if(clientSocket < 0){
                    cerr << "Failed to accepted connection" << endl;
                    return 1;
                }
                else{
                    cout << "New connection from:" << inet_ntoa(clientAddr.sin_addr) << endl;
                    fds.push_back({clientSocket, POLLIN});
                }
            }else if(fd.revents & POLLIN){
                // 需要读取信息
                // 此时的不是监听socket,而是客户端的连接socket
                char buffer[1024];
                ssize_t bytesRead = recv(fd.fd, buffer, sizeof(buffer), 0);
                if(bytesRead <= 0){
                    if(bytesRead < 0){
                        cerr << "Error reading from client." << endl;
                    }
                    else{
                        cout << "Connection clost by client." << endl;
                    }
                    close(fd.fd);
                    // 这个remove_if()不是很熟
                    fds.erase(remove_if(fds.begin(), fds.end(),
                            [&](const pollfd& pfd){ return pfd.fd == fd.fd; }),
                            fds.end());
                }else{
                    // 处理数据
                    cout << "Received data: " << string(buffer, bytesRead) << endl;
                    // 将收到的数据回发给客户端
                    send(fd.fd, buffer, bytesRead, 0);
                }
            }
        }
    }

    // 关闭监听套接字
    close(serverSocket);
    return 0;
}

总结

学到这里我就发现:想要真正去理解Linux网络编程,还是要懂Linux内核,比如:套接字和文件描述符的联系?要去了解下select和poll的工作原理,这样才能理解程序为何这么编写。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在计算机网络编程领域中,Linux系统的网络编程技术有着非常重要的作用。如果要对Linux网络编程技术进行深入学习并掌握相关技术,在网络编程的基础知识学习之后,更需要好的网络编程指导书籍,其中之一就是《Linux网络编程》这本pdf。 该PDF以C语言为基础,主题集中在Linux系统下的socket编程。从最基础的使用socket创建TCP/IP连接,再到linux网络编程中比较重要的IO复用技术(如select/poll/epoll/kqueue),再到IPV6编程网络安全以及多线程程序的编写,都有详细的说明和样例。此外,这本pdf还解释了许多难点读者通常会面临的系统调用和API,比如getaddrinfo、getnameinfo等。 总的来说,这本pdf具备以下七个方面的优点: 1. 适合初学者,从最基础的socket连接开始,循序渐进掌握网络编程技能。 2. 含有丰富的实例代码,读者可以从中学到标准的Linux网络编程模型。 3. 全面介绍IO复用的实现技术,能够有效提高服务端程序的并发能力和处理性能。 4. 在IPv6协议栈的更新和普及的背景下,该pdf更新了原有的IPv4编程技术,并为读者讲解了IPv6编程中需要注意的各种细节和改进。 5. 详细讲解了网络安全方面的内容,使读者了解到许多网络编程的安全问题和如何进行防范。 6. 内容简单易懂,配合详细的图示和实例代码,真正做到"所见所得"。 7. 不仅适合初学者,对于有经验的网络工程师也是一本很有价值的参考书,并能够解决许多实际网络编程场景中出现的问题。 因此,这本pdf对于想要学习Linux网络编程技术,从网络编程基础和IO复用技术的学习到网络安全、多线程程序以及IPv6编程的学习都具有非常大的参考价值,特别是对于初学者来说,经过深入学习后,能够对Linux网络编程技术有更加深入的理解,为参加面试和实际项目工作奠定更坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

默示MoS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值