最近学习route组件,了解了些关于tcp通讯中I/O复用的知识。好比:select,poll,epoll。目前系统主要是用select。原本觉得select是个好东西,解决了单进程单线程的server能够链接多个客户端的问题。后来,同事跟我说read函数是阻塞的,那么链接创建后,server会阻塞在read处,其余链接就无法正常工做了。而后这个问题就一直困扰着。想起了以前在知乎上有个问题是:怎么设计tcp链接?有个点赞不少的是建多线程,一个线程一个链接。可是,也有很多人批判这个设计,说这样太费资源,指出I/O多路复用中使用相似于select在一个线程中能够实现链接多个客户端。当时也是没想明白,并且公司route组件(老版本)的设计是一个线程一个链接,apache也是一个进程一个链接,坏处就是链接数量不多,毕竟进程切换是耗cpu的。后来就查阅资料,而后经过代码测试,read的阻塞能够不会干扰其余链接的,一个server连N客户端跟连一个客户端一个麻溜溜的。测试代码以下: apache
1 for (;;) { 2 memset(szBuf, 0, sizeof(szBuf)); 3 FD_ZERO(&fset); 4 FD_SET(fd, &fset); 5 tv.tv_sec = 5; 6 tv.tv_usec = 0; 7 8 for (int i = 0; i < BACKLOG; i++) { 9 if (fd_A[i] != 0) 10 FD_SET(fd_A[i], &fset); 11 } 12 13 ret = select(maxfd+1, &fset, NULL, NULL, &tv); 14 15 if (ret < 0) { 16 printf("select调用发生错误\n"); 17 break; 18 } 19 else if (ret == 0) { 20 printf("select timeout\n"); 21 continue; 22 } 23 else { 24 printf("select normal\n"); 25 } 26 27 for (int i = 0; i < BACKLOG; i++) { 28 if (fd_A[i] && FD_ISSET(fd_A[i], &fset)) { 29 printf("recv before\n"); 30 if ((ret = recv(fd_A[i], szBuf, sizeof(szBuf), 0)) == 0) { 31 close(fd_A[i]); 32 FD_CLR(fd_A[i], &fset); 33 fd_A[i] = 0; 34 conn_amount--; 35 } 36 else { 37 printf("fd_A[%d]:%s", i, szBuf); 38 } 39 } 40 } 41 42 if (FD_ISSET(fd, &fset)) { 43 newfd = accept(fd, (struct sockaddr *)&cli_addr, &cli_len); 44 if (newfd <= 0) { 45 printf("accept出错\n"); 46 continue; 47 } 48 else 49 printf("accept normal\n"); 50 。。。
当客户端connect链接上的时候,会输出"select normal" "accept normal",没有走到read/recv这块;若是5秒内客户端没其余操做,server就会在select处超时(select的超时时间设置的是5秒)。接着客户端调用send,而后代码会走到select->read这块,并无进去accept。由于在调用accept,recv/read以前我用了FD_ISSET来判断。多线程
fd_set是一组文件描述符(fd)的集合,它用一位来表示一个fd。至于fd有多大,操做系统定义了常量FD_SETSIZE。在好久 之前是32,如今通常是1024。select函数用于检查fd_set集合中是否有可读的,同时也会更新fd_set集合。FD_ISSET用于测试指定的文件描述符是否在该集合中。假设如今客户端1是成功链接的,若是客户端2发起链接,那么select后客户端1对应fd使用FD_ISSET后返回值是false的,那么就不去调用recv/read函数。若是客户端1发送数据过来,select检测到后,使用FD_ISSET判断链接1返回true,能够用recv/read不会阻塞;使用FD_ISSET判断链接2的返回是false的,不去调用recv/read函数。tcp
同时,客户端在send的时候一次发2k数据,在server接收一次1k的,第一次没取完,select会再次检测到该fd可读,再收一次,正好2k,select才不会检测到该fd可读。这个例子是一个简单的非阻塞(NIO)的例子,难点就是对于半包问题要处理好。不少时候咱们接收到的数据要完整了才行进行decode。函数