使用select函数改进客户端/服务器端程序
一、当我们使用单进程单连接且使用readline修改后的客户端程序,去连接使用readline修改后的服务器端程序,会出现一个有趣的现象,先来看输出:
先运行服务器端,再运行客户端,
simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echoser_recv_peek
recv connect ip=127.0.0.1 port=54005
simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echocli_recv_peek
local ip=127.0.0.1 port=54005
可以先查看一下网络状态,
simba@ubuntu:~$ netstat -an | grep tcp | grep 5188
tcp 0 0 0.0.0.0:5188 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:54005 127.0.0.1:5188 ESTABLISHED
tcp 0 0 127.0.0.1:5188 127.0.0.1:54005 ESTABLISHED
可以看出建立了连接,服务器端有两个进程,一个父进程处于监听状态,另一子进程正在对客户端进行服务。
再ps 出服务器端的子进程,并kill掉它,
simba@ubuntu:~$ ps -ef | grep echoser
simba 4549 3593 0 15:57 pts/0 00:00:00 ./echoser_recv_peek
simba 4551 4549 0 15:57 pts/0 00:00:00 ./echoser_recv_peek
simba 4558 4418 0 15:57 pts/6 00:00:00 grep --color=auto echoser
simba@ubuntu:~$ kill -9 4551
这时再查看一下网络状态,
simba@ubuntu:~$ netstat -an | grep tcp | grep 5188
tcp 0 0 0.0.0.0:5188 0.0.0.0:* LISTEN
tcp 1 0 127.0.0.1:54005 127.0.0.1:5188 CLOSE_WAIT
tcp 0 0 127.0.0.1:5188 127.0.0.1:54005 FIN_WAIT2
来分析一下,我们将server子进程 kill掉,则其终止时,socket描述符会自动关闭并发FIN段给client,client收到FIN后处于CLOSE_WAIT状态,但是client并没有终止,也没有关闭socket描述符,因此不会发FIN
段给 server子进程,因此server 子进程的TCP连接处于FIN_WAIT2状态。
为什么会出现这种情况呢,来看client的部分程序:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
void do_echocli(int sock)
{ char sendbuf[1024] = {0}; while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) writen(sock, sendbuf, strlen(sendbuf)); int ret = readline(sock, recvbuf, sizeof(recvbuf)); //按行读取 fputs(recvbuf, stdout); memset(sendbuf, 0, sizeof(sendbuf)); } close(sock); |
客户端程序阻塞在了fgets 那里,即从标准输入读取数据,所以不能执行到下面的readline,也即不能返回0,不会退出循环,不会调用close关闭sock,所以出现上述的情况,即状态停滞,不能向前推进。具体的状态变化可以参见这里。
出现上述问题的根本原因在于客户端程序不能并发处理从标准输入读取数据和从套接字读取数据两个事件,我们可以使用前面讲过的select函数来完善客户端程序,如下所示:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
void do_echocli(int sock)
{ fd_set rset; FD_ZERO(&rset); int nready; char sendbuf[1024] = {0}; while (1) FD_SET(fd_stdin, &rset); if (nready == 0) if (FD_ISSET(sock, &rset)) int ret = readline(sock, recvbuf, sizeof(recvbuf)); //按行读取 fputs(recvbuf, stdout); if (FD_ISSET(fd_stdin, &rset)) if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL) writen(sock, sendbuf, strlen(sendbuf)); close(sock); |
即将两个事件都添加进可读事件集合,在while循环中,如果select返回说明有事件发生,依次判断是哪些事件发生,如果是标准输入有数据可读,则读取后再次回到循环开头select阻塞等待事件发生,如果是套接口有数据可读,且返回为0则说明对方已经关闭连接,退出循环并调用close关闭sock。
重复前面的实验过程,把客户端换成使用select函数修改后的程序,可以看到最后的输出:
simba@ubuntu:~$ netstat -an | grep tcp | grep 5188
tcp 0 0 0.0.0.0:5188 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:5188 127.0.0.1:54007 TIME_WAIT
即
client 关闭socket描述符,server
子进程的TCP连接收到client发的FIN段后处于TIME_WAIT状态,此时会再发生一个ACK段给client,client接收到之后就处于CLOSED状态,这个状态存在时间很短,所以看不到客户端的输出条目,TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximum
segment lifetime)的时间后才能回到CLOSED状态,需要有MSL 时间的主要原因是在这段时间内如果最后一个ack段没有发送给对方,则可以重新发送。
过一小会再次查看网络状态,
simba@ubuntu:~$ netstat -an | grep tcp | grep 5188
tcp 0 0 0.0.0.0:5188 0.0.0.0:* LISTEN
可以发现只剩下服务器端父进程的监听状态了,由TIME_WAIT状态转入CLOSED状态,也很快会消失。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
二、前面我们实现的能够并发服务的服务器端程序是使用fork出多个子进程来实现的,现在学习了select函数,可以用它来改进服务器端程序,实现单进程并发服务。先看如下程序,再来解释:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
/*************************************************************************
> File Name: echoser.c > Author: Simba > Mail: dameng34@163.com > Created Time: Fri 01 Mar 2013 06:15:27 PM CST ************************************************************************/ #include<stdio.h> #define ERR_EXIT(m) \ int main(void) struct sockaddr_in servaddr; if (bind(listenfd, (struct sockaddr*)&servaddr,sizeof(servaddr)) < 0) if (listen(listenfd, SOMAXCONN) < 0) //listen应在socket和bind之后,而在accept之前 int nready; while (1) { if (nready == 0) if (FD_ISSET(listenfd, &rset)) { printf("recv connect ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), FD_SET(conn, &allset); if (--nready <= 0) for (i = 0; i <= maxi; i++) { if (FD_ISSET(conn, &rset)) { } /* select所能承受的最大并发数受 |
程序有点长,但逻辑并不复杂,我们按照正常运行的状况走一下就清晰了。
前面调用socket,listen,bind等函数等初始化工作就不说了。程序第一次进入while
循环,只把监听套接字加入关心的事件,select返回说明监听套接字有可读事件,即已完成连接队列不为空,这时调用accept不会阻塞,返回一个已连接套接字,将这个套接字加入allset,因为第一次运行则nready
= 1,直接continue跳回到while
循环开头,再次调用select,这次会关心监听套接字和一个已连接套接字的可读事件,如果继续有客户端连接上来则继续将其加入allset,这次nready
= 2,继续执行下面的for 循环,然后对客户端进行服务。服务完毕再次回到while 开头调用select
阻塞时,就关心一个监听套接字和2个已连接套接字的可读事件了,一直循环下去。
程序大概逻辑就这样,一些细节就大家自己想想了,比如client数组是用来保存已连接套接字的,为了避免每次都得遍历到FD_SETSIZE-1,保存一个最大不空闲下标maxi,每次遍历到maxi就可以了。每次得到一个conn,要判断一下conn与maxfd的大小。
当得知某个客户端关闭,则需要将conn在allset中清除掉。之所以要有allset 和 rset 两个变量是因为rset是传入传出参数,在select返回时rset可能被改变,故需要每次在回到while 循环开头时需要将allset 重新赋予rset 。
参考:
《Linux C 编程一站式学习》
《TCP/IP详解 卷一》
《UNP》
使用select函数改进客户端/服务器端程序的更多相关文章
- UNIX网络编程——使用select函数编写客户端和服务器
首先看原先<UNIX网络编程--并发服务器(TCP)>的代码,服务器代码serv.c: #include<stdio.h> #include<sys/types.h> ...
- IO复用与select函数
socket select函数的详细讲解 select函数详细用法解析 http://blog.chinaunix.net/uid-21411227-id-1826874.html linu ...
- select模型(一 改进客户端)
一.改程序使用select来改进客户端对标准输入和套接字输入的处理,否则关闭服务器之后循环中的内容都要被gets阻塞.原程序中https://www.cnblogs.com/wsw-seu/p/841 ...
- UNIX网络编程——select函数的并发限制和 poll 函数应用举例
一.用select实现的并发服务器,能达到的并发数,受两方面限制 1.一个进程能打开的最大文件描述符限制.这可以通过调整内核参数.可以通过ulimit -n来调整或者使用setrlimit函数设置, ...
- 并发服务器--02(基于I/O复用——运用Select函数)
I/O模型 Unix/Linux下有5中可用的I/O模型: 阻塞式I/O 非阻塞式I/O I/O复用(select.poll.epoll和pselect) 信号驱动式I/O(SIGIO) 异步I/O( ...
- 线程模型、pthread 系列函数 和 简单多线程服务器端程序
一.线程有3种模型,分别是N:1用户线程模型,1:1核心线程模型和N:M混合线程模型,posix thread属于1:1模型. (一).N:1用户线程模型 “线程实现”建立在“进程控制”机制之上,由用 ...
- socket何时处于”读就绪状态“?---通过“应用程序大爷"和"内核孙子"对话再看重要的select函数的使用方法
前面. 我已经陆续介绍过select函数的一些零碎知识, 在本文中,我们来讨论这样一个问题:socket何时处于读就绪状态? 事实上主要讨论select函数, 毕竟socket的读就绪状态会导致sel ...
- posix 线程(一):线程模型、pthread 系列函数 和 简单多线程服务器端程序
posix 线程(一):线程模型.pthread 系列函数 和 简单多线程服务器端程序 一.线程有3种模型,分别是N:1用户线程模型,1:1核心线程模型和N:M混合线程模型,posix thread属 ...
- select 函数1
Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect.accept.recv或recvfrom这样的阻塞程序( ...
随机推荐
- IDEA是如何导入项目的,及启动导入项目遇到的问题:无法加载主类的一连串问题
1.启动报错误: 找不到或无法加载主类 org.spring.springboot.Application 可能在工程下面有多个module,然后,module里面的iml配置文件不止一个,删除留主的 ...
- IOS之UITabBarController
在学习IOS开发过程中,针对于UITabBarController的使用也不少出现,UITabBarController和UINavigationController类似,UITabBarContro ...
- js 获取读取cookie
// --- 设置cookie function setCookie(sName, sValue, expireHours) { var cookieString = sName + &quo ...
- CSDN问答频道“华章杯”11月排行榜活动开始,丰厚奖品等你拿
CSDN问答频道月度排行榜,是CSDN问答频道从3月开始举办的活动,旨在鼓励更多用户参与提问和解答,创造一个良好的互帮互助氛围,使参与者在问和答的过程中得到技术水平的提升,也希望大家能在技术交流中结交 ...
- (转)Overview : Writing Scripts in C# 使用C#书写脚本
Apart from syntax, there are some differences when writing scripts in C# or Boo. Most notable are: 除 ...
- 单页WEB应用(三),Chat聊天模块
Chat 聊天模块 这个模块应该就是该书全篇的唯一一个模块吧,后面差点儿全部的篇章都环绕这个模块去实现的,只是就通过这一个模块的实现和上线,也能体现单页应用开发到公布上线的整个过程,毕竟后面的数据.通 ...
- Grails开发环境的高速搭建
Grails开发环境的高速搭建 1 JAVA环境变量的设置和Grails设置环境变量 个人參考 JAVA_HOME =E:\kaifa\Java\jdk7_32 GRAILS_HOME =E:\kai ...
- Canvas简述
HTML Canvas API有两方面优势可以弥补:首先,不需要将所绘制图像中的每个图元当做对象存储,因此执行性能非常好:其次,在其他编程语言现有的优秀二维绘图API的基础上实现Canvas API相 ...
- maven 配置 Java Servlet API
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --><dependency> ...
- html页面禁止选择复制剪切
在body加入 onselectstart="return false" oncopy="return false;" oncut="return f ...