网络软件设计多路通信与select()阻塞与非阻塞机制制作主讲段景山段景山2多路通信问题的提出(一)标准服务器流程listen(s,5);……while(1){newsock=accept(s,…);recv(newsock,buf1,…);send(newsock,buf2,…);processbuf1;preparebuf2;closesocket(newsock);}while(recv(newsock,buf1,…)0){}虽然在客户机一端已有多个已建立连接的套接字,但服务器流程限制了每次只能对一个连接进行操作,需要一种机制能使服务器程序同时操作多条连接ASectionofCOServer段景山多路通信问题的提出(二)3利用标准流程建立的一个聊天程序框架connect(s,……);……while(1){recv(s,rcvBuf,…);send(s,sndBuf,…);printf(“%s”buf2)closesocket(newsock);}}受流程限制,必须要读到键盘数据,才能发送,才能接收对方的数据;必须收到对方的数据后,才能等待键盘输入数据。需要一种机制能使程序同时等待多个事件。ASectionofCOClientscanf(”%s”,sndBuf);段景山4多路通信设计什么叫多路通信/多路复用?多条连接上的数据同时通信什么叫“同时”?同一时刻并行同一时间并发:在一段时间内同时发生一段时间究竟有多长?通信领域:从通信的开始到通信的结束从建立连接到关闭连接段景山5多路通信设计怎样同时通信?“感觉”上的同时,因为实际上还是只有一只“笔”任务队列逐个处理各任务的事件,注意不是以任务为单位,而是以事件为单位从总体时间看,在一段时间内“同时”处理了多个任务的事件段景山6select()功能设计:该函数能检查多个套接字的状态,服务器程序就可以根据这些信息做出相应的处理输入:套接字队列将希望多路操作的一组套接字排成队列输出:各待查套接字当前的状态如:是否有数据到达,是否需要关闭等段景山7Select()程序框架多路复用的程序基本框架技术难点makethesockets_listwhile(1){select(sockets_list);逐个检查队列中的套接字状态;不同的状态作出相应的处理;}for(i=0;in;i++){getasocketfromlist;switch(socketstate){caseRECV:recv(socket,data);process(data);caseCLOSE:closesocket(socket);}}套接字队列和状态表示段景山8多路复用程序设计关键技术关键技术:套接字队列状态表示与处理段景山9关键技术:套接字队列1、套接字队列2、套接字队列的操作初始化、插入、删除、查找、遍历等typedefstructfd_set{u_intfd_count;SOCKETfd_array[FD_SETSIZE];}fd_set;fd_count:队列中有多少个套接字;fd_array:套接字队列段景山10关键技术:套接字队列套接字接口提供了一些专门的操作实现套接字队列的插入、删除和查找等功能初始化FD_ZERO(*set)--初始化套接字队列-set插入FD_SET(s,*set)--将套接字s插入到队列set中删除FD_CLR(s,*set)--从队列set中删除套接字s查找FD_ISSET(s,*set)--查找s是否在队列set中例段景山11关键技术:套接字队列除了“查询套接字”队列,我们还需要建立自己套接字队列--套接字管理队列为什么?1、查询套接字队列内容在每次select后会动态变化2、我们需要一个完整的队列以便每次能保证检索所有的“在线”套接字怎么做?makethesockets_listwhile(1){select(sockets_list);for(i=0;in;i++){getasocketfromlist;switch(socketstate){caseRECV:recv(socket,data);process(data);caseCLOSE:closesocket(socket);}}}段景山12关键技术:套接字队列自己的套接字管理队列怎么做?定义操作:init()、insert()、delete()make_fdlist(),fromourlisttosocketliststructsocket_list{SOCKETMainSock;intnum;SOCKETsock_array[64];};段景山13多路复用程序设计关键技术3、状态表示用逐个标识方式--标志:为每个套接字设置标志位,表明所处状态用分类处理方式--状态队列:具有某个状态的套接字进入到相应的状态队列中状态类别名字含义读状态readfds套接字在读状态队列中,表示该套接字收到远方数据写状态writefds表示套接字已准备好,用户可以向套接字发送数据意外状态exceptfds意外情况例段景山14多路复用程序设计关键技术socket的状态不止这三种,有些状态在适当的时候利用三种基本状态的一种表现出来。例如:读状态当套接字先前处于listen(),读状态意味者在这个套接字上有了新的连接写状态当套接字先前处于connect()--申请连接远端,写状态意味着与远方的连接已经建立意外状态当套接字先前处于connect()--申请连接远端,其它状态意味着与远方的连接已经失败例例例段景山15select()intselect(INintnfds,INOUTfd_set*readfds,INOUTfd_set*writefds,INOUTfd_set*exceptfdsINstructtimeval*timeout)intnfds:忽略readfds、writefds、exceptfds:三个状态队列功能:调用select时,三个队列中的套接字表示要求系统检查的套接字集合,调用select后,系统将这些集合中真正具有相应状态的套接字保留在队列里,不具有状态的套接字从队列中删除timeval*timeout:select操作完成的时限时限的作用,减少查询空闲套接字的频繁程度返回:满足条件的套接字总数量段景山16输入、输出多路复用引入了select机制的多路复用程序框架makethethreestatuslistwhile(1){select(0,readfds,writefds,exceptfds,NULL);}for(i=0;in;i++){makeasocket_list;所有多路复用的套接字集合getasocketsformsocket_list;(onebyone)if(FD_ISSET(s,readfds))dorecv()oraccept();if(FD_ISSET(s,writefds))dosend(data);if(FD_ISSET(s,exceptfds))closesocket(s);}思考:如果所有的通信都很少活动,怎样将轮询的次数降下来?段景山17事件驱动的机制基于select的程序可以认为是具有事件驱动特点的机制select可看作是事件发生器得到事件后进行分发处理select()switch()case1case2casen段景山18事件驱动的机制在select的事件中最重要的是:接收数据事件--读事件有读事件发生,则从套接字读出数据发送数据事件--写事件有写事件发生,则向套接字发送数据???段景山19case1case2事件驱动的机制“标准”的事件驱动流程recv(data)processdataselect()switch()读事件organizedatasend(data)写事件段景山仔细一想,问题出现了事件驱动模型上:发生可写事件,对应于发送数据业务模型上:“读”和“写”的驱动机制是不一样的。用户“读”是被动的是先有数据可读,然后才能够正确的读用户“写”是主动的表面上,是先可写,然后才能够写但是,写还有个前提:有数据可写产生可写的数据,是用户程序主动产生的“产生可写的数据”,不是由“可写”这个事件驱动的——想要说话,不是由电话“接通”触发的20段景山事件驱动的机制“写事件”不是想象中的“写事件”写事件的正确含义——当写事件发生时,表示套接字已经准备好,可以向外发送数据了不是先有写事件,才发送数据--即:不是看见电话可以用,我们就要打一通电话而是:先有发送数据的需要,再向系统询问是否可以发送数据,系统给出发送数据事件后,我们才发送数据。此外,没有数据需要发送时,也就不会去关心套接字是否可以发送21段景山22case1case2事件驱动的机制发送流程在观念上的转变:recv(data)processdatawanttosendsomethingorganizesendingDataFD_SET(s,&writefds)select()switch()读事件send(sendingData)FD_CLR(s,&writefds)写事件段景山23事件驱动的机制关于写事件的另外一个角度:写不动问题简单的处理方法:想发就发例:产生可写数据,往往是因对方数据的处理的结果,因此可以在处理完对方的数据后,直接发送。后续问题:当发生系统发送速度慢,造成发送不能成功时,仍然需要等待可写事件——即,不能阻塞等待,也不能不阻塞(不断尝试发送)而使系统更加繁忙。recv(data)processdatawantsendsometingsend(someting)段景山24上机1、编写具有select机制的服务器,可同时与多个客户机程序通信2、通过程序调试感受从阻塞机制到非阻塞机制的变化对任务的具体要求:客户机每从键盘收到一串字符就发送给服务器服务器,收到客户机的数据后打印到屏幕,并回送一个应答信息客户机收到用户敲入的“exit”后,退出。服务器使用select()同时接收多个用户的需求客户机收到服务器的应答后,等待用户键盘输入思考:1、如果用无连接(UDP)方式,如何实现以上任务是无连接服务程序复杂还是面向连接服务程序复杂2、在客户机程序中使用select,将实现什么功能段景山25多路复用程序演示套接字管理队列的定义,及相关操作设置套接字为非阻塞状态建立多路复用程序框架完成逐个检查套接字状态、并处理的过程,注意对主套接字的单独处理对比实验:非多路复用机制的程序网络软件设计阻塞与非阻塞机制段景山27阻塞与非阻塞阻塞当要求的系统服务得到满足时才返回的调用例:recv(s,buffer,……);当远端有数据送来时才“返回”非阻塞:无论系统服务是否能立即完成,系统调用都立即返回例:recv(s,buffer,……);并不等到对方数据送来才返回,无论套接口的so_rev队列是否有数据,非阻塞的recv都将立刻返回段景山28阻塞与非阻塞回顾socket接口的队列机制socketso_qso_rcvso_sndASectionofCOserverlisten(s,5);……while(1){newsock=accept(s,…);send(newsock,buf2,…);processbuf1;preparebuf2;closesocket(newsock);}while(recv(newsock,buf1,…)0){}当so_q中没有已建立连接的套接字时阻塞当接收队列中没有数据时阻塞阻塞会导致后续程序无法执行,但不阻塞,后续程序执行会错误段景山29阻塞与非阻塞主要的套接字函数中具有阻塞状态的有:socket()bind()listen()accept()recv()send()closesocket()recvfrom()sendto()connect()select()setsockopt()ioctl