大家好,欢迎来到IT知识分享网。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以戳这里获取
参数说明
nfds:是一个整数值, 表示集合中所有文件描述符的范围,即所有文件描述符的最大值+1。在windows中不需要管这个。
linux select第一个参数的函数: 待测试的描述集的总个数。 但要注意, 待测试的描述集总是从0, 1, 2, …开始的。 所以, 假如你要检测的描述符为8, 9, 10, 那么系统实际也要监测0, 1, 2, 3, 4, 5, 6, 7, 此时真正待测试的描述符的个数为11个, 也就是max(8, 9, 10) + 1
注意:
1、果你要检测描述符8, 9, 10, 但是你把select的第一个参数定为8, 实际上只检测0到7, 所以select不会感知到8, 9, 10描述符的变化。
2、果你要检测描述符8, 9, 10, 且你把select的第一个参数定为11, 实际上会检测0-10, 但是, 如果你不把描述如0 set到描述符中, 那么select也不会感知到0描述符的变化。
所以, select感知到描述符变化的必要条件是, 第一个参数要合理, 比如定义为fdmax+1, 且把需要检测的描述符set到描述集中。
fd_set:
一个文件描述符集合保存在fd_set变量中,可读,可写,异常这三个描述符集合需要使用三个变量来保存,分别是 readfds,writefds,exceptfds。我们可以认为一个fd_set变量是由很多个二进制构成的数组,每一位表示一个文件描述符是否需要监视。
对于fd_set类型的变量,我们只能使用相关的函数来操作。
void FD_CLR(int fd, fd_set *set);//清除某一个被监视的文件描述符。 int FD_ISSET(int fd, fd_set *set);//测试一个文件描述符是否是集合中的一员 void FD_SET(int fd, fd_set *set);//添加一个文件描述符,将set中的某一位设置成1; void FD_ZERO(fd_set *set);//清空集合中的文件描述符,将每一位都设置为0;
使用案例:
fd_set readfds; int fd; FD_ZERO(&readfds)//新定义的变量要清空一下。相当于初始化。 FD_SET(fd,&readfds);//把文件描述符fd加入到readfds中。 //select 返回 if(FD_ISSET(fd,&readset))//判断是否成功监视 { //dosomething }
readfds:
监视文件描述符的一个集合,我们监视其中的文件描述符是不是可读,或者更准确的说,读取是不是不阻塞了。
writefds:
监视文件描述符的一个集合,我们监视其中的文件描述符是不是可写,或者更准确的说,写入是不是不阻塞了。
exceptfds:
用来监视发生错误异常文件
timeout
struct timeval{ long tv_sec;//秒 long tv_usec;//微秒 }
如果timeout->tv_sec0 && timeout->tv_sec0 ,不等待直接返回,加入的描述符都会被测试,并且返回满足要求的描述符个数,这种方法通过轮询,无阻塞地获得了多个文件描述符状态。
如果timeout->tv_sec!=0 || timeout->tv_sec!=0 ,等待指定的时间。当有描述符复合条件或者超过超时时间的话,函数返回。等待总是会被信号中断。
原理
理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。
执行fd_set set;FD_ZERO(&set);则set用位表示是0000,0000。
若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
若再加入fd=2,fd=1,则set变为0001,0011
执行select(6,&set,0,0,0)阻塞等待
若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。
返回值
成功时:返回三中描述符集合中”准备好了“的文件描述符数量。
超时:返回0
错误:返回-1,并设置 errno
EBADF:集合中包含无效的文件描述符。(文件描述符已经关闭了,或者文件描述符上已经有错误了)。
EINTR:捕获到一个信号。
EINVAL:nfds是负的或者timeout中包含的值无效。
ENOMEM:无法为内部表分配内存。
pselect
#include <sys/select.h> int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);
select和pselect有三个主要的区别:
1、select超时使用的是struct timeval,用秒和微秒计时,而pselect使用struct timespec ,用秒和纳秒。
struct timespec{ time_t tv_sec;//秒 long tv_nsec;//纳秒 }
2、select会更新超时参数timeout 以指示还剩下多少时间,pselect不会。
当pselect的sigmask==NULL时pselect和select一样
当sigmask!=NULL时,等效于以下原子操作:
sigset_t origmask; sigprocmask(SIG_SETMASK, &sigmask, &origmask); ready = select(nfds, &readfds, &writefds, &exceptfds, timeout); sigprocmask(SIG_SETMASK, &origmask, NULL);
接收信号的程序通常只使用信号处理程序来引发全局标志。全局标志将指示事件必须被处理。在程序的主循环中。一个信号将导致select和pselect返回-1 并将erron=EINTR。
我们经常要在主循环中处理信号,主循环的某个位置将会检查全局标志,那么我们会问:如果信号在条件之后,select之前到达怎么办。答案是select会无限期阻塞。
这种情况很少见,但是这就是为什么出现了pselect。因为他是类似原子操作的。
举个栗子:
static volatile sig_atomic_t got_SIGCHLD = 0; static void child_sig_handler(int sig) { got_SIGCHLD = 1; } int main(int argc, char *argv[]) { sigset_t sigmask, empty_mask; struct sigaction sa; fd_set readfds, writefds, exceptfds; int r; sigemptyset(&sigmask); sigaddset(&sigmask, SIGCHLD); if (sigprocmask(SIG_BLOCK, &sigmask, NULL) == -1) { perror("sigprocmask"); exit(EXIT_FAILURE); } sa.sa_flags = 0; sa.sa_handler = child_sig_handler; sigemptyset(&sa.sa_mask); if (sigaction(SIGCHLD, &sa, NULL) == -1) { perror("sigaction"); exit(EXIT_FAILURE); } sigemptyset(&empty_mask); for (;;) { /* main loop */ /* Initialize readfds, writefds, and exceptfds before the pselect() call. (Code omitted.) */ r = pselect(nfds, &readfds, &writefds, &exceptfds, NULL, &empty_mask); if (r == -1 && errno != EINTR) { /* Handle error */ } if (got_SIGCHLD) { got_SIGCHLD = 0; /* Handle signalled event here; e.g., wait() for all terminated children. (Code omitted.) */ } /* main body of program */ } }
总结
select()可以同时监视多个描述符,如果他们没有活动,则正确地将进程置于休眠状态。Unix程序员们经常要处理多个文件描述符的I/O,他们的数据流可能是间歇性的。如果只创建read或者write会导致程序阻塞。
在我们使用select的时候,需要注意:
1、我们应该总是设置timeout=0,因为如果没有可用的数据,程序在运行时间里将无视可做。依赖超时的代码通常是不可移植,并且很难调试。
2、nfds的值一要准备且适当。
3、如果在调用完select之后,你不想检查结果,也不想做出适当的响应,那么文件描述符不需要添加到集合中。
4、select返回后,所有的文件描述符都应该被检查,看看他们是否准备好了。
5、read,recv,write,send,这几个函数不一定读/写你所请求的全部数据。如果他们读/写全部数据,是因为低流量负载和快速流。情况并非重视如此,应该处理你的函数仅管理发送或接收单个字节的情况。
6、除非你真的确信你有少量的数据要处理,否则不要一次只读一个字节,当你每次都能缓冲的时候,尽可能多的读取数据是非常低效的。
7、read,recv,write,send和select都会有返回-1的情况,并set errno的值。这些errno必须被恰当的处理。如果你的程序不会接收到任何信号,那么errno永远都不会等于EINTR,如果你的程序并不会设置非阻塞IO,那么errno就不会等于EAGAIN。
8、调用read,recv,write,send,不要使buffer的长度为0;
9、如果read,recv,write,send调用失败,并且返回的errno不是7中说的那两种情况,或者返回0,意思是“end-of-file”,这种情况下我们不应再将文件描述符传递给select。
10、每次调用select之前,timeout都用重新设置。
11、由于select()修改其文件描述符集,如果调用在循环中使用,则必须在每次调用之前重新初始化这些集。
大多数的操作系统都支持select。相比于试图用线程,进程,IPCS,信号,内存共享等方式来解决问题,select函数更有效且轻松。系统调用poll和select相似,在监视稀疏文件集合的时候更加有效。poll现在也在被广泛的使用,但没有select简便。linux专用的epoll在监视大连数据时比select和poll更加有效。
案例
案例1
下面是”man select “帮助文档中案例:
#include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int main(void) { fd_set rfds;//定义一个能保存文件描述符集合的变量 struct timeval tv;//定义超时时间 int retval;//保存返回值 /* Watch stdin (fd 0) to see when it has input. */ /* 监测标准输入流(fd=0)看什么时候又输入*/ FD_ZERO(&rfds);//初始化集合 FD_SET(0, &rfds);//把文件描述符0加入到监测集合中。 /* Wait up to five seconds. */ /* 设置超时时间为5s */ tv.tv_sec = 5; tv.tv_usec = 0; /*调用select函数,将文件描述符集合设置成读取监测 */ retval = select(1, &rfds, NULL, NULL, &tv); /* Don't rely on the value of tv now! */ /* 这时候的tv值是不可依赖的 */ /*根据返回值类型判断select函数 */ if (retval == -1) perror("select()"); else if (retval) printf("Data is available now.\n"); /* FD_ISSET(0, &rfds) will be true. */ /* 因为值增加了一个fd,如果返回值>0,则说明fd=0在集合中。*/ else printf("No data within five seconds.\n"); exit(EXIT_SUCCESS); }
案例2
下面是”man select_tut “帮助文档中案例:
这个例子更好的说明了select函数的作用,这是一个TCP转发相关的程序,从一个端口转发到另一个端口
#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/time.h> #include <sys/types.h> #include <string.h> #include <signal.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> static int forward_port; #undef max #define max(x,y) ((x) > (y) ? (x) : (y)) static int listen_socket(int listen_port) { struct sockaddr_in a; int s; int yes; if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); return -1; } yes = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof(yes)) == -1) { perror("setsockopt"); close(s); return -1; } memset(&a, 0, sizeof(a)); a.sin_port = htons(listen_port); a.sin_family = AF_INET; if (bind(s, (struct sockaddr *) &a, sizeof(a)) == -1) { perror("bind"); close(s); return -1; } printf("accepting connections on port %d\n", listen_port); listen(s, 10); return s; } static int connect_socket(int connect_port, char *address) { struct sockaddr_in a; int s; if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); close(s); return -1; } memset(&a, 0, sizeof(a)); a.sin_port = htons(connect_port); a.sin_family = AF_INET; if (!inet_aton(address, (struct in_addr *) &a.sin_addr.s_addr)) { perror("bad IP address format"); close(s); return -1; } if (connect(s, (struct sockaddr *) &a, sizeof(a)) == -1) { perror("connect()"); shutdown(s, SHUT_RDWR); close(s); return -1; } return s; } #define SHUT_FD1 do { \ if (fd1 >= 0) { \ shutdown(fd1, SHUT_RDWR); \ close(fd1); \ fd1 = -1; \ } \ } while (0)   网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。 [需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/) 一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长! \ } \ } while (0) [外链图片转存中...(img-D8RasWs0-55)] [外链图片转存中...(img-KWaosUFO-56)] 网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。 [需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/) 一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/156972.html