网络编程0x05 select函数

2023-12-09 21:52
文章标签 函数 编程 网络 select 0x05

本文主要是介绍网络编程0x05 select函数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 事件分类
      • 读事件就绪
      • 写事件就绪
      • 异常事件就绪
    • 接口API
    • select示例
    • nc安装和使用
      • nc安装
      • nc常用命令
    • select函数使用注释事项

select函数的作用是检测一组socket中某个或者某几个是否有"事件"就绪。

事件分类

读事件就绪

  • socket内核中,接收缓冲区中的子节数大于等于低水位标记SO_RCVLOWAT, 此时调用recv或者read函数可以无阻塞的读该描述符,并且返回值大于0
  • TCP对端关闭连接,此时调用recv或read函数对socket进行读,返回值为0
  • 监听fd上有新连接请求
  • socket上有未处理的错误

写事件就绪

  • socket内核中,发送缓冲区中的可用字节数(发送缓冲区的空闲位置)大于等于低水位标记SO_SNDLOWAT, 此时可以无阻塞的写,并且返回值大于0
  • socket的写操作被关闭(调用了close或者shutdown函数),对于一个写操作被关闭的socket进行写操作,会触发SIGPIPE信号
  • socket有非阻塞的connect连接成功或者失败之后

异常事件就绪

  • socket上收到带外数据

接口API

/* According to POSIX.1-2001 */
#include <sys/select.h>/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

select参数说明:

  • nfds: 所有需要使用select函数监听fd中最大fd值加1
  • readfds: 需要监听可读事件的fd集合
  • writefds: 需要监听可写事件的fd集合
  • exceptfds: 需要监听异常事件的fd集合
  • timeout: 超时时间,即在这个参数设定的时间内检测这些fd事件,超过这个时间后select函数将立即返回。

相关结构体:

timeout超时时间的结构体为struct timeval

struct timeval {long    tv_sec;         /* seconds */long    tv_usec;        /* microseconds */
};

select函数超时总时间是timeout->tv_sec和timeout->tv_usec之后,前者单位是秒,后者单位是微秒

readfds/writefds/exceptfds的类型都是fd_set。其结构如下:

//  sys/select.h /* The fd_set member is required to be an array of longs.  */
typedef long int __fd_mask;/* Some versions of <linux/posix_types.h> define this macros.  */
#undef	__NFDBITS
/* It's easier to assume 8-bit bytes than to get CHAR_BIT.  */
#define __NFDBITS	(8 * (int) sizeof (__fd_mask))    // 64
#define	__FD_ELT(d)	((d) / __NFDBITS)
#define	__FD_MASK(d)	((__fd_mask) 1 << ((d) % __NFDBITS))/* fd_set for select and pselect.  */
typedef struct{/* XPG4.2 requires this member name.  Otherwise avoid the namefrom the global namespace.  */
#ifdef __USE_XOPEN__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];  // 1024/64 = 16
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif} fd_set;/* Maximum number of file descriptors in `fd_set'.  */
#define	FD_SETSIZE		__FD_SETSIZE    // 1024/* Access macros for `fd_set'.  */
#define	FD_SET(fd, fdsetp)	__FD_SET (fd, fdsetp)
#define	FD_CLR(fd, fdsetp)	__FD_CLR (fd, fdsetp)
#define	FD_ISSET(fd, fdsetp)	__FD_ISSET (fd, fdsetp)
#define	FD_ZERO(fdsetp)		__FD_ZERO (fdsetp)extern int select (int __nfds, fd_set *__restrict __readfds,fd_set *__restrict __writefds,fd_set *__restrict __exceptfds,struct timeval *__restrict __timeout);/* We don't use `memset' because this would require a prototype andthe array isn't too big.  */
#define __FD_ZERO(s) \do {									      \unsigned int __i;							      \fd_set *__arr = (s);						      \for (__i = 0; __i < sizeof (fd_set) / sizeof (__fd_mask); ++__i)	      \__FDS_BITS (__arr)[__i] = 0;					      \} while (0)
#define __FD_SET(d, s) \((void) (__FDS_BITS (s)[__FD_ELT(d)] |= __FD_MASK(d)))
#define __FD_CLR(d, s) \((void) (__FDS_BITS (s)[__FD_ELT(d)] &= ~__FD_MASK(d)))
#define __FD_ISSET(d, s) \((__FDS_BITS (s)[__FD_ELT (d)] & __FD_MASK (d)) != 0)
  • FD_SET(fd, fdsetp)宏 将一个fd添加到fdsetp这个集合中
  • FD_CLR(fd, fdsetp)宏 从fdsetp集合中删除一个fd
  • FD_ISSET(fd, fdsetp)宏 检测对应位置上是否置1
  • FD_ZERO(fdsetp)宏 将fdsetp中所有fd都清除掉

select示例

/*** @brief select示例*/ 
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>#include <iostream>
#include <vector>
#include <mutex>std::mutex g_mutex;#ifndef LOG_MSG
#define LOG_MSG(x)  do {                                            \std::lock_guard<std::mutex> locker(g_mutex);\std::cout << x << std::endl;                \} while(0)
#endif //LOG_MSGconstexpr int32_t kInvalidFd = -1;int main(int argc, char *argv[])
{if (argc < 3){LOG_MSG("Usage: ");LOG_MSG("          " << argv[0] << " $port $ip");return -1;}// 创建监听套接字int listenfd = socket(AF_INET, SOCK_STREAM, 0);if (listenfd < 0){LOG_MSG("create socket fail");return -1;}// init server addrsockaddr_in bindaddr;bindaddr.sin_family = AF_INET;bindaddr.sin_addr.s_addr = inet_addr(argv[2]);      // htonl(INADDR_ANY);bindaddr.sin_port= htons(::atoi(argv[1]));if(bind(listenfd,(struct sockaddr*) &bindaddr, sizeof(bindaddr)) == -1){LOG_MSG("bind error");close(listenfd);return -1;}// start listenif(listen(listenfd, SOMAXCONN) == -1){LOG_MSG("listen error");close(listenfd);return -1;}std::vector<int> clientfds;char recvbuf[256];int maxfd = listenfd;do{fd_set readset;FD_ZERO(&readset);  FD_SET(listenfd, &readset);for (auto& item : clientfds){if (item != kInvalidFd){FD_SET(item, &readset);}}timeval tm;tm.tv_sec = 1;tm.tv_usec = 0;const auto ret = select(maxfd + 1, &readset, nullptr, nullptr, &tm);if (ret < 0){if (errno != EINTR)break;}else if (ret == 0){// timeoutcontinue;}else{if (FD_ISSET(listenfd, &readset)){// 检查监听fd上是否有读事件struct sockaddr_in clientaddr;socklen_t client_len = sizeof(clientaddr);int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &client_len);if (clientfd < 0){// 连接出错,退出break;}LOG_MSG("accept a client connection, fd " << clientfd << " : " << inet_ntoa(clientaddr.sin_addr));clientfds.emplace_back(clientfd);if (clientfd > maxfd)maxfd = clientfd;}else{for (auto& clifd : clientfds){if (clifd != kInvalidFd && FD_ISSET(clifd, &readset)){memset(recvbuf, 0, sizeof(recvbuf));int length = recv(clifd, recvbuf, 256, 0);if (length <= 0 && errno != EINTR){LOG_MSG("recv data error, clienfd: " << clifd);close(clifd);clifd = kInvalidFd;continue;}LOG_MSG("clientfd: " << clifd << ", recv data: " << recvbuf);}}}}} while (true);for (const auto& clifd : clientfds){if (clifd != kInvalidFd){close(clifd);}}close(listenfd);return 0;
}

程序执行后输出结果如下:

# 启动服务端
➜  build ./test_select 8899 127.0.0.1
accept a client connection, fd 4 : 127.0.0.1
clientfd: 4, recv data: 33312222clientfd: 4, recv data: 455533322332clientfd: 4, recv data: 4444recv data error, clienfd: 4
# 启动客户端
➜  ~ nc -v 127.0.0.1 8899
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connected to 127.0.0.1:8899.
33312222
455533322332
4444

由于nc发送的数据是按换行符来区分的,每个数据包默认的换行符以\n结束,所以服务端收到数据后,显示出来的数据每一行下面都一个空白行。

nc安装和使用

nc安装

在CentOS上,可以使用以下命令安装nc:

yum update  
yum install nc

nc常用命令

  1. 打开一个终端并启动nc监听器:nc -lnvp <端口号>
# 监听端口号为1234的端口
nc -lnvp 1234
  1. 在另一个终端中,输入以下命令以连接到nc监听器:nc <IP地址> <端口号>
# 连接到IP地址为192.168.1.100的计算机上的端口号为1234
nc 192.168.1.100 1234

select函数使用注释事项

  1. select函数调用前后会修改readfds/writefds/exceptfds这3个集合中的内容,所以下次调用select复用这个变量,需要再次调用FD_ZERO将集合清理,然后调用FD_SET将需要检测的事件fd再次添加进去。
  2. select函数也会修改timeval结构体的值,如果我们需要复用这个变量,需要给timeval变量重新设置值。
  3. select函数的timeval结构体的tv_sec和tv_usec这个两个值设置为0, 即检测事件设置超时时间为0. 其行为是select会检测相关集合中的fd, 如果没有就绪的事件,则立即返回。

这篇关于网络编程0x05 select函数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/475207

相关文章

如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解

《如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解》:本文主要介绍如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别的相关资料,描述了如何使用海康威视设备网络SD... 目录前言开发流程问题和解决方案dll库加载不到的问题老旧版本sdk不兼容的问题关键实现流程总结前言作为

Python itertools中accumulate函数用法及使用运用详细讲解

《Pythonitertools中accumulate函数用法及使用运用详细讲解》:本文主要介绍Python的itertools库中的accumulate函数,该函数可以计算累积和或通过指定函数... 目录1.1前言:1.2定义:1.3衍生用法:1.3Leetcode的实际运用:总结 1.1前言:本文将详

轻松上手MYSQL之JSON函数实现高效数据查询与操作

《轻松上手MYSQL之JSON函数实现高效数据查询与操作》:本文主要介绍轻松上手MYSQL之JSON函数实现高效数据查询与操作的相关资料,MySQL提供了多个JSON函数,用于处理和查询JSON数... 目录一、jsON_EXTRACT 提取指定数据二、JSON_UNQUOTE 取消双引号三、JSON_KE

MySQL数据库函数之JSON_EXTRACT示例代码

《MySQL数据库函数之JSON_EXTRACT示例代码》:本文主要介绍MySQL数据库函数之JSON_EXTRACT的相关资料,JSON_EXTRACT()函数用于从JSON文档中提取值,支持对... 目录前言基本语法路径表达式示例示例 1: 提取简单值示例 2: 提取嵌套值示例 3: 提取数组中的值注意

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

Java function函数式接口的使用方法与实例

《Javafunction函数式接口的使用方法与实例》:本文主要介绍Javafunction函数式接口的使用方法与实例,函数式接口如一支未完成的诗篇,用Lambda表达式作韵脚,将代码的机械美感... 目录引言-当代码遇见诗性一、函数式接口的生物学解构1.1 函数式接口的基因密码1.2 六大核心接口的形态学

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

SQL Server使用SELECT INTO实现表备份的代码示例

《SQLServer使用SELECTINTO实现表备份的代码示例》在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误,在SQLServer中,可以使用SELECTINT... 在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误。在 SQL Server 中,可以使用 SE

SSID究竟是什么? WiFi网络名称及工作方式解析

《SSID究竟是什么?WiFi网络名称及工作方式解析》SID可以看作是无线网络的名称,类似于有线网络中的网络名称或者路由器的名称,在无线网络中,设备通过SSID来识别和连接到特定的无线网络... 当提到 Wi-Fi 网络时,就避不开「SSID」这个术语。简单来说,SSID 就是 Wi-Fi 网络的名称。比如

Oracle的to_date()函数详解

《Oracle的to_date()函数详解》Oracle的to_date()函数用于日期格式转换,需要注意Oracle中不区分大小写的MM和mm格式代码,应使用mi代替分钟,此外,Oracle还支持毫... 目录oracle的to_date()函数一.在使用Oracle的to_date函数来做日期转换二.日