目录

  • select参数解释
  • select使用规范
  • select使用缺点
  • 基本流程
  • 实例代码
  • 通信效果演示
  • 往期文章

select参数解释

extern int select (int __nfds, fd_set *__restrict __readfds,fd_set *__restrict __writefds,fd_set *__restrict __exceptfds,struct timeval *__restrict __timeout);

__nfds:一般设置为所有需要使用select函数检测时间的fd中的最大值+1
__readfds:需要监听可读事件的fd集合
__writefds:需要监听可写事件的fd集合
__exceptfds:需要监听可写事件的fd集合
__timeout:超时时间,在这个设定的时间内检测这些fd事件,超过这个超时时间,select将立即返回
fd_set这个结构体信息如下:

/* 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];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif} fd_set;

可以简化看做:

typedef struct {long int __fds_bits[16];
} fd_set;

long int 占8字节,也就是8 * 8 = 64个bit,所以__fds_bits数组总共就占64 * 16 = 1024个fd状态。每个bit位0表示无事件,1表示有事件。

select使用规范

1、select在调用前后可能会修改readfdswritefdsexceptfds中的内容,所以如果在下次调用时复用这些fd_set,则要在下次调用前使用FD_ZEROfd_set清空,然后调用FD_SET将要检测的fd添加到fd_set中
2、linux系统下select函数也会修改timeval结构体的值,所以想要复用也必须给其重新设置值
3、select函数的timeval结构体中的tv_sectv_usec如果都被设置为0,代表着检测事件的总时间被设置为0,行为就变成了select检测相关集合中的fd,如果没有需要的事件,则立即返回
4、如果select函数的timeval参数设置为NULL,则select会一直阻塞下去,直到我们需要的事件被触发
5、Linux下,select函数第一个参数必须设置为需要检测事件fd中最大值+1,所以每次产生一个新fd都需要和maxfd作比较

select使用缺点

1、select函数需要将fd集合从用户态拷贝到内核态,在fd较多时开销较大。并且每次检测时也是在内核中遍历这个fd_set。
2、单个进程能够监视的文件描述符数量上存在最大限制,linux上我们算过了,只有1024个
3、select函数每次调用之前都要对传入的参数重新设定,比较麻烦
4、在linux上select函数的实现原理是底层的poll函数,所以select和poll本质上没有区别

基本流程

实例代码

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <sys/time.h>
#include <vector>
#include <errno.h>
#include <stdio.h>
#include <string.h>
using namespace std;
int main() {// 创建一个监听socketint listenfd = socket(AF_INET, SOCK_STREAM, 0);if (listenfd == -1) {cout << "create listen error" << endl;return -1;}// 初始化服务器地址struct sockaddr_in bindaddr;bindaddr.sin_family = AF_INET;bindaddr.sin_addr.s_add当断开各个客户端时,服务端的select函数对各个客户端fd进行检测时,仍然会触发可读事件r = htonl(INADDR_ANY);bindaddr.sin_port = htons(3000);if (bind(listenfd, (struct sockaddr *)& bindaddr, sizeof(bindaddr)) == -1) {cout << "bind listen socket error" << endl;close(listenfd);return -1;}// 启动监听if (listen(listenfd,SOMAXCONN) == -1) {cout << "listen error" << endl;close(listenfd);return -1;}// 存储客户端socket的数组vector<int> clientfds;int maxfd;while (true) {fd_set readset;// 对标志位清零FD_ZERO(&readset);// 将监听的socket加入到待检测的可读事件中// 第listenfd位被置为1FD_SET(listenfd, &readset);maxfd = listenfd;// 将客户端socket加入到待检测的可读事件中for (int i = 0; i < clientfds.size(); i++) {if (clientfds[i] != -1) {FD_SET(clientfds[i], &readset);maxfd = max(maxfd, clientfds[i]);}}// 设置超时时间为1stimeval time;time.tv_sec = 1;time.tv_usec = 0;// 暂且只检测可读事件,不检测可写和异常事件int ret = select(maxfd + 1, &readset, NULL, NULL, &time);if (ret == -1) {// 出错if (errno != EINTR)break;} else if (ret == 0) {// select函数超时continue;} else {// 检测到某个socket有事件// 是否是监听socket的可读事件if (FD_ISSET(listenfd, &readset)) {// 如果是监听socket的可读事件,表示现在有新的连接到来struct sockaddr_in clientaddr;socklen_t clientaddrlen = sizeof(clientaddr);// 接受客户端连接int clientfd = accept(listenfd, (struct sockaddr *)& clientaddr, &clientaddrlen);if (clientfd == -1) {cout << "client socket error" << endl;break;} else {cout << "accept a client connection , fd:" << clientfd << endl;clientfds.push_back(clientfd);}} else {// 从client socket上接受数据// 假设对端发送过来的数据长度不超过63个字符char recvbuf[64];for (int i = 0; i < clientfds.size(); i++) {if (clientfds[i] != -1 && FD_ISSET(clientfds[i], &readset)) {memset(recvbuf, 0, sizeof(recvbuf));// 接受数据int length = recv(clientfds[i], recvbuf, 64, 0);if (length <= 0) {cout << "recv data error, clientfd:" << clientfds[i] << endl;close(clientfds[i]);// 不直接删除该元素而是将位置元素标记clientfds[i] = -1;continue;} else {cout << "clientfd:" << clientfds[i] << "recv data: " << recvbuf << endl;}}}}}}// 处理之后,关闭所有客户端for (int i = 0; i < clientfds.size(); i++) {if (clientfds[i] != -1)close(clientfds[i]);}// 关闭监听close(listenfd);return 0;
}

通信效果演示

这里不需要写客户端程序,直接用nc命令模拟,指定一下服务端的ip地址和端口号就可以通信了,127.0.0.1就是系统的回环地址,直接是用于本机内的socket的通信。这里我们开了两个客户端,分别发送hello和hello world。
由于nc命令发送的数据是按换行符区分的,所以数据包最后一个都是以\n结束。
当断开各个客户端时,服务端的select函数对各个客户端fd进行检测时,仍然会触发可读事件
不过此时对这些fd进行调用recv函数的话会返回0,表示对端关闭了连接。然后服务端这边将fd进行置-1,然后关闭连接即可。

往期文章

C++网络编程快速入门(一):TCP网络通信基本流程以及基础函数使用

C++网络编程快速入门(二):Linux下使用select演示简单服务端程序相关推荐

  1. C++网络编程快速入门(四):EPOLL模型使用

    目录 基本使用方法 step1:创建epollfd step2:将fd绑定到epollfd step3:调用epoll_wait检测事件 epoll_wait与poll.select区别所在 水平触发 ...

  2. 【Socket网络编程进阶与实战】------ Socket网络编程快速入门

    前言 本篇博客主要是分享,socket网络编程进阶与实践☞socket网络编程快速入门 一.聊一聊Socket 学习目标与收获

  3. 网络编程(51)—— Windows下使用select进行IO复用

    本文主要介绍在Windows系统下使用select搭建回声服务端的方法.在之前的<网络编程(16)-- IO复用技术之select>一文中我们介绍了在Linux使用Select进行IO复用 ...

  4. Windows下C语言网络编程快速入门

    C语言的学习,一般的方式是,先学C,然后是C++,最好还要有汇编语言和微机原理基础,然后才是Visual C++.这样的方式,对学习者来说,要花费很多时间和耐力.而在学校教学中,也没有时间深入学习Wi ...

  5. Winsock网络编程快速入门

     一.基本知识 1.Winsock,一种标准API,一种网络编程接口,用于两个或多个应用程序(或进程)之间通过网络进行数据通信.具有两个版本: Winsock 1: Windows CE平台支持. ...

  6. C++网络编程快速入门(三):阻塞与非阻塞式调用网络通信函数

    目录 阻塞与非阻塞定义 send与recv connect 一些问题 为什么要将监听socket设置为非阻塞 阻塞与非阻塞定义 阻塞模式指的是当前某个函数执行效果未达预期,该函数会阻塞当前的执行线程, ...

  7. C++网络编程快速入门(一):TCP网络通信基本流程以及基础函数使用

    目录 流程概述 服务器端代码实现 客户端代码实现 函数和结构讲解 sockaddr_in和sockaddr socket : 创建一个socket连接 bind :绑定地址以及端口号问题 流程概述 客 ...

  8. 2、Linux下编译并搭建AzerothCore服务端

    目录 一.VM虚拟机 1 - 初始化虚拟机 2 - 安装ubuntu-20.04-desktop-amd64 3 - 系统设置 4 - root用户设置 5 - 查看ip地址 二.编码源码 1 - U ...

  9. Linux下客户端主动Close,服务端阻塞的read函数不会马上返回

    今天在做一个socket通信的时侯,发现close后,阻塞的read函数并没有马上返回,导致后面的时序出现问题,具体情况如下: 服务端与客户端正常建立通信,都采用阻塞模式.当客户端close后,服务端 ...

最新文章

  1. 使用.NET FileSystemWatcher对象监控磁盘文件目录的改变
  2. “金星生命论”乌龙事件新进展:那只是二氧化硫而已
  3. Vue开发跨端应用(六)添加onsenui组件库
  4. 程序员在囧途之软件投标实战
  5. helm部署Loki
  6. Spring Boot中使用Spring-data-jpa让数据访问更简单、更优雅
  7. oracle path函数,自定义类似 sys_connect_by_path 功能的函数
  8. 手机网络游戏SDK集成指南
  9. javascript-mqtt
  10. USB免驱NFC读写器 Android系统中NFC读写范例
  11. mycncart 之中国银联在线支付方式
  12. 使用java自带的方式调用打印机打印图片
  13. C++中endl的本质是什么
  14. php mysql好学吗_零基础小白PHP开发好学吗
  15. php远程下载到本地,PHP 下载远程文件到本地的简单示例
  16. 如何将交叉引用参考文献批量变为上标
  17. Microsoft Word 文档修改默认字体
  18. 新手学CAD入门到精通,迅捷CAD图库大全免费下载!
  19. 易创索讯-SEO网站优化获得销售的6种策略!
  20. Python爬虫爬取相关图片

热门文章

  1. 毕业设计C语言网吧管理系统,毕业设计网吧管理系统.doc
  2. 杭电考研计算机专业课_杭州电子科技大学计算机考研专业课平均分76.03,国家线复试是这个原因!...
  3. 28. css样式中px转rem
  4. random模块详解
  5. block,inline,inline-block的区别
  6. laravel中的自定义函数的加载和第三方扩展库加载
  7. FFT实现高精度乘法
  8. vue.js--基础事件定义,获取数据,执行方法传值
  9. [转] vim自定义配置 和 在ubnetu中安装vim
  10. android:configChanges属性总结