select实现多路IO

  1. 源码地址:https://github.com/whuwzp/linuxc/tree/master/select
  2. 源码说明:
    • server.cpp: 监听127.1:6666,功能是将收到的小写转大写
    • include/wrap.cpp: 封装的一些socket基本操作,加了基本的错误处理

1. 概要

int select(int nfds, fd_set *restrict readfds,
fd_set *restrict writefds, fd_set *restrict errorfds,
struct timeval *restrict timeout);
void FD_CLR(int fd, fd_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_ZERO(fd_set *fdset);

1.1 select函数

//原型
int select(int nfds, fd_set *restrict readfds,
fd_set *restrict writefds, fd_set *restrict errorfds,
struct timeval *restrict timeout);
//我的代码中的
nselect = select(fd_max + 1, &readfds, nullptr, nullptr, nullptr);
  1. fd_set类型数据: 用位图表示1024个fd(文件描述符)集合,sizeof看了该类型大小为128字节,也就是1024bit,应该是第n个bit表示fd为n的文件描述符,这也符合默认select最多可以监听1024个fd
  2. readfds,writefds,errorfds: 分别是读,写,错误的集合
  3. 以上的fds是输入输出参数,既是函数输入又是输出(以下以readfds为例)
    • 输入: select依次去看readfds的1024bit,谁为1(或为0,这是猜想的),如果为1,假设第n个为1,那么select就会监听fd==n的文件描述符
    • 输出: 如果监听的可读的话,那么就把这个bit置为1,假设第n个为1,则表示fd==n的文件描述符可读
  4. timeout: 就是超时返回, nullptr是永久等待
  5. nfds: 最大的文件描述符,应该是为了节约时间吧,这样就不用去轮询1024个fd,例如只有一个fd为5,那么只用轮询5前的几个,5之后就不用管了

注意:

  1. 由于fds是输入输出参数,所以每次select之后fds的内容都可能被修改,所以下次select前需要给fds重新赋值
  2. FD_SETSIZE默认就是1024

1.2 FD_xxx函数

void FD_CLR(int fd, fd_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_ZERO(fd_set *fdset);
  • FD_CLR: 清除fdset中的fd这个文件描述符,看看fd_set函数,这个实现应该很简单,就是直接第fd个bit置为0
  • FD_ISSET: 判断fd是不是在fdset中,这个用于判断select之后,fd是否有信号到来,实现也很简单,就是看看第fd个bit是不是1
  • FD_SET: 这个是把fd加入到fdset中,一般在select前,设置需要监听的fds
  • FD_ZERO: 清零

2. 核心代码

#include "include/wrap.h"
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdnoreturn.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h> /* See NOTES */
#include <unistd.h>
#include <wait.h>
#define LOCALIP "127.0.0.1"
#define PORT 6666 void handler(char *in, char *out) {
for (int i = 0; i < (int)strlen(out) + 1; ++i) {
out[i] = toupper(in[i]);
}
} int workthread(const int &fd_client) {
char recvbuf[2048] = {0};
char sendbuf[2048] = {0};
int ret = 0; ret = (int)Read(fd_client, recvbuf, 2048);
if (ret <= 0) {
printf("ret==0\n");
return ret;
} handler(recvbuf, sendbuf); ret = (int)Write(fd_client, sendbuf, strlen(sendbuf) + 1);
return ret;
} void startsock(int &fd, struct sockaddr_in &addr, const char *ip,
const int port) {
fd = Socket(AF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip);
addr.sin_port = htons(port);
}
int main() {
int fd_server = 0;
int fd_client = 0;
int ret = 0;
struct sockaddr_in sock_client;
struct sockaddr_in sock_server;
socklen_t client_len = (socklen_t)sizeof(sock_client);
int opt = 0;
fd_set readfds;//作为select的参数
fd_set allfds;//保存fds,因为readfds会被修改, 见 1.1 select 注意
int fd_clients[FD_SETSIZE]; //用于保存客户端连接的fd
memset(fd_clients, -1, sizeof(fd_clients)); //用-1表明是没有用过的fd,方便后面找空位
int maxi = 0;
int fd_max = 0;
int nselect = 0;
int i = 0; startsock(fd_server, sock_server, LOCALIP, PORT);
opt = 1;
Setsockopt(fd_server, SOL_SOCKET, SO_REUSEADDR, &opt,
(socklen_t)sizeof(opt));
Bind(fd_server, (struct sockaddr *)&sock_server, sizeof(sock_server));
Listen(fd_server, 5); FD_ZERO(&allfds);
FD_ZERO(&readfds);
FD_SET(fd_server, &allfds);//把listenfd加入待监听的
fd_max = fd_server; while (true) {
readfds = allfds; //见 1.1 select 注意
printf("selecting...\n");
nselect = select(fd_max + 1, &readfds, nullptr, nullptr, nullptr);
if (nselect == -1) {
if (errno == EINTR) {
continue;
} else {
perror_exit("select failed");
}
}
printf("get %d select\n", nselect);
if (FD_ISSET(fd_server, &readfds)) { //看看listenfd有没有信号,就是有没有新连接
fd_client =
Accept(fd_server, (struct sockaddr *)&sock_client, &client_len);
printf("accept: %s: %d\n", inet_ntoa(sock_client.sin_addr),
ntohs(sock_client.sin_port));
for (i = 0; i < FD_SETSIZE; ++i) {
if (fd_clients[i] != -1) continue; //找数组中没有用过的空位存fd_client
fd_clients[i] = fd_client;
break;
}
printf("i: %d, FD_SETSIZE: %d\n", i, FD_SETSIZE);
if (i == FD_SETSIZE) perror_exit("too many clients");//超过1024
if (i > maxi) maxi = i;
if (fd_client > fd_max) fd_max = fd_client;//找最大的fd
FD_SET(fd_client, &allfds);//加入监听集合
nselect--;
}
printf("going to find client, maxi: %d, nselect: %d\n", maxi, nselect);
for (i = 0; (i <= maxi) && (nselect > 0); ++i) {//maxi为了节约时间,不用轮询1024次
if (FD_ISSET(fd_clients[i], &readfds) == 0) continue;//轮询哪个clientfd有信号
printf("find client %d\n", i + 1);
ret = workthread(fd_clients[i]);//处理
if (ret <= 0) {//断开
Close(fd_clients[i]);
FD_CLR(fd_clients[i], &allfds);
fd_clients[i] = -1;
}
nselect--;
}
sleep(3);
}
Close(fd_server);
for (i = 0; i < FD_SETSIZE; ++i) {
if (fd_clients[i] == -1) continue;
Close(fd_clients[i]);
}
}

注意: 代码中maxi和fd_max的操作看上去很繁琐,其实都是为了节约时间,例如

  1. maxi是标记当前fd_clients中用到的最大索引值,这样我们找哪个fd有信号时,就不用轮询fd_clients中1024个fd了;
  2. fd_max是最大的fd,这样nfds就可以方便设置,select就不用监听1024个
  3. 如果觉得繁琐,其实可以全部改成1024,就是浪费

3. 参考网址

  1. https://www.cnblogs.com/alantu2018/p/8612722.html#top
  2. https://www.bilibili.com/video/av53016117

Linux C++ 网络编程学习系列(2)——多路IO之select实现的更多相关文章

  1. Linux C++ 网络编程学习系列(1)——端口复用实现

    Linux C++ 网络编程学习系列(1)--端口复用实现 源码地址:https://github.com/whuwzp/linuxc/tree/master/portreuse 源码说明: serv ...

  2. Linux C++ 网络编程学习系列(6)——多路IO之epoll高级用法

    poll实现多路IO 源码地址:https://github.com/whuwzp/linuxc/tree/master/epoll_libevent 源码说明: server.cpp: 监听127. ...

  3. Linux C++ 网络编程学习系列(5)——多路IO之epoll边沿触发

    多路IO之epoll边沿触发+非阻塞 源码地址:https://github.com/whuwzp/linuxc/tree/master/epoll_ET_LT_NOBLOCK_example 源码说 ...

  4. Linux C++ 网络编程学习系列(4)——多路IO之epoll基础

    epoll实现多路IO 源码地址:https://github.com/whuwzp/linuxc/tree/master/epoll 源码说明: server.cpp: 监听127.1:6666,功 ...

  5. Linux C++ 网络编程学习系列(3)——多路IO之poll实现

    poll实现多路IO 源码地址:https://github.com/whuwzp/linuxc/tree/master/poll 源码说明: server.cpp: 监听127.1:6666,功能是 ...

  6. Linux C++ 网络编程学习系列(7)——mbedtls编译使用

    mbedtls编译使用 环境: Ubuntu18.04 编译器:gcc或clang 编译选项: 静态编译使用 1. mbedtls源码 下载地址: https://github.com/ARMmbed ...

  7. Linux C网络编程学习笔记

    Linux C网络编程总结报告 一.Linux C 网络编程知识介绍: 网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户端:(client) 在网络程序中, ...

  8. linux下网络编程学习——入门实例ZZ

    http://www.cppblog.com/cuijixin/archive/2008/03/14/44480.html 是不是还对用c怎么实现网络编程感到神秘莫测阿,我们这里就要撕开它神秘的面纱, ...

  9. Linux下网络编程学习杂记

    1.TCP/IP协议的体系结构包含四层:应用层(负责应用程序的网络服务,通过端口号识别各个不同的进程)->传输层(传输控制层协议TCP.用户数据报协议UDP.互联网控制消息协议ICMP)-> ...

随机推荐

  1. LeetCode | 707. 设计链表

    设计链表的实现.您可以选择使用单链表或双链表.单链表中的节点应该具有两个属性:val 和 next.val 是当前节点的值,next 是指向下一个节点的指针/引用.如果要使用双向链表,则还需要一个属性 ...

  2. Java后台面试记录

    腾讯一面: 总结:考基础和代码(网址A是不是网址B的子域) + SQL(选出重复邮箱)(以下是没回答上来的) 逻辑回归公式(简历上写了协同过滤) 详见:https://blog.csdn.net/ma ...

  3. JVM年轻代,老年代,永久代详解

    前言 最近被问到了这个问题,解释的不是很清晰,有一些概念略微模糊,在此进行整理和记录,分享给大家.本篇文章主要讲解内存区域的年轻代,老年代和永久代,略微提及一些垃圾回收算法,下面是正文. 堆整体 堆主 ...

  4. 《HelloGitHub》第 48 期

    兴趣是最好的老师,HelloGitHub 就是帮你找到兴趣! 简介 分享 GitHub 上有趣.入门级的开源项目. 这是一个面向编程新手.热爱编程.对开源社区感兴趣 人群的月刊,月刊的内容包括:各种编 ...

  5. JSP(二)----指令,注释,内置对象

    ##  JSP 1.指令 *  作用:用于配置JSP页面,导入资源文件 *  格式: <%@  指令名称  属性名1=属性值1  属性名2=属性值2  %> <%@ page con ...

  6. JSP(一)----入门学习

    ##  JSP 1.概念: *  Java  Server  Pages:java服务端页面 *  可以理解为:一个特殊的页面,其中既可以直接定义html标签,又可以定义java代码 2.原理 *  ...

  7. 【C++】Strassen算法代码

    本文仅代码,无理论解释 实话实说,我觉得这个算法在C系列的语言下,简直垃圾到爆炸--毕竟是一群完全不懂程序数学家对着纸弄出来的,看起来好像非常的有用,实际上耗时是非常爆炸的. 但是<算法导论&g ...

  8. bugku论剑场web解题记录

    前言 国庆这几天感觉没什么好玩的地方,家又离的太远,弱鸡的我便决定刷刷题涨涨知识,于是就有了这篇文章.. 正文 写的不对的地方欢迎指正 web26 打开直接就是代码,这应该就是一道代码审计的题了 这里 ...

  9. Nuget多项目批量打包上传服务器的简明教程

    本篇不会介绍Nuget是什么,如何打包上传Nuget包,怎么搭建私有Nuget服务器.这些问题园子里都有相应的文章分享,这里不做过多阐述.另外本文假设你已经下载了Nuget.exe,并且已经设置好了环 ...

  10. 带权并查集 HDU - 3047

    题意: 一圈座位有n个,给出m组序号之间的关系,比如,1 2 150 代表2号坐在1号位置序号+150,看m组数据有多少组冲突的. 思路: 带权并查集模板. #include<stdio.h&g ...