问题聚焦:

当客户端阻塞于从标准输入接收数据时,将读取不到别的途径发过来的必要信息,如TCP发过来的FIN标志。

因此,进程需要内核一旦发现进程指定的一个或多个IO条件就绪(即输入已准备好被读取,或者描述符已能承接更多的输出),它就通知进程。
这个机制称为I/O复用,这是由select, poll, epoll函数支持的。

编译环境:
    Ubuntu12.04  g++
需求描述:
  1. 单进程,IO复用,实现多个连接同时监听和收发信息
  2. 当服务器进程一终止,客户就能马上得到结果(select +shutdown实现)
  3. 当客户端使用"exit"命令或者Cirl+C结束进程时,服务器可以立即感应到,并关闭当前接口(select+close实现
来看一下实现后的运行效果:
 
步骤:
  1. 服务器连接了第一个客户,并收发消息“hello world”
  2. 服务器连接了第二个客户,并收发消息“hello select”
  3. 服务器从第一个客户收发消息“hello world again”
  4. 服务器从第二个客户收发消息“hello select again”
  5. 第二个客户关闭连接
  6. 第一个客户关闭连接

在了解select实现之前,先复习一下之前了解的IO模型
五种IO模型
  • 阻塞式IO
  • 非阻塞式IO
  • IO复用
  • 信号驱动式IO
  • 异步IO
对比:

一个输入操作通常包括两个不同的阶段:
  • 等待数据准备好
  • 从内核向进程复制数据
对于TCP来说,这两步分别为:
  • 等待数据从网络中到达,当所等待分组到达时,它被复制到内核中的某个缓冲区
  • 把数据从内核缓冲区复制到应用进程缓冲区

select
流程:
 
  1. select主要通过维护两个数组,来实现端口的轮询:
  2. client[]数组,记录有哪些连接已经建立
  3. rset[]数组,记录有注册哪些端口,需要监听
  4. 当rset数组中注册的端口被激活,这时将端口号放到client数组中,稍后遍历client[]数组,处理连接上的数据

代码实现:
服务器端:
 #include "mtserver.h"

 int main(int argc, char* argv[])
{
checkArgc(argc, ); const char* ip = argv[];
int port = atoi( argv[] ); /* declare socket*/
int listenfd, connfd, sockfd;
int ret; /* initialize listen socket*/
mySocket(listenfd); /* server address */
struct sockaddr_in servaddr;
initSockAddr(servaddr, ip, port); /* bind */
myBind(listenfd,
(struct sockaddr*)&servaddr,
sizeof(servaddr)); /* listen */
myListen(listenfd, ); /* handle SIGCHLD signal*/
//signal(SIGCHLD, handle_sigchild); /* waiting for connecting */
pid_t chipid;
socklen_t clilen;
struct sockaddr_in cliaddr; /* select initialize */
int maxfd, maxi, i;
bool toclose;
int nready, client[FD_SETSIZE];
fd_set rset, allset; maxfd = listenfd;
maxi = -;
for ( i=; i < FD_SETSIZE; i++ )
client[i] = -; FD_ZERO(&allset);
FD_SET(listenfd, &allset); printf("Waiting for connecting...\n"); for(;;) {
rset = allset;
if ( (nready=select(maxfd+, &rset, NULL, NULL, NULL)) < ) {
fprintf(stderr,
"select failed.%s\n",
strerror(errno));
continue;
} /* handle listen fd and no recv or respond */
if (FD_ISSET(listenfd, &rset)) {
clilen = sizeof(cliaddr);
connfd = myAccept(listenfd,
(struct sockaddr*)&cliaddr,
&clilen);
printf("Connection is established with sockfd: %d\n",
connfd);
for ( i = ; i < FD_SETSIZE; i++) {
if ( client[i] < ) {
client[i] = connfd;
break;
}
} if (i == FD_SETSIZE) {
fprintf(stderr,
"too many clients\n"
);
break;
} FD_SET( connfd, &allset );
if ( connfd > maxfd ) {
maxfd = connfd;
}
if ( i > maxi) {
maxi = i;
} if (--nready <= ) {
continue;
}
} /* handle accept fds(client[]) and handle recv or respond msg */
for ( i = ; i <= maxi; i++) {
if ( (sockfd = client[i]) < )
continue;
if ( FD_ISSET(sockfd, &rset) ) {
if( (toclose = handle_recv(sockfd))) {
printf("Client close this connection: %d\n" ,
sockfd);
close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -;
} if (--nready <= )
break;
}
}
}
} bool handle_recv(int connfd) { char recvbuf[BUFSIZE]; memset( recvbuf, '\0', BUFSIZE );
if ( recv(connfd, recvbuf,BUFSIZE,) != ) {
if (!strcmp(recvbuf, "exit"))
return true;
fprintf(stderr,"recv msg: \"%s\" from connfd:%d\n", recvbuf, connfd);
send(connfd, recvbuf, strlen(recvbuf), );
fprintf(stderr,"send back: \"%s\" to connfd:%d\n\n", recvbuf, connfd);
}
else
return true;
return false;
}
客户端:
#include "mtclient.h"

int main(int argc, char* argv[])
{
checkArgc(argc, ); int port = atoi(argv[]);
char* ip = argv[]; int sockfd;
struct sockaddr_in servaddr; mySocket(sockfd); initSockAddr(servaddr,ip, port); myConnect(sockfd,
(struct sockaddr*)&servaddr,
sizeof(servaddr)); handle_msg(sockfd);
exit(); } void handle_msg(int sockfd) { char sendbuf[BUFSIZE];
char recvbuf[BUFSIZE]; int maxfdpl, ret;
fd_set rset;
int normalTermi = ; FD_ZERO(&rset); while() {
memset( sendbuf, '\0', BUFSIZE );
memset( recvbuf, '\0', BUFSIZE ); if (normalTermi == )
FD_SET( , &rset ); FD_SET( sockfd, &rset );
maxfdpl = sockfd + ; if(DEBUG)
printf("Debug: waiting in select\n");
if ( select( maxfdpl, &rset, NULL, NULL, NULL) < ) {
fprintf(stderr,
"select failed.%s\n",
strerror(errno));
}
if(DEBUG)
printf("Debug: after select\n"); if (FD_ISSET( sockfd, &rset )) {
if (recv(sockfd, recvbuf, BUFSIZE, ) == ) { if(DEBUG)
printf("Debug: ready to quit, normalTermi: %d\n" ,
normalTermi); if (normalTermi == ) {
printf("handle_msg: normal terminated.\n");
return;
}
else {
printf("handle_msg: server terminated.\n");
exit();
}
}
fprintf(stderr,
"recv back: %s\n",
recvbuf);
}
else if ( FD_ISSET( , &rset ) ) {
gets(sendbuf);
if (strlen(sendbuf) > ) {
send(sockfd, sendbuf, strlen(sendbuf), );
if ( !strcmp(sendbuf, "exit") ) {
normalTermi = ;
shutdown(sockfd, SHUT_WR);
FD_CLR(, &rset);
continue;
}
}
}
}
close( sockfd );
return;
}

问题:
1 监听标准输入的描述符?
解决:标准输入描述符:0
2 当客户端发送所有消息,即可关闭连接,但是如果这时候调用close方法,会导致接收不到仍在传送过来的信息。
方案:需要一种关闭TCP连接其中一半的方法,也即是说,我们想给服务器发送一个FIN,告诉它我们已经完成了数据发送,但是仍然保持套接字描述符打开以便读取。
完成这个功能的函数为shutdown。
shutdown函数可以不管描述符的引用计数,就激发TCP的正常连接终止序列。
关闭一半的图示:
 
函数声明:
#include <sys/socket.h>
int shutdown(int sockfd, int howto);
howto:取值SHUT_RD(关闭这一端的读,不再读取连接上的数据) 
            SHUT_WR(关闭这一端的写,不再往连接上写数据) 
            SHUT_RDWR(关闭这一端的读和写)
3 套接字描述符的第一个可用描述符是多少?
答案:3。0 1 2分别为标准输入,标准输出,标准错误输出。
4 服务器进程终止后的动作?
这里需要知道的一点是,当服务器进程一终止,就会对客户进程发送一个FIN信号,这时套接字连接可读,read返回0

参考资料:
《Linux高性能服务器编程》
《UNIX网络编程 卷1:套接字联网API(第3版)》

服务器编程入门(11)TCP并发回射服务器实现 - 单线程select实现的更多相关文章

  1. socket编程之并发回射服务器3

    在socket编程之并发回射服务器一文中,服务器采用多进程的方式实现并发,本文采用多线程的方式实现并发. 多线程相关API: // Compile and link with -pthread int ...

  2. socket编程之并发回射服务器2

    承接上文:socket编程之并发回射服务器 为了让服务器进程的终止一经发生,客户端就能检测到,客户端需要能够同时处理两个描述符:套接字和用户输入. 可以使用select达到这一目的: void str ...

  3. socket编程之并发回射服务器

    使用到的函数: // 子进程返回0,父进程返回子进程ID,出错返回-1 pid_t fork(void); pid_t wait(int *wstatus); // 最常用的option是WNOHAN ...

  4. 第二十篇:不为客户连接创建子进程的并发回射服务器(poll实现)

    前言 在上文中,我使用select函数实现了不为客户连接创建子进程的并发回射服务器( 点此进入 ).但其中有个细节确实有点麻烦,那就是还得设置一个client数组用来标记select监听描述符集中被设 ...

  5. 不为客户连接创建子进程的并发回射服务器( poll实现 )

    前言 在上文中,我使用select函数实现了不为客户连接创建子进程的并发回射服务器( 点此进入 ).但其中有个细节确实有点麻烦,那就是还得设置一个client数组用来标记select监听描述符集中被设 ...

  6. 第十九篇:不为客户连接创建子进程的并发回射服务器(select实现)

    前言 在此前,我已经介绍了一种并发回射服务器实现.它通过调用fork函数为每个客户请求创建一个子进程.同时,我还为此服务器添加了自动消除僵尸子进程的机制.现在请想想,在客户量非常大的情况下,这种为每个 ...

  7. 不为客户连接创建子进程的并发回射服务器( select实现 )

    前言 在此前,我已经介绍了一种并发回射服务器实现( 点此进入 ).它通过调用fork函数为每个客户请求创建一个子进程.同时,我还为此服务器添加了自动消除僵尸子进程的机制.现在请想想,在客户量非常大的情 ...

  8. 并发回射服务器的最基本实现思路( fork )

    前言 一个服务器,通常会在一段时间内接收到多个请求.如果非要等到处理完一个请求再去处理下一个,势必会造成大部分用户的不满( 尤其当有某个请求需要占用大量时间时 ).如何解决这个问题?让处理这些用户请求 ...

  9. 基本 TCP 的回射服务器

    实验一 代码:链接[01项目] 1. 先启动服务器,如图: 2. 然后启动客户端,如图: 3. 输出结果: [注意]:在服务器终止时,给父进程发送了一个SIGCHILD信号,这一点本例发生了,但是我们 ...

随机推荐

  1. PHP学习之-1.5 字符串

    字符串 一个字符串是用双引号扩起来的一个词或者一个句子,比如 "Hello Word" ,你可以使用PHP语言输入这个字符串,像这样 <?php echo "Hel ...

  2. 零基础数据分析与挖掘R语言实战课程(R语言)

    随着大数据在各行业的落地生根和蓬勃发展,能从数据中挖金子的数据分析人员越来越宝贝,于是很多的程序员都想转行到数据分析, 挖掘技术哪家强?当然是R语言了,R语言的火热程度,从TIOBE上编程语言排名情况 ...

  3. Swift - 使用TableView的静态单元格进行页面布局

    通过使用静态单元格的列表,我们可以很方便的进行页面布局.下面通过一个“添加任务页面”来进行演示. 效果图如下: 实现步骤: 1,在storyboard中拖入一个TableViewController, ...

  4. 简易的sniffer程序

    真的非常简易,这个程序不过抓一些发送到本机的数据包,然后显示出来它们的一些信息罢了.      程序很easy!       #include <WinSock2.h> #include ...

  5. Effective C++_笔记_条款04_确定对象被使用之前已先被初始化

    (整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 读取未初始化的值会导致不确定的行为.在某些平台上,仅仅只是读取为 ...

  6. Irvine的专业汇编网站

    http://asmirvine.com/ http://download.csdn.net/download/stupid_boy2007/3890853 http://download.csdn. ...

  7. poj2418 map 快排水过

    /*计算每个单词的百分比按字典序输出*/ #include <cstdio>#include<cstdlib>#include <cstring>#include ...

  8. ajax后台处理返回json值

    public ActionForward xsearch(ActionMapping mapping, ActionForm form, HttpServletRequest request, Htt ...

  9. 积累的VC编程小技巧之按钮

    1.资源种创建的控件,对其属性的动态控制: 在对话框类的头文件里创建所要改变属性的控件的对象,如要改变一个Button(其ID为IDC_MyButton)的属性,则需创建Cbutton的对象m_but ...

  10. 汇编与高级语言(插图结合Delphi代码,来自linzhengqun)

    汇编与高级语言 1.      汇编基础知识 1.1.      寄存器 寄存器 用途 EAX,EBX,EDX,ECX 通用寄存器,由程序员自己指定用途,也有一些不成文的用法: EAX:常用于运算. ...