当前位置: 首页 > news >正文

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

文章目录

    • poll模型
      • pollfd结构体
      • nfds_t的定义
    • 一个简单的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,而是客户端的连接socketchar 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的工作原理,这样才能理解程序为何这么编写。

http://www.lryc.cn/news/300720.html

相关文章:

  • Excel模板2:进度条甘特图
  • 数据结构:4_二叉树
  • 设计模式之:状态模式(State Pattern)
  • 【微服安全】API密钥和令牌与微服务安全的关系
  • Mock.js
  • 【c++】list详细讲解
  • C#面:在.NET中 类 System.Web.UI.Page 可以被继承吗?
  • AI:128-基于机器学习的建筑物能源消耗预测
  • php基础学习之可变函数(web渗透测试关键字绕过rce和回调函数)
  • MongoDB聚合操作符:$acos
  • 开源PDF工具 Apache PDFBox 认识及使用(知识点+案例)
  • 微软.NET6开发的C#特性——委托和事件
  • 卷积神经网络的基本结构
  • python:使用GDAL库读取遥感影像指定行列数/经纬度坐标的像素值
  • Redis篇----第一篇
  • C语言-----用二维数组解决菱形的打印问题
  • .NET Core WebAPI中使用swagger版本控制,添加注释
  • css篇---移动端适配的方案有哪几种
  • 一、部署Oracle
  • 11-编写自动化测试
  • 爱上JVM——常见问题(一):JVM组成
  • C#系列-EF扩展框架AutoMapper应用实例(40)
  • DataX源码分析-插件机制
  • 容器高级知识: 适配器模式与 Sidecar 模式的区别
  • 使用Xdisplay将ipad作为扩展显示器Agent闪退问题
  • DVXplorer事件相机入门
  • ubuntu屏幕小的解决办法
  • 黑群晖一键修复:root、AME、DTS、转码、CPU型号等
  • Repo命令使用实例(三十八)
  • 2024年华为OD机试真题-分披萨-Python-OD统一考试(C卷)