《TCPIP网络编程基础教程》-第6章单线程并发机制的实现原理与方法_第1页
《TCPIP网络编程基础教程》-第6章单线程并发机制的实现原理与方法_第2页
《TCPIP网络编程基础教程》-第6章单线程并发机制的实现原理与方法_第3页
《TCPIP网络编程基础教程》-第6章单线程并发机制的实现原理与方法_第4页
《TCPIP网络编程基础教程》-第6章单线程并发机制的实现原理与方法_第5页
已阅读5页,还剩33页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

6.1单线程并发TCP服务器与客户端的进程结构6.1.1单线程并发TCP服务器的进程结构采用多线程编程的目的是最大限度地利用CPU资源,当某一线程的处理不需要占用CPU而只和I/O等资源打交道时,采用基于多线程的并发模式就可以让需要占用CPU资源的其他线程有机会获得CPU资源,从而提高了CPU资源的利用效率。每个程序执行时都会产生一个进程,而每一个进程至少要有一个主线程。该线程其实是进程执行的一条线索,除了主线程外,程序员还可以给进程增加其他的线程,即程序员可增加进程其他的执行线索,由此在某种程度上可以看成是给一个应用程序增加了多任务功能。当应用程序运行后就可以根据各种条件挂起或运行这些线程,尤其在多CPU环境中,这些线程是并发运行的。下一页返回6.1单线程并发TCP服务器与客户端的进程结构为此,在写服务器处理模型的程序时,除了上述多进程模型(服务器每收到一个客户请求,就创建一个新的进程来处理该请求)与多线程模型(服务器每收到一个客户请求,就创建一个新的线程来处理该请求)之外,人们针对单CPU环境提出了第三种模型,称为SELECT事件驱动模型。在该模型中,服务器每收到一个客户请求,就将其放入一个事件列表,然后让主线程通过非阻塞I/O方式来处理该客户请求。显然,在上述SELECT事件驱动模型中采用的是一种单线程的并发模型,其线程结构如图6.1所示。上一页下一页返回6.1单线程并发TCP服务器与客户端的进程结构显然,与其他模型相比,由于SELECT事件驱动模型只使用了单线程(进程)执行,因此其占用的资源少,不用消耗太多的CPU资源,同时能够为多客户端提供服务。如果试图建立一个简单的事件驱动的服务器程序,该模型具有一定的参考价值。但该模型也有严重的缺陷,例如,如图6.2所示,由于该模型将事件探测和事件响应夹杂在一起,因此,一旦事件响应的执行体过于庞大,则将对整个模型造成灾难性的后果。上一页下一页返回6.1单线程并发TCP服务器与客户端的进程结构6.1.2单线程并发TCP客户端的进程结构单线程并发TCP客户端与单线程并发TCP服务器一样,使用异步I/O。客户为到多个服务器的连接创建套接字描述符。同时,它还可以有一个或多个用于获得键盘或鼠标输入的描述符。客户程序的主体含有一个循环,该循环使用select等待其中任何一个描述符准备就绪。如果输入描述符准备就绪,客户就读取输入,并且可以将输入存储起来以后再用,也可以立刻开始处理输入。如果TCP连接输出就绪,客户就在此TCP连接上准备和发送请求。如果TCP连接输入就绪,客户就读取这个服务器发出的响应并加以处理。图6.3给出了在Linux系统中如何使用单线程并发TCP客户端方法来支持面向连接的应用协议。上一页返回6.2单线程并发TCP服务器软件的设计流程6.2.1UNIX/Linux环境下单线程并发TCP服务器软件设计流程在UNIX/Linux环境下,单线程并发TCP服务器模型的程序设计流程主要包括以下五个步骤。步骤1:创建主套接字并将其绑定到这个服务器的熟知端口上。将该主套接字添加到文件描述符表中,该表中的项是可以进行I/O的描述符。步骤2:调用select()函数在主套接字上等待I/O。步骤3:如果主套接字准备就绪,就调用accept()函数获得下一个客户的连接请求并产生一个新的临时套接字,然后再将该临时套接字也添加到文件描述符表中。下一页返回6.2单线程并发TCP服务器软件的设计流程步骤4:如果是主套接字以外的某个临时套接字准备就绪,就调用recv()函数从该临时套接字读取客户发送过来的消息,然后再构造响应,并调用send()函数将响应发回给该客户。步骤5:返回步骤2继续执行。6.2.2Windows环境下单线程并发TCP服务器软件设计流程在Windows环境下,单线程并发TCP服务器模型的程序设计流程主要包括以下五个步骤。步骤1:创建主套接字并将其绑定到这个服务器的熟知端口上。将该主套接字添加到文件描述符表中,该表中的项是可以进行I/O的描述符。上一页下一页返回6.2单线程并发TCP服务器软件的设计流程步骤2:调用select()函数或者WSAAsyncSelect()函数在主套接字上等待I/O。步骤3:如果主套接字准备就绪,就调用accept()函数获得下一个客户的连接请求并产生一个新的临时套接字,然后再将该临时套接字也添加到文件描述符表中。步骤4:如果是主套接字以外的某个临时套接字准备就绪,就调用recv()函数从该临时套接字读取客户发送过来的消息,然后再构造响应,并调用send()函数将响应发回给该客户。步骤5:返回步骤2继续执行。上一页返回6.3单线程并发TCP服务器实现例程6.3.1UNIX/Linux环境下单线程并发TCP服务器实现例程例程功能简介:首先服务器只创建单个线程,负责将套接字描述符加入文件描述符集中,然后循环测试文件描述符集合中的套接字,看是否有某个套接字已准备就绪:若有某个套接字已准备就绪,则基于该套接字来接收该客户端发送过来的数据并将应答回送给该客户端,且当数据接收完毕之后,关闭该套接字并将其从文件描述符中清除。下一页返回6.3单线程并发TCP服务器实现例程上述单线程并发TCP服务器例程的C语言源代码如下:#include<sys/types.h>#include<sys/socket.h>#include<sys/time.h>#include<pthread.h>#include<netinet/in.h>#include<errno.h>#include<unistd.h>#include<string.h>#include<stdio.h>#defineSERVER_PORT10000//定义端口号为10000上一页下一页返回6.3单线程并发TCP服务器实现例程#defineQUEUE20//定义等待队列长度为20#defineBUFSIZE4096externinterrno;intecho(intfd){charbuf[BUFSIZ];//声明缓存区数组bufintcc;cc=recv(fd,buf,BUFSIZE,0);//从套接字fd中接收数据if(cc<0)printf("echorecv:%s\n",strerror(errno));/*调用send()函数将buf中的数据写入套接字fd*/if(cc&&send(fd,buf,cc,0)<0)上一页下一页返回6.3单线程并发TCP服务器实现例程printf("echosend:%s\n",strerror(errno));returncc;}intmain(intargc,char*argv[]){structsockaddr_inservaddr,clientaddr;intmsock;//主套接字描述符fd_setrfds;//可读文件描述符集合fd_setafds;//活动文件描述符集合,用于保存所有的文件描述符unsignedintalen;intfd,nfds;220上一页下一页返回6.3单线程并发TCP服务器实现例程msock=socket(AF_INET,SOCK_STREAM,0);//创建主套接字if(msock<0){//调用socket()函数出错printf("CreateSocketFailed!\n");exit(-1);}memset(&servaddr,0,sizeof(structsockaddr_in));/*以下3条语句用于给端点地址结构体变量servaddr赋值*/servaddr.sin_family=AF_INET;//给协议族字段赋值servaddr.sin_addr.s_addr=htonl(INADDR_ANY);//给IP地址字段赋值servaddr.sin_port=htons(SERVER_PORT);//给端口号字段赋值/*以下语句用于调用bind()函数将主套接字与端点地址绑定*/上一页下一页返回6.3单线程并发TCP服务器实现例程ret=bind(msock,(structsockaddr*)&servaddr,sizeof(structsockaddr_in));if(ret<0){//调用bind()函数出错printf("ServerBindPort:%dFailed!\n",SERVER_PORT);exit(-1);}/*以下语句用于设置等待队列长度和设套接字为被动模式*/ret=listen(msock,QUEUE);if(ret<0){//调用listen()函数出错printf("ListenFailed!\n");exit(-1);}上一页下一页返回6.3单线程并发TCP服务器实现例程nfds=getdtablesize();//调用getdtablesize()来获取描述符的最大数FD_ZERO(&afds);//初始化活动文件描述符集合FD_SET(msock,&afds);//将msock套接字添加到活动文件描述符集while(1){memcpy(&rfds,&afds,sizeof(rfds));/*将afds中的套接字描述符添加到rfds中*//*以下语句用于确定rfds中哪个套接字已经就绪*/if(select(nfds,&rfds,(fd_set*)0,(fd_set*)0,(structtimeval*)0)<0)printf("select:%s\n",strerror(errno));/*以下语句用于测试主套接字msock的状态,如果主套接字已经就绪,则调用accept()函数建立与客户端的连接并创建从套接字ssock用于负责处理与该客户端的交互*/上一页下一页返回6.3单线程并发TCP服务器实现例程if(FD_ISSET(msock,&rfds)){//判断主套接字是否已经就绪intssock;alen=sizeof(client);ssock=accept(msock,(structsockaddr*)&client,&alen);if(ssock<0)printf("accept:%s\n",strerror(errno));FD_SET(ssock,&afds);//将ssock加入afds中}/*当主套接字没有就绪时,以下语句用于测试其他从套接字的状态,如果某个从套接字已经就绪,则调用echo()函数基于该从套接字读取客户端发送过来的数据*/for(fd=0;fd<nfds;++fd){//循环判断哪些从套接字已经就绪上一页下一页返回6.3单线程并发TCP服务器实现例程if(fd!=msock&&FD_ISSET(fd,&rfds)){if(echo(fd)==0){/*调用echo()函数接收数据,若为0则表示数据接收完毕*/221(void)close(fd);FD_CLR(fd,&afds);/*交互完毕后将fd从afds中清除*/}}}//for循环结束}//while循环结束close(msock);上一页下一页返回6.3单线程并发TCP服务器实现例程FD_CLR(msock,&afds);return0;}6.3.2Windows环境下单线程并发TCP服务器实现例程例程功能简介:首先服务器只创建单个线程,负责将套接字描述符加入文件描述符集中,然后循环测试文件描述符集合中的套接字,看是否有某个套接字已准备就绪:若有某个套接字已准备就绪,则基于该套接字来接收该客户端发送过来的数据并将应答回送给该客户端,且当数据接收完毕之后,关闭该套接字并将其从文件描述符中清除。上一页下一页返回6.3单线程并发TCP服务器实现例程上述单线程并发TCP服务器例程的C语言源代码如下:#include"stdafx.h"#include<stdio.h>#include<stdlib.h>#include<windows.h>#include<winsock2.h>#include<string.h>#include<malloc.h>#include<pthread.h>#pragmacomment(lib,"ws2_32.lib")intecho(intfd){上一页下一页返回6.3单线程并发TCP服务器实现例程charbuf[BUFSIZ];//声明缓存区数组bufintcc;cc=recv(fd,buf,BUFSIZE,0);//从套接字fd中接收数据if(cc<0)printf("echorecv:%s\n",strerror(errno));/*调用send()函数将buf中的数据写入套接字fd*/if(cc&&send(fd,buf,cc,0)<0)printf("echosend:%s\n",strerror(errno));returncc;}intmain(){上一页下一页返回6.3单线程并发TCP服务器实现例程SOCKETmsock;//声明主套接字描述符变量SOCKETssock;//声明服务器端临时套接字描述符变量structsockaddr_inserver,client;fd_setrfds;//可读文件描述符集合fd_setafds;//活动文件描述符集合,用于保存所有的文件描述符if(ssock<0)printf("accept:%s\n",strerror(errno));FD_SET(ssock,&afds);//将ssock加入afds中}/*当主套接字没有就绪时,以下语句用于测试其他从套接字的状态,如果某个从套接字已经就绪,则调用echo()函数基于该从套接字读取客户端发送过来的数据*/上一页下一页返回6.3单线程并发TCP服务器实现例程for(fd=0;fd<nfds;++fd){//循环判断哪些从套接字已经就绪if(fd!=msock&&FD_ISSET(fd,&rfds)){if(echo(fd)==0){/*调用echo()函数接收数据,若为0,则表示数据接收完毕*/(void)closesocket(fd);FD_CLR(fd,&afds);/*若交互完毕,将fd从afds中清除*/}}}//for循环结束}//while(1)循环结束closesocket(msock);上一页下一页返回6.3单线程并发TCP服务器实现例程FD_CLR(msock,&afds);WSACleanup();//结束WinsockSocketAPIreturn0;}6.3.3UNIX/Linux环境下单线程并发TCP客户端实现例程例程功能简介:首先客户端只创建单个线程,负责将标准输入和客户端套接字描述符加入文件描述符集中,然后循环测试文件描述符集合中的套接字,看是否有某个套接字已准备就绪:若标准输入准备就绪,则从键盘读取用户的输入数据并发送给服务器端;若客户端套接字准备就绪(有数据可读),则调用recv()函数接收服务器端回送的应答数据。上一页下一页返回6.3单线程并发TCP服务器实现例程上述单线程并发TCP客户端例程的C语言源代码如下:#include<stdio.h>#include<stdlib.h>#include<sys/socket.h>#include<string.h>#include<sys/types.h>#include<sys/time.h>#include<pthread.h>#defineSERVERIP"172.0.0.1"//定义IP地址常量#defineSERVERPORT10000//定义端口号为10000#defineQUEUE20//定义等待队列长度为20上一页下一页返回6.3单线程并发TCP服务器实现例程#defineBUFSIZE1024//定义缓冲区大小为1024Bintmain(){intresult;inttsock;intlen=sizeof(structsockaddr);structsockaddr_inservaddr;fd_setread_fds,test_fds;//声明两个文件描述符集合变量intfd;intmax_fds;charbuffer[BUFSIZE];tsock=socket(AF_INET,SOCK_STREAM,0);//创建套接字上一页下一页返回6.3单线程并发TCP服务器实现例程if(tsock<0){//调用socket()函数出错printf("CreateSocketFailed!\n");exit(-1);}intopt=SO_REUSEADDR;/*设置与套接字关联的选项,允许套接字重用本地地址和端口*/setsockopt(socketfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));memset(&servaddr,0,sizeof(structsockaddr_in));/*以下3条语句用于给端点地址结构体变量servaddr赋值*/servaddr.sin_family=AF_INET;//给协议族字段赋值上一页下一页返回6.3单线程并发TCP服务器实现例程inet_aton(SERVERIP,&servaddr.sin_addr);//给IP地址字段赋值servaddr.sin_port=htons(SERVERPORT);//给端口号字段赋值/*以下语句用于向远程服务器发起TCP连接建立请求*/intret;ret=connect(tsock,(structsockaddr*)&servaddr,sizeof(structsockaddr));if(ret<0){//调用connect()函数出错printf("ConnectFailed!\n");exit(-1);}FD_ZERO(&read_fds);上一页下一页返回6.3单线程并发TCP服务器实现例程FD_SET(0,&read_fds);//将标准输入的描述符0添加到文件描述符集FD_SET(socketfd,&read_fds);//将套接字描述符添加到文件描述符集max_fds=socketfd+1;printf("Chatnow!!\n");while(1){test_fds=read_fds;//memcpy(&test_fds,&read_fds,sizeof(test_fds));result=select(max_fds,&test_fds,(fd_set*)NULL,(fd_set*)NULL,(structtimeval*)NULL);上一页下一页返回6.3单线程并发TCP服务器实现例程if(result<1){printf("Selecterror.\n");exit(1);}if(FD_ISSET(0,&test_fds)){//判断标准输入是否已经就绪memset(buffer,'\0',sizeof(buffer));fgets(buffer,sizeof(buffer),stdin);//从键盘读取一行客户输入if((strncmp("quit",buffer,4))==0){printf("\nYouaregoingtoquit\n");break;}上一页下一页返回6.3单线程并发TCP服务器实现例程elseif(result==0){printf("Theothersidehasterminalthechat\n");pthread_mutex_unlock(&work_mutex);break;}else{printf("receivemessage:%s",buffer);}228}pthread_mutex_unlock(&work_mutex);//解锁上一页下一页返回6.3单线程并发TCP服务器实现例程sleep(1);/*如果没有这一行,当前线程会一直占据buffer,让当前线程暂停一秒可以实现1对N的功能*/}close(fd);pthread_exit(NULL);return0;}上一页下一页返回6.3单线程并发TCP服务器实现例程6.3.4Windows环境下单线程并发TCP客户端实现例程例程功能简介:首先客户端只创建单个线程,负责将标准输入和客户端套接字描述符加然后循环测试文件描述符集合中的套接字,看是否有某个套接字已准备就绪:若标准输入准备就绪,则从键盘读取用户的输入数据并发送给服务器端;若客户端套接字准备就绪(有数据可读),则调用recv()函数接收服务器端回送的应答数据。上述单线程并发TCP客户端例程的C语言源代码如下:#include"stdafx.h"#include<stdio.h>#include<stdlib.h>上一页下一页返回6.3单线程并发TCP服务器实现例程#include<pthread.h>#include<winsock2.h>#include<string.h>#include<malloc.h>#pragmacomment(lib,"ws2_32.lib")#define

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论