多路io复用Select [补档-2023-07-16]
select
2.1 简介
select函数可以用于实现高效的多路复用 I/O,同时处理多个文件描述符的事件,包括监听可读、可写和异常条件,具有阻塞和非阻塞模式,并可以设置超时时间。这使得程序能够高效地处理并发任务,提高性能和响应性。
2.2 select函数
头文件:#include <sys/select>
函数原型:int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
函数参数:
nfds:监视文件描述符的最大值加一(文件描述符集合中所有的文件描述符的最大值+1)
readfds:指向一个文件描述符集合,用于监视是否有数据可读。
writefds:指向一个文件描述符集合,用于监视是否可以写入数据。
exceptfds:指向一个文件描述符集合,用于监视是否有异常情况。
timeout:指向一个struct timeval结构体,表示超时时间,即select函数最多等待的时间。
结构体struct timeval 的成员:
time_t tv_sec:表示秒数。
suseconds_t tv_usec:表示微妙数。
函数返回值:
成功返回准备就绪的文件描述符总数。
监听超时则返回0。
发生错误返回-1。
2.3 select的几个宏函数
2.3.1将指定的一个文件描述符从集合中清除
函数原型:FD_CLR(int fd, fd_set *set);
函数参数:fd为指定的文件描述符,set为指定的文件描述符集合
2.3.2 将指定的文件描述符集合清空,所有位置都置为0
函数原型:FD_ZERO(fd_set *set);
函数参数:set为指定的文件描述符集合
2.3.3 将指定的文件描述符加入到指定的集合中
函数原型:FD_SET(int fd, fd_set *set);
函数参数:fd为指定的文件描述符,set为指定的文件描述符集合
2.3.4 判断指定的文件描述符是否在某个文件描述符集合中
函数原型:FD_ISSET(int fd, fd_set *set);
函数参数:fd为指定的文件描述符,set为指定的文件描述符集合
2.4 使用select创建一个多路io复用的服务端:
第一步:创建套接字。
第二步:准备一些变量和常量,用于记录最大文件描述符,客户端发来的数据,循环次数,条件判断等待。
第三步:初始化本地(服务器)地址结构体sockaddr_in。
第四步:将套接字与本地地址绑定,并且建议判断一下是否绑定成功。
第五步:切换为监听状态。
第六步:进入一个无限循环然后持续监听。
马上进入循环时初始化文件描述符集合,进入后将刚才监听的文件描述符添加到里面。
在所有已连接的客户端文件描述符集合中寻找最大的文件描述符,然后记录下来。
使用select函数对整个文件描述符集合进行监听,如果监听失败则退出程序,如果有文件描述符可操作, 则进行具体操作。
当select监听有变化则检测监听的套接字是否有新的连接请求,如果有则使用accept接收连接,并且将accept返回的已经和客户端建立连接的描述符加入到已连接的客户端数组中。
处理客户端的请求。遍历已连接的客户端数组,逐个判断每个客户端文件描述符数组中的元素是否中文件描述符集合中,如果在则代表有数据可读。
从客户端接收数据,并且进行处理。如果无法接收数据则关闭客户端文件描述符然后将其从描述符集合中移除。否则就对客户端发来的数据进行相关操作。
第七步:当循环结束后,记得关闭所有已连接的客户端文件描述符和服务器文件描述符。
2.5 使用select创建的多路io的服务端代码示例
点击查看代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#define MAX_CLIENTS 100 //最大客户端描述符数
#define BUF_SIZE 128 //字符串数组的最大长度
int main()
{
int client_fds[MAX_CLIENTS]; //用于存储客户端文件描述符,用来辅助文件描述符集使用
int server_fd; //服务端文件描述符(用于监听)
fd_set readfds; //可读文件描述符集
char data[BUF_SIZE];//用于存储接收来自客户端的信息
int maxfd; //最大文件描述符
int i;//下面可能会用到循环,它用来循环
int retval; //用于记录select的值
//创建套接字,ipv4,tcp
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
printf("创建套接字失败!\n");
exit(-1);
}
//分别用于存储服务端和客户端的地址信息
struct sockaddr_in server_addr, client_addr;
//初始化本地地址信息
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(10066);
server_sddr.sin_addr.s_addr = htonl(INADDR_ANY);
//将套接字与本地服务器地址绑定
int ret;//用于检测绑定或者切换至监听是否成功
ret = bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret == -1) {
printf("本地地址与套接字失败!\n");
exit(-1);
}
//切换监听
ret = listen(server_fd, MAX_CLIENTS);
if (ret == -1) {
printf("切换监听态失败!\n");
exit(-1);
}
printf("服务器开始监听,监听端口:10066 监听地址:任意\n");
//因为接下来的select需要接收一个集合的最大描述符,先用一个已有的文件描述符给他赋值
maxfd = server_fd;
//初始化客户端文件描述符集合,-1代表可用
for (i = 0; i < MAX_CLIENTS; i++) {
client_fds[i] = -1;
}
FD_ZERO(&readfds); //初始化可读文件描述符集
while (1) {
FD_SET(server_fd, &readfds); //将当前监听的描述符加入到里面
//将已连接的文件描述符加入到集合中
for (i = 0; i < MAX_CLIENTS; i++) {
int client_fd = client_fds[i];
if (client_fd != -1) {
//不等于-1则代表在被使用中代表已连接
FD_SET(client_fd, &readfds);
if (client_fd > maxfd) {
//如果当前已连接的文件描述符比之前的最大描述符要大,记得交换
//因为select函数接收的是集合中值最大的
maxfd = client_fd;
}
}
}
//使用select函数进程监听整个集合
retval = select(maxfd + 1, &readfds, NULL, NULL, NULL);
if (retval == -1) {
//等于-1代表监听失败
printf("监听失败!!!\n");
exit(-1);
}
else if (retval > 0) {
//大于0代表描述符集readfds有变化
//检测socket_fd是否有新的链接
if (FD_ISSET(server_fd, &readfds)) {
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd,(struct sockaddr *)&client_addr,&client_len);
if (client_fd == -1) {
printf("接收链接失败!!!\n");
}
else {
//将刚才接收到的新的来自客户端的socket添加到结合中
for (i = 0; i < MAX_CLIENTS; i++) {
if (client_fds[i] == -1) {
//-1则代表当前数组位置可用
client_fds[i] = client_fd;
break;
}
}
}
}
//处理来自客户端的请求
for (i = 0; i < MAX_CLIENTS; i++) {
int client_fd = client_fds[i];
if (client_fd != -1 && FD_ISSET(client_fd, &readfds)) {
//首先数组元素是被使用的状态,并且要判断数组元素i对应的描述符是否中集合中
memset(data, 0, BUF_SIZE);
//接收信息
ssize_t n = recv(client_fd, data, sizeof(data),0);
if (n <= 0) {
printf("未能接收客户端数据!\n");
close(client_fd);//关闭文件描述符
client_fds[i] = -1;//记得将这个坑置为-1,因为咱们操作完了
}
else {
//回应客户端
printf("服务端已经接收到客户端数据:\n%s\n",data);
if (send(client_fd, data, sizeof(data), 0) == -1) {
printf("回送服务端数据失败!\n");
}
}
}
}
}
}
//用完了记得循环关闭链接
for (i = 0; i < MAX_CLIENTS; i++) {
int client_fd = client_fds[i];
if (client_fd != 1) {
close(client_fd);
}
}
close(server_fd);
return 0;
}
2.6 select的优缺点
select优点:
1 一个进程可以支持多个客户端
2 select支持跨平台
select缺点:
1 代码编写困难
2 会涉及到用户区到内核区的来回拷贝,销毁资源大。
3 当客户端多个连接, 但少数活跃的情况, select效率较低
例如: 作为极端的一种情况, 3-1023文件描述符全部打开, 但是只有1023有发送数据, select就显得效率低 下
4 最大支持1024个客户端连接
select最大支持1024个客户端连接不是有文件描述符表最多可以支持1024个文件描述符限制的, 而是由 FD_SETSIZE=1024限制的.
FD_SETSIZE=1024 fd_set使用了该宏, 当然可以修改内核, 然后再重新编译内核, 一般不建议这么做.
多路io复用Select [补档-2023-07-16]的更多相关文章
- Redis03——Redis之单线程+多路IO复用技术
Redis 是单线程+多路IO复用技术 多路复用:使用一个线程来检查多个文件描述符的就绪状态 如果有一个文件描述符就绪,则返回 否则阻塞直到超时 得到就绪状态后进行真正的操作可以在同一个线程里执行,也 ...
- 多路IO复用模型--select, poll, epoll
select 1.select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数 2.解决1024以下客户端时使用se ...
- Linux C++ 网络编程学习系列(2)——多路IO之select实现
select实现多路IO 源码地址:https://github.com/whuwzp/linuxc/tree/master/select 源码说明: server.cpp: 监听127.1:6666 ...
- 基于select类型多路IO复用,实现简单socket并发
还有很多缺限,如客户断开无限重复 以下转至老师博客: server: #!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = " ...
- Linux企业级项目实践之网络爬虫(27)——多路IO复用
与多线程和多进程相比,I/O多路复用的最大优势是系统开销小,系统不需要建立新的进程或者线程,也不必维护这些线程和进程. 主要应用: (1)客户程序需要同时处理交互式的输入和服务器之间的网络连接 (2) ...
- io复用select方法编写的服务器
摘要:io多路复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般都是读就绪或者写就绪),就能通知应用程序进行相应的读写操作.select函数作为io多路复用的机制,第一个参数nfds是f ...
- 协程与多路io复用epool关系
linux上其实底层都基于libevent.so模块实现的,所以本质一样 gevent更关注于io和其它 epool只是遇到io就切换,而gevent其它等待也切换
- IO复用——select系统调用
1.select函数 此函数用于在一段时间内,监听用户感兴趣的文件描述符上的可读.可写和异常等事件. #include<sys/select.h> int select(int nfds, ...
- 1高并发server:多路IO之select
1 select A:select能监听的文件描写叙述符个数受限于FD_SETSIZE,一般为1024.单纯改变进程打开 的文件描写叙述符个数并不能改变select监听文件个数 B:解决1024 ...
- python3.x 多路IO复用补充asyncio
asyncio模块是python之父写的模块,按说应该是靠谱的,python3.6版本定义为稳定版本. 说明书:https://docs.python.org/3/library/asyncio.ht ...
随机推荐
- #1010:Tempter of the Bone(DFS + 奇偶剪枝)
Problem Description The doggie found a bone in an ancient maze, which fascinated him a lot. However, ...
- POJ - 1611 : The Suspects (普通并查集)
思路: 模板题,一步一步加入集合,最后判断有多少人跟0在同一个集合就行了. #include<iostream> #include<cstdio> using namespac ...
- freeswitch配置SBC的方案
概述 freeswitch 是一款好用的开源软交换平台. 但是,fs不是专为SBC而开发的,所以需要做一些定制化的配置和开发. 本文主要介绍如何利用fs的基本功能配置一个简单的SBC方案,满足一般化需 ...
- python之pycharm常见使用技巧
一.ctrl+d:复制
- KVM 管理工具:libvirt
libvirt 简介 libvirt 是目前使用最为广泛的对 KVM 虚拟机进行管理的工具和应用程序接口.
- (已解决)vscode python 代码高亮异常 - 引入的包不显示
问题情况:识别不了引入的包,代码一片白花花的. 解决方法:点最左下角的齿轮,打开设置,搜索 language,把 python 的 language server 改成 pylance,如下图: 如果 ...
- PMP项目变更管理及变更流程总结
转载请注明出处: 1. 变更管理流程 2.变更管理流程十步: 0 预防--1发起变更请求--2分析影响--3备选方案--4CCB批准--5更新项目管理计划--6沟通干系人--7执行--8检查--9总结 ...
- 基于java+springboot的酒店预定网站、酒店客房管理系统
该系统是基于Java的酒店客房预订系统设计与实现.是给师弟开发的毕业设计.现将源代码开放出来,感兴趣的同学可以下载. 演示地址 前台地址: http://hotel.gitapp.cn 后台地址: h ...
- Angular系列教程之单向绑定与双向绑定
.markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...
- 调整PR界面字体大小
1.问题 界面字体太大或者太小,看得不舒服 2.解决问题 按住ctrl+F12,调出如下工作台 选择Debug Datatbase View 其中找到AdobeCleanFontSize,并修改 重启 ...