网络编程实用教程第7 8章-多线程编程课件_第1页
网络编程实用教程第7 8章-多线程编程课件_第2页
网络编程实用教程第7 8章-多线程编程课件_第3页
网络编程实用教程第7 8章-多线程编程课件_第4页
网络编程实用教程第7 8章-多线程编程课件_第5页
已阅读5页,还剩119页未读 继续免费阅读

下载本文档

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

文档简介

网络编程实用教程第7章WinSock的多线程编程2021/8/171网络编程实用教程第7章WinSock的多线程编程2021本章内容:WinSock需要多线程编程的原因:Win32操作系统下的多进程多线程机制、多线程机制在网络编程中的应用和VisualC++6.0对多线程网络编程的支持。分析了MFC支持的两种线程,给出了创建MFC的工作线程、创建并启动用户界面线程和终止线程的步骤。2021/8/172本章内容:2021/8/1727.1WinSock为什么需要多线程编程2021/8/1737.1WinSock为什么需要多线程编程2021/8/17.1.1WinSock的两种输入输出模式如前所述,WinSock在进行输入输出的时候,可以使用两种工作模式:“阻塞”模式:又称为同步模式“非阻塞”模式:又称为异步模式

工作在“阻塞”模式下的套接字被称为阻塞套接字,而工作在“非阻塞”模式下的套接字称为非阻塞套接字。2021/8/1747.1.1WinSock的两种输入输出模式如前所7.1.2两种模式的优缺点及解决方法“阻塞”与“非阻塞”模式的优点和缺点:阻塞套接字的I/O操作工作情况比较确定,即调用、等待、返回。大部分情况下,I/O操作都能成功地完成,只是花费了等待的时间,容易编程;需要建立多个套接字连接来为多个客户服务的时候,或在数据的收发量不均匀的时候,或在输入输出的时间不确定的时候,阻塞套接字的性能低下,甚至无能为力。2021/8/1757.1.2两种模式的优缺点及解决方法“阻塞”与“非阻塞”使用非阻塞套接字,需要编写更多的代码,因为必须恰当地把握调用I/O函数的时机,尽量减少无功而返的调用,还必须详加分析每个Winsock调用中收到的错误,采取相应的对策,这种I/O操作的随机性使得非阻塞套接字显得难于操作。所以必须采取适当的对策,让阻塞和非阻塞套接字能够满足各种场合的要求。对于非阻塞工作模式:引入了五种“套接字I/O模型”。阻塞的工作模式,则引入了多线程机制。2021/8/176使用非阻塞套接字,需要编写更多的代码,因为必须恰当地把握调用7.2Win32操作系统下的多进程多线程机制2021/8/1777.2Win32操作系统下的多进程多线程机制2021/87.2.2Win32OS支持多线程应用程序、进程及线程的关系DOS是单用户单任务的。

Win32操作系统是多任务的,并且支持一个进程中有多个线程。一个线程(thread)是进程内的一条执行路径,是一个应用程序中的一条可执行路径。一个进程中至少要有一个线程,称为主线程。当启动了一个应用程序时,操作系统将为它创建了一个进程,同时创建该进程的主线程,并开始执行主线程。主线程可以创建并启动其他辅助线程,由主线程创建的线程又可以创建并启动更多的线程。2021/8/1787.2.2Win32OS支持多线程应用程序、进程及线程7.2.2Win32OS支持多线程单CPU分时地运行进程中的各个线程2021/8/1797.2.2Win32OS支持多线程单CPU分时地运行进7.2.3多线程机制在网络编程中的应用如果一个应用程序,有多个任务需要同时进行处理,则适合使用多线程机制。对于网络上客户机软件:采用多线程,能克服在单线程的编程模式下,由于阻塞等待而产生的客户程序就不能及时响应用户的操作命令的问题。对于网络上服务器软件:采用多线程的编程技术,能更好地为多个客户服务。对于一个客户:采用多线程机制也能大大提高应用程序的运行效率。网络在线实时监控软件:2021/8/17107.2.3多线程机制在网络编程中的应用如果一个应7.3VC6.0对多线程网络编程的支持VC6.0环境下,两种开发程序的方法:直接使用Win32API来编写Win32应用程序利用MFC基础类库编写C++风格的应用程序。在这两种Windows应用程序的开发方式下,多线程的编程原理是一致的。2021/8/17117.3VC6.0对多线程网络编程的支持VC6.0环境下7.3.1MFC支持的两种线程微软的基础类库MFC提供了对于多线程应用程序的支持。在MFC中,线程分为两种:用户接口线程:(user-interfacethread),或称用户界面线程;工作线程:(theworkerthread),这两类线程可以满足不同任务的处理需求。2021/8/17127.3.1MFC支持的两种线程微软的基础类库MF1、用户接口线程

作用:用于处理用户的输入,响应用户产生的消息。MFC为用户接口线程提供了一个消息泵。同时包含一个消息循环,以应对各种事件。

MFC应用程序的CWinApp类对象是一个典型的用户接口线程在MFC应用程序中,CWinThread是用户接口线程的基类,CWinApp就是从CWinThread类派生出来的,编写用户接口线程候,也需要从CWinThread类派生。2021/8/17131、用户接口线程2021/8/17132.工作线程

工作线程(workerthread),适用于处理那些不要求用户输入并且比较消耗时间的其他任务。对用户来说,工作线程运行在后台。这就使得工作线程特别适合去等待一个事件的发生。CWinThread类同样是工作线程的基类。在编写工作线程的时候,可以调用MFC的AfxBeginThread函数,来创建CWinThread对象。2021/8/17142.工作线程2021/8/17147.3.2创建MFC的工作线程利用MFC创建工作线程的步骤:第一步:编程实现控制函数第二步:创建并启动工作线程一般不必从CWinThread派生一个类。如果需要一个特定版本的CWinThread类,也可以去派生;但对于大多数的工作线程是不要求的。可以不作任何修改地使用CWinThread类。2021/8/17157.3.2创建MFC的工作线程利用MFC创建工作线程的步7.3.2创建MFC的工作线程1、编程实现控制函数一个工作线程对应一个控制函数。线程执行的任务都应编写在控制函数之中,规定了该线程的执行代码,当控制函数执行结束而退出时,线程也就随之终止。编写工作线程的控制函数必须遵守一定的格式,控制函数的原型声明是:UINTControlFunctionName(LPVOIDpParam);

pParam是一个数据结构;2021/8/17167.3.2创建MFC的工作线程1、编程实现控制函数2027.3.2创建MFC的工作线程2.创建并启动工作线程(Startingthethread)

启动线程:即开始运行它对应的控制函数。在主线程或其他线程中调用AfxBeginThread()函数就可以创建新的线程,并使新线程开始运行。一般将线程的创建者称为新线程的父线程。AfxBeginThread()函数有两个重载的版本,区别在于使用的入口参数不同。2021/8/17177.3.2创建MFC的工作线程2.创建并启动工作线程(S7.3.2创建MFC的工作线程CWinThread*AfxBeginThread(AFX_THREADPROCpfnThreadProc,//控制函数的地址LPVOIDpParam,//数据结构的指针,传数据给线程控制函数intpPriority=THREAD_PRIORITY_NORMAL,//优先级UINTnStackSize=0,//线程的堆栈大小(缓冲区)DWORDdwCreateFlags=0,//线程的运行状态,是否被挂起LPSECURITY_ATTTRIBUTESlpSecurityAttrs=NULL//安全属性);

后面4个参数为可选参数。2021/8/17187.3.2创建MFC的工作线程CWinThread*创建工作线程的例子3、创建工作线程的例子功能:求长度为N的数组Arry的各元素的和。编程实现线程控制函数(1)//首先定义了一个结构:struct{

intN;//数组元素的个数。

double*Arry;//指向一个双精度实数的数组

}myData;

//定义了此结构类型的变量,省略了初始化的代码myDatass;

2021/8/1719创建工作线程的例子3、创建工作线程的例子2021/8/171创建工作线程的例子(2)//接着定义线程的控制函数。UINTMyCalcFunc(LPVOIDpParam){ //如果入口参数为空指针,终止线程。 if(pPara==NULL)AfxEndThread(MY_NULL_POINTER_ERROR); intN=pPara->N;//数组的元素个数。 double*Arry=pPara->Arry;//指向数组的第一个元素。 doublesum=0;//数组元素之和。 for(inti=0;i<N;i++)sum+=Arry[i];//求和。 CStringbb; bb.Format(“数组的和是:%d”,sum);//格式化显示字符串。 AfxMessageBox(bb);//显示结果。 return0; }改为:pPara

2021/8/1720创建工作线程的例子(2)//接着定义线程的控制函数。改为:创建工作线程的例子调用AfxBeginThread()来创建并启动这个线程。将控制函数名和结构变量的地址作为参数传递,其他的参数省略,使用默认值。AfxBeginThread(MyCalcFunc,&ss);一旦调用了此函数,线程就被创建,并开始执行线程函数。当数据的计算完成时,函数将停止运行,相应的线程也随即终止。线程拥有的堆栈和其他资源都将释放。CWinThread对象将被删除。2021/8/1721创建工作线程的例子调用AfxBeginThread()来创建创建工作线程的例子4.创建工作线程的一般模式可以得出创建工作线程的一般模式:定义一个结构myData用于传递数据;工作线程控制函数的框架;UINTMyThreadProc(LPVOIDpPara){if(pPara==NULL){AfxEndThread(MY_NULL_POINTER_ERROR);

return1;

}2021/8/1722创建工作线程的例子4.创建工作线程的一般模式2021/8/1创建工作线程的例子 ……//利用入口参数作某些工作。 return0;//线程成功地完成并返回。 }在程序的另一个函数中插入以下代码。 ..............

myDatass;

AfxBeginThread(MyThreadProc,ss);2021/8/1723创建工作线程的例子 ……//利用入口参数作某些工作。2027.3.3创建并启动用户界面线程用户界面线程允许用户使用更多的用户界面对象,如对话框或其他窗口。因此,要使用用户界面线程,需要做更多的工作:创建并启动用户界面线程要经过三个步骤:从CWinThread类派生出自己的线程类;改造这个线程类,使它能够完成用户希望的工作创建并启动用户界面线程。2021/8/17247.3.3创建并启动用户界面线程用户界面线程允许7.3.3创建并启动用户界面线程1、从CWinThread类派生出自己的线程类要创建一个用户界面线程,首先从CWinThread类派生出自己的线程类,可借助ClassWizard来完成。

2、改造自己的线程类对这个派生的线程类作以下改造工作:在类的.h头文件中,用DECLARE_DYNCREATE宏来声明这个类(使其支持动态创建);DECLARE_DYNCREATE(class_name)2021/8/17257.3.3创建并启动用户界面线程1、从CWinThrea7.3.3创建并启动用户界面线程在类的.CPP文件中,用IMPLEMENT_DYNCREATE宏来实现这个类。调用格式是:IMPLEMENT_DYNCREATE(class_name,base_class_name)两个参数分别是参数是线程类名和它的基类名。2021/8/17267.3.3创建并启动用户界面线程在类的.CPP文件中,用7.3.3创建并启动用户界面线程对派生类的改造:这个线程类须重载它的基类的某些成员函数,如该类的InitInstance()成员函数;对于其他成员函数,可以有选择地重载。创建新的用户界面窗口类,并建立线程类与用户界面窗口类的联系。利用类向导为新建的线程类添加控件成员变量,添加响应消息的成员函数,编写实现代码。2021/8/17277.3.3创建并启动用户界面线程对派生类的改造:20217.3.3创建并启动用户界面线程成员函数说明ExitInstance线程终止时执行InitInstance线程实例化时执行OnIdle线程空闲时,一般不重载PreTranslateMessage消息派发前可以重新解释消息Run线程控制函数,消息循环,不重载WinThread的成员函数包括:2021/8/17287.3.3创建并启动用户界面线程成员函数说明ExitIn7.3.3创建并启动用户界面线程3.创建并启动用户界面线程可以使用MFC提供的AfxBeginThread()函数的另一个创建并启动用户界面线程,调用格式是:CWinThread*AfxBeginThread(CRuntimeClass*pThreadClass,//继承的线程类指针intpPriority=THREAD_PRIORITY_NORMAL,//优先级UINTnStackSize=0,//创建线程的堆栈大小DWORDdwCreateFlags=0,//线程的状态LPSECURITY_ATTTRIBUTESlpSecurityAttrs=NULL);2021/8/17297.3.3创建并启动用户界面线程3.创建并启动用户界面线7.3.3创建并启动用户界面线程4.AfxBeginThread()函数所作的工作它创建一个新的用户自己的线程类的对象,该对象继承了CWinThread类的属性。MFC自动调用新线程类的InitInstance()函数,来初始化这个新的线程类对象实例。调用CWinThread::CreateThread函数来开始执行这个线程,并运行RUN函数,进入消息循环。函数返回一个指向新生成的CWinThread对象的指针,可以把它保存在一个变量中,其它线程就可以利用这个指针来访问该线程类的成员变量或成员函数。2021/8/17307.3.3创建并启动用户界面线程4.AfxBeginTh7.3.3创建并启动用户界面线程线程的消息循环:系统自动地为每一个线程创建一个消息队列,如果线程创建了一个或多个窗口,就必须提供一个消息循环,这个消息循环从线程的消息队列中获取消息,并把它们发送到相应的windows过程。

因为系统将消息导向独立的应用程序窗口,所以在开始线程的消息循环之前,线程必须至少创建一个窗口,大多数基于Win32的应用程序包含一个单一的线程,该线程创建了若干窗口。2021/8/17317.3.3创建并启动用户界面线程线程的消息循环:20217.3.4终止线程1、正常终止线程工作线程:线程控制函数代码执行完毕用户线程:发送消息到窗口2、提前终止线程利用AfxEndThread(UNITnExitCode)函数参数为线程的终止代码,该函数应该在线程内部调用;3、终止线程的另一种方法TerminateThread()函数强行终止。2021/8/17327.3.4终止线程1、正常终止线程2021/8/17324、获取线程的终止代码BoolGetExitCodeThread(HANDLEhThread,//线程句柄LPDWORDlpExitCode//接收线程的终止代码)5、设置线程的优先级SetThreadPriority(HANDLEhThread,//线程句柄intnPriority//优先级水平)2021/8/17334、获取线程的终止代码2021/8/1733多线程编程实例2021/8/1734多线程编程实例2021/8/1734第8章Winsock的输入/输出模型2021/8/1735第8章Winsock的输入/输出模型2021/8/173

第7章提到,WinSock在进行输入输出的时候,可以采用阻塞模式或非阻塞模式。使用非阻塞套接字,带有I/O操作的随机性,使非阻塞套接字难于操作,给编程带来困难。为了解决这个问题,对于非阻塞的套接字工作模式,进一步引入了五种“套接字I/O模型”,它们有助于应用程序通过一种异步方式,同时对一个或多个套接字上进行的通信加以管理。

2021/8/1736第7章提到,WinSock在进行输入输出的时候,可这些模型包括:select(选择)、WSAAsyncSelect(异步选择)、WSAEventSelect(事件选择)、OverlappedI/O(重叠式I/O)Completionport(完成端口)。本章将主要介绍前三种。2021/8/1737这些模型包括:2021/8/1737不同的Windows平台支持不同的I/O模型。如表8.1:2021/8/1738不同的Windows平台支持不同的I/O模型。如表8.1:28.1select模型select(选择)模型是Winsock中最常见的I/O模型。它的中心思想是利用select函数,实现对多个套接字I/O的管理。利用select函数,可以判断套接字上是否存在数据,或者能否向一个套接字写入数据。只有在条件满足时,才对套接字进行输入输出操作,从而避免无功而返的I/O函数调用,避免频繁产生WSAEWOULDBLOCK错误,使输入输出变得有序。2021/8/17398.1select模型select(选择)模型是1.select的函数select的函数原型如下,其中fd_set数据类型,代表着一系列特定套接字的集合。intselect( intnfds, fd_setFAR*readfds, fd_setFAR*writefds, fd_setFAR*exceptfds, conststructtimevalFAR*timeout

);2021/8/17401.select的函数2021/8/1740说明:

select函数对readfds、writefds和exceptfds三个集合中指定的套接字进行检查,看是否有数据可读、可写或有带外数据,如果有至少一个套接字符合条件,就立即返回。符合条件的套接字仍在集合中,不符合条件的套接字则被删去。如果一个也没有,则等待。但最多等待timeout所指定的时间,便返回。2021/8/1741说明:2021/8/17412.操作套接字集合的宏在程序中,用select对套接字进行监视之前,必须先将要检查的套接字句柄分配给某个集合,设置好相应的fd_set结构,再来调用select函数,便可知道一个套接字上是否正在发生上述的I/O活动。Winsock提供了下列宏操作,专门对fd_set数据类型进行操作FD_CLR(s,*set):从set中删除套接字s。2021/8/17422.操作套接字集合的宏2021/8/1742FD_ISSET(s,*set):检查s是否set集合的一名成员;如答案是肯定的是,则返回TRUE。FD_SET(s,*set):将套接字s加入集合set。FD_ZERO(*set):将set初始化成空集合。其中,参数s是一个要检查的套接字,参数set是一个fd_set集合类型的指针。例如:调用select函数前,可使用FD_SET宏将指定的套接字加入到fd_read集合中,select函数完成后,可使用FD_ISSET宏,来检查该套接字是否仍在fd_read集合中。2021/8/1743FD_ISSET(s,*set):检查s是否set集合的一3.select模型的操作步骤使用FD_ZERO宏初始化感兴趣的每一个fd_set集合。使用FD_SET宏将要检查的套接字句柄添加到感兴趣的每个fd_set集合中,相当在指定的fd_set集合中,设置好要检查的I/O活动。调用select函数,然后等待。select完成并返回后,会修改每个fd_set结构,删除那些不存在待决I/O操作的套接字句柄,在各个fd_set集合中返回符合条件的套接字。2021/8/17443.select模型的操作步骤2021/8/1744根据select的返回值,使用FD_ISSET宏,对每个fd_set集合进行检查,判断一个特定的套接字是否仍在集合中,便可判断出哪些套接字存在着尚未完成(待决)的I/O操作。知道了每个集合中“待决”的I/O操作之后,对相应的套接字的I/O进行处理,然后返回步骤1,继续进行select处理。2021/8/1745根据select的返回值,使用FD_ISSET宏,对每个f4.举例下面的例子用select管理一个套接字上的I/O操作:SOCKETs; //定义一个套接字fd_setfdread; //定义一个套接字集合变量intret; //返回值//创建一个套接字,并接受连接.......2021/8/17464.举例2021/8/1746//管理该套接字上的输入/输出while(TRUE){

FD_ZERO(&fdread);//调用select()之前要清除套接字集合

FD_SET(s,&fdread);//将套接字s添加到fdread集合中 //调用select()函数,并等待它的完成, //这里只是想检查s是否有数据可读

if((ret=select(0,&fdread,NULL,NULL,NULL))== SOCKET_ERROR) {

……//处理错误的代码

}

2021/8/1747//管理该套接字上的输入/输出2021/8/1747//返回值大于零,说明有符合条件的套接字//对于本例这个简单的情况,select()的返回值应是1。//如果程序处理更多的套接字,返回值可能大于1,//应用程序应检查特定的套接字是否在返回的集合中if(ret>0){if(FD_ISSET(s,&fdread)){

……//对该套接字进行读操作}}}2021/8/1748//返回值大于零,说明有符合条件的套接字2021/8/1748.2WSAAsyncSelect异步I/O模型异步I/O模型通过调用WSAAsyncSelect函数实现。利用这个模型,应用程序可在一个套接字上,接收以Windows消息为基础的网络事件通知。该模型最早出现于Winsock的1.1中,以适应其多任务消息环境。2021/8/17498.2WSAAsyncSelect异步I/O模型1.WSAAsyncSelect函数函数的定义是: intWSAAsyncSelect( SOCKETs, HWNDhWnd, unsignedintwMsg, longlEvent );2021/8/17501.WSAAsyncSelect函数2021/8/17502.窗口回调例程应用程序在一个套接字上调用WSAAsyncSelect函数时,该函数的hWnd参数指定了一个窗口句柄。函数成功调用后,当指定的网络事件发生时,会自动执行该窗口对应的窗口回调例程。并将网络事件通知和Windows消息的相关信息,传递给该例程的入口参数,用户可以在该例程中添加自己的代码,针对不同的网络事件进行处理,从而实现有序的套接字输入和输出。2021/8/17512.窗口回调例程2021/8/1751窗口回调例程应定义成如下形式:LRESULTCALLBACKWindowProc(HWNDhWnd,UINTuMsg,WPARAMwParam,LPARAMlParam);

2021/8/1752窗口回调例程应定义成如下形式:2021/8/17523.举例2021/8/17533.举例2021/8/17538.3WSAEventSelect事件选择模型WSAEventSelect事件选择模型和WSAAsyncSelect模型类似,它也允许程序在一个或多个套接字上,接收以事件为基础的网络事件通知。表8.2总结的由WSAAsyncSelect模型采用的网络事件,均可原封不动地移植到事件选择模型中。也就是说,在用新模型开发的应用程序中,也能接收和处理所有那些事件。该模型最主要的差别在于,网络事件会投递至一个事件对象句柄,而非投递至一个窗口例程。以下按照使用此模型的编程步骤介绍。

2021/8/17548.3WSAEventSelect事件选择模型W1.创建事件对象句柄事件选择模型要求应用程序针对每一个套接字,首先创建一个事件对象。方法是调用WSACreateEvent函数,它的定义如下:WSAEVENTWSACreateEvent(void);

返回值:一个创建好的事件对象句柄。

2021/8/17551.创建事件对象句柄2021/8/17552.关联套接字和事件对象,注册关心的网络事件有了事件对象句柄后,接下来将其与某个套接字关联在一起,同时注册感兴趣的网络事件类型(表8-2),这就需要调用WSAEventSelect函数:intWSAEventSelect( SOCKETs, WSAEVENThEventObject, longlNetworkEvents );2021/8/17562.关联套接字和事件对象,注册关心的网络事件2021/8/13.等待网络事件触发事件对象句柄的工作状态套接字同一个事件对象句柄关联在一起以后,程序便调用WSAWaitForMultipleEvents函数,等待网络事件触发事件对象句柄的工作状态:DWORDWSAWaitForMultipleEvents( DWORDcEvents, constWSAEVENTFAR*lphEvents, BOOLfWaitAll, DWORDdwTimeout, BOOLfAlertable );该函数用来等待一个或多个事件对象句柄,当其中一个或所有句柄进入“已传信”状态后,或在超过了一个规定的时间期限后,立即返回。2021/8/17573.等待网络事件触发事件对象句柄的工作状态2021/8/14.检查套接字上所发生的网络事件类型知道了造成网络事件的套接字后,接下来可调用WSAEnumNetworkEvents函数,检查套接字上发生了什么类型的网络事件。该函数定义如下:intWSAEnumNetworkEvents( SOCKETs, WSAEVENThEventObject, LPWSANETWORKEVENTSlpNetworkEvents);2021/8/17584.检查套接字上所发生的网络事件类型2021/8/17585.处理网络事件在确定了套接字上发生的网络事件类型后,可以根据不同的情况做出相应的处理。完成了对WSANETWORKEVENTS结构中的事件的处理之后,应用程序应在所有可用的套接字上,继续等待更多的网络事件。完成了对一个事件对象的处理后,应调用WSACloseEvent函数,释放由事件句柄使用的系统资源。函数的定义如下:BOOLWSACloseEvent(WSAEVENThEvent);该函数也将一个事件句柄作为自己唯一的参数,并会在成功后返回TRUE,失败后返回FALSE。2021/8/17595.处理网络事件2021/8/17596.举例

2021/8/17606.举例2021/8/17608.4其他模型重叠I/O模型在Winsock中,能使应用程序达到更佳的性能。重叠模型的基本原理是让应用程序使用一个重叠的数据结构,一次投递一个或多个Winsock的I/O请求。针对那些提交的请求,在它们完成之后,应用程序可为它们提供服务。自Winsock2.0发布开始,重叠I/O便已集成到新的Winsock函数中。因此,重叠I/O模型适用于安装了Winsock2.0的所有Windows平台。2021/8/17618.4其他模型重叠I/O模型2021/8/1761“完成端口”模型

是迄今为止最为复杂的一种I/O模型。然而,假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能!该模型只适用于WindowsNT和Windows2000操作系统。因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的CPU数量的增多,应用程序的性能也可以线性提升时,才应考虑采用“完成端口”模型。2021/8/1762“完成端口”模型2021/8/1762网络编程实用教程第7章WinSock的多线程编程2021/8/1763网络编程实用教程第7章WinSock的多线程编程2021本章内容:WinSock需要多线程编程的原因:Win32操作系统下的多进程多线程机制、多线程机制在网络编程中的应用和VisualC++6.0对多线程网络编程的支持。分析了MFC支持的两种线程,给出了创建MFC的工作线程、创建并启动用户界面线程和终止线程的步骤。2021/8/1764本章内容:2021/8/1727.1WinSock为什么需要多线程编程2021/8/17657.1WinSock为什么需要多线程编程2021/8/17.1.1WinSock的两种输入输出模式如前所述,WinSock在进行输入输出的时候,可以使用两种工作模式:“阻塞”模式:又称为同步模式“非阻塞”模式:又称为异步模式

工作在“阻塞”模式下的套接字被称为阻塞套接字,而工作在“非阻塞”模式下的套接字称为非阻塞套接字。2021/8/17667.1.1WinSock的两种输入输出模式如前所7.1.2两种模式的优缺点及解决方法“阻塞”与“非阻塞”模式的优点和缺点:阻塞套接字的I/O操作工作情况比较确定,即调用、等待、返回。大部分情况下,I/O操作都能成功地完成,只是花费了等待的时间,容易编程;需要建立多个套接字连接来为多个客户服务的时候,或在数据的收发量不均匀的时候,或在输入输出的时间不确定的时候,阻塞套接字的性能低下,甚至无能为力。2021/8/17677.1.2两种模式的优缺点及解决方法“阻塞”与“非阻塞”使用非阻塞套接字,需要编写更多的代码,因为必须恰当地把握调用I/O函数的时机,尽量减少无功而返的调用,还必须详加分析每个Winsock调用中收到的错误,采取相应的对策,这种I/O操作的随机性使得非阻塞套接字显得难于操作。所以必须采取适当的对策,让阻塞和非阻塞套接字能够满足各种场合的要求。对于非阻塞工作模式:引入了五种“套接字I/O模型”。阻塞的工作模式,则引入了多线程机制。2021/8/1768使用非阻塞套接字,需要编写更多的代码,因为必须恰当地把握调用7.2Win32操作系统下的多进程多线程机制2021/8/17697.2Win32操作系统下的多进程多线程机制2021/87.2.2Win32OS支持多线程应用程序、进程及线程的关系DOS是单用户单任务的。

Win32操作系统是多任务的,并且支持一个进程中有多个线程。一个线程(thread)是进程内的一条执行路径,是一个应用程序中的一条可执行路径。一个进程中至少要有一个线程,称为主线程。当启动了一个应用程序时,操作系统将为它创建了一个进程,同时创建该进程的主线程,并开始执行主线程。主线程可以创建并启动其他辅助线程,由主线程创建的线程又可以创建并启动更多的线程。2021/8/17707.2.2Win32OS支持多线程应用程序、进程及线程7.2.2Win32OS支持多线程单CPU分时地运行进程中的各个线程2021/8/17717.2.2Win32OS支持多线程单CPU分时地运行进7.2.3多线程机制在网络编程中的应用如果一个应用程序,有多个任务需要同时进行处理,则适合使用多线程机制。对于网络上客户机软件:采用多线程,能克服在单线程的编程模式下,由于阻塞等待而产生的客户程序就不能及时响应用户的操作命令的问题。对于网络上服务器软件:采用多线程的编程技术,能更好地为多个客户服务。对于一个客户:采用多线程机制也能大大提高应用程序的运行效率。网络在线实时监控软件:2021/8/17727.2.3多线程机制在网络编程中的应用如果一个应7.3VC6.0对多线程网络编程的支持VC6.0环境下,两种开发程序的方法:直接使用Win32API来编写Win32应用程序利用MFC基础类库编写C++风格的应用程序。在这两种Windows应用程序的开发方式下,多线程的编程原理是一致的。2021/8/17737.3VC6.0对多线程网络编程的支持VC6.0环境下7.3.1MFC支持的两种线程微软的基础类库MFC提供了对于多线程应用程序的支持。在MFC中,线程分为两种:用户接口线程:(user-interfacethread),或称用户界面线程;工作线程:(theworkerthread),这两类线程可以满足不同任务的处理需求。2021/8/17747.3.1MFC支持的两种线程微软的基础类库MF1、用户接口线程

作用:用于处理用户的输入,响应用户产生的消息。MFC为用户接口线程提供了一个消息泵。同时包含一个消息循环,以应对各种事件。

MFC应用程序的CWinApp类对象是一个典型的用户接口线程在MFC应用程序中,CWinThread是用户接口线程的基类,CWinApp就是从CWinThread类派生出来的,编写用户接口线程候,也需要从CWinThread类派生。2021/8/17751、用户接口线程2021/8/17132.工作线程

工作线程(workerthread),适用于处理那些不要求用户输入并且比较消耗时间的其他任务。对用户来说,工作线程运行在后台。这就使得工作线程特别适合去等待一个事件的发生。CWinThread类同样是工作线程的基类。在编写工作线程的时候,可以调用MFC的AfxBeginThread函数,来创建CWinThread对象。2021/8/17762.工作线程2021/8/17147.3.2创建MFC的工作线程利用MFC创建工作线程的步骤:第一步:编程实现控制函数第二步:创建并启动工作线程一般不必从CWinThread派生一个类。如果需要一个特定版本的CWinThread类,也可以去派生;但对于大多数的工作线程是不要求的。可以不作任何修改地使用CWinThread类。2021/8/17777.3.2创建MFC的工作线程利用MFC创建工作线程的步7.3.2创建MFC的工作线程1、编程实现控制函数一个工作线程对应一个控制函数。线程执行的任务都应编写在控制函数之中,规定了该线程的执行代码,当控制函数执行结束而退出时,线程也就随之终止。编写工作线程的控制函数必须遵守一定的格式,控制函数的原型声明是:UINTControlFunctionName(LPVOIDpParam);

pParam是一个数据结构;2021/8/17787.3.2创建MFC的工作线程1、编程实现控制函数2027.3.2创建MFC的工作线程2.创建并启动工作线程(Startingthethread)

启动线程:即开始运行它对应的控制函数。在主线程或其他线程中调用AfxBeginThread()函数就可以创建新的线程,并使新线程开始运行。一般将线程的创建者称为新线程的父线程。AfxBeginThread()函数有两个重载的版本,区别在于使用的入口参数不同。2021/8/17797.3.2创建MFC的工作线程2.创建并启动工作线程(S7.3.2创建MFC的工作线程CWinThread*AfxBeginThread(AFX_THREADPROCpfnThreadProc,//控制函数的地址LPVOIDpParam,//数据结构的指针,传数据给线程控制函数intpPriority=THREAD_PRIORITY_NORMAL,//优先级UINTnStackSize=0,//线程的堆栈大小(缓冲区)DWORDdwCreateFlags=0,//线程的运行状态,是否被挂起LPSECURITY_ATTTRIBUTESlpSecurityAttrs=NULL//安全属性);

后面4个参数为可选参数。2021/8/17807.3.2创建MFC的工作线程CWinThread*创建工作线程的例子3、创建工作线程的例子功能:求长度为N的数组Arry的各元素的和。编程实现线程控制函数(1)//首先定义了一个结构:struct{

intN;//数组元素的个数。

double*Arry;//指向一个双精度实数的数组

}myData;

//定义了此结构类型的变量,省略了初始化的代码myDatass;

2021/8/1781创建工作线程的例子3、创建工作线程的例子2021/8/171创建工作线程的例子(2)//接着定义线程的控制函数。UINTMyCalcFunc(LPVOIDpParam){ //如果入口参数为空指针,终止线程。 if(pPara==NULL)AfxEndThread(MY_NULL_POINTER_ERROR); intN=pPara->N;//数组的元素个数。 double*Arry=pPara->Arry;//指向数组的第一个元素。 doublesum=0;//数组元素之和。 for(inti=0;i<N;i++)sum+=Arry[i];//求和。 CStringbb; bb.Format(“数组的和是:%d”,sum);//格式化显示字符串。 AfxMessageBox(bb);//显示结果。 return0; }改为:pPara

2021/8/1782创建工作线程的例子(2)//接着定义线程的控制函数。改为:创建工作线程的例子调用AfxBeginThread()来创建并启动这个线程。将控制函数名和结构变量的地址作为参数传递,其他的参数省略,使用默认值。AfxBeginThread(MyCalcFunc,&ss);一旦调用了此函数,线程就被创建,并开始执行线程函数。当数据的计算完成时,函数将停止运行,相应的线程也随即终止。线程拥有的堆栈和其他资源都将释放。CWinThread对象将被删除。2021/8/1783创建工作线程的例子调用AfxBeginThread()来创建创建工作线程的例子4.创建工作线程的一般模式可以得出创建工作线程的一般模式:定义一个结构myData用于传递数据;工作线程控制函数的框架;UINTMyThreadProc(LPVOIDpPara){if(pPara==NULL){AfxEndThread(MY_NULL_POINTER_ERROR);

return1;

}2021/8/1784创建工作线程的例子4.创建工作线程的一般模式2021/8/1创建工作线程的例子 ……//利用入口参数作某些工作。 return0;//线程成功地完成并返回。 }在程序的另一个函数中插入以下代码。 ..............

myDatass;

AfxBeginThread(MyThreadProc,ss);2021/8/1785创建工作线程的例子 ……//利用入口参数作某些工作。2027.3.3创建并启动用户界面线程用户界面线程允许用户使用更多的用户界面对象,如对话框或其他窗口。因此,要使用用户界面线程,需要做更多的工作:创建并启动用户界面线程要经过三个步骤:从CWinThread类派生出自己的线程类;改造这个线程类,使它能够完成用户希望的工作创建并启动用户界面线程。2021/8/17867.3.3创建并启动用户界面线程用户界面线程允许7.3.3创建并启动用户界面线程1、从CWinThread类派生出自己的线程类要创建一个用户界面线程,首先从CWinThread类派生出自己的线程类,可借助ClassWizard来完成。

2、改造自己的线程类对这个派生的线程类作以下改造工作:在类的.h头文件中,用DECLARE_DYNCREATE宏来声明这个类(使其支持动态创建);DECLARE_DYNCREATE(class_name)2021/8/17877.3.3创建并启动用户界面线程1、从CWinThrea7.3.3创建并启动用户界面线程在类的.CPP文件中,用IMPLEMENT_DYNCREATE宏来实现这个类。调用格式是:IMPLEMENT_DYNCREATE(class_name,base_class_name)两个参数分别是参数是线程类名和它的基类名。2021/8/17887.3.3创建并启动用户界面线程在类的.CPP文件中,用7.3.3创建并启动用户界面线程对派生类的改造:这个线程类须重载它的基类的某些成员函数,如该类的InitInstance()成员函数;对于其他成员函数,可以有选择地重载。创建新的用户界面窗口类,并建立线程类与用户界面窗口类的联系。利用类向导为新建的线程类添加控件成员变量,添加响应消息的成员函数,编写实现代码。2021/8/17897.3.3创建并启动用户界面线程对派生类的改造:20217.3.3创建并启动用户界面线程成员函数说明ExitInstance线程终止时执行InitInstance线程实例化时执行OnIdle线程空闲时,一般不重载PreTranslateMessage消息派发前可以重新解释消息Run线程控制函数,消息循环,不重载WinThread的成员函数包括:2021/8/17907.3.3创建并启动用户界面线程成员函数说明ExitIn7.3.3创建并启动用户界面线程3.创建并启动用户界面线程可以使用MFC提供的AfxBeginThread()函数的另一个创建并启动用户界面线程,调用格式是:CWinThread*AfxBeginThread(CRuntimeClass*pThreadClass,//继承的线程类指针intpPriority=THREAD_PRIORITY_NORMAL,//优先级UINTnStackSize=0,//创建线程的堆栈大小DWORDdwCreateFlags=0,//线程的状态LPSECURITY_ATTTRIBUTESlpSecurityAttrs=NULL);2021/8/17917.3.3创建并启动用户界面线程3.创建并启动用户界面线7.3.3创建并启动用户界面线程4.AfxBeginThread()函数所作的工作它创建一个新的用户自己的线程类的对象,该对象继承了CWinThread类的属性。MFC自动调用新线程类的InitInstance()函数,来初始化这个新的线程类对象实例。调用CWinThread::CreateThread函数来开始执行这个线程,并运行RUN函数,进入消息循环。函数返回一个指向新生成的CWinThread对象的指针,可以把它保存在一个变量中,其它线程就可以利用这个指针来访问该线程类的成员变量或成员函数。2021/8/17927.3.3创建并启动用户界面线程4.AfxBeginTh7.3.3创建并启动用户界面线程线程的消息循环:系统自动地为每一个线程创建一个消息队列,如果线程创建了一个或多个窗口,就必须提供一个消息循环,这个消息循环从线程的消息队列中获取消息,并把它们发送到相应的windows过程。

因为系统将消息导向独立的应用程序窗口,所以在开始线程的消息循环之前,线程必须至少创建一个窗口,大多数基于Win32的应用程序包含一个单一的线程,该线程创建了若干窗口。2021/8/17937.3.3创建并启动用户界面线程线程的消息循环:20217.3.4终止线程1、正常终止线程工作线程:线程控制函数代码执行完毕用户线程:发送消息到窗口2、提前终止线程利用AfxEndThread(UNITnExitCode)函数参数为线程的终止代码,该函数应该在线程内部调用;3、终止线程的另一种方法TerminateThread()函数强行终止。2021/8/17947.3.4终止线程1、正常终止线程2021/8/17324、获取线程的终止代码BoolGetExitCodeThread(HANDLEhThread,//线程句柄LPDWORDlpExitCode//接收线程的终止代码)5、设置线程的优先级SetThreadPriority(HANDLEhThread,//线程句柄intnPriority//优先级水平)2021/8/17954、获取线程的终止代码2021/8/1733多线程编程实例2021/8/1796多线程编程实例2021/8/1734第8章Winsock的输入/输出模型2021/8/1797第8章Winsock的输入/输出模型2021/8/173

第7章提到,WinSock在进行输入输出的时候,可以采用阻塞模式或非阻塞模式。使用非阻塞套接字,带有I/O操作的随机性,使非阻塞套接字难于操作,给编程带来困难。为了解决这个问题,对于非阻塞的套接字工作模式,进一步引入了五种“套接字I/O模型”,它们有助于应用程序通过一种异步方式,同时对一个或多个套接字上进行的通信加以管理。

2021/8/1798第7章提到,WinSock在进行输入输出的时候,可这些模型包括:select(选择)、WSAAsyncSelect(异步选择)、WSAEventSelect(事件选择)、OverlappedI/O(重叠式I/O)Completionport(完成端口)。本章将主要介绍前三种。2021/8/1799这些模型包括:2021/8/1737不同的Windows平台支持不同的I/O模型。如表8.1:2021/8/17100不同的Windows平台支持不同的I/O模型。如表8.1:28.1select模型select(选择)模型是Winsock中最常见的I/O模型。它的中心思想是利用select函数,实现对多个套接字I/O的管理。利用select函数,可以判断套接字上是否存在数据,或者能否向一个套接字写入数据。只有在条件满足时,才对套接字进行输入输出操作,从而避免无功而返的I/O函数调用,避免频繁产生WSAEWOULDBLOCK错误,使输入输出变得有序。2021/8/171018.1select模型select(选择)模型是1.select的函数select的函数原型如下,其中fd_set数据类型,代表着一系列特定套接字的集合。intselect( intnfds, fd_setFAR*readfds, fd_setFAR*writefds, fd_setFAR*exceptfds, conststructtimevalFAR*timeout

);2021/8/171021.select的函数2021/8/1740说明:

select函数对readfds、writefds和exceptfds三个集合中指定的套接字进行检查,看是否有数据可读、可写或有带外数据,如果有至少一个套接字符合条件,就立即返回。符合条件的套接字仍在集合中,不符合条件的套接字则被删去。如果一个也没有,则等待。但最多等待timeout所指定的时间,便返回。2021/8/17103说明:2021/8/17412.操作套接字集合的宏在程序中,用select对套接字进行监视之前,必须先将要检查的套接字句柄分配给某个集合,设置好相应的fd_set结构,再来调用select函数,便可知道一个套接字上是否正在发生上述的I/O活动。Winsock提供了下列宏操作,专门对fd_set数据类型进行操作FD_CLR(s,*set):从set中删除套接字s。2021/8/171042.操作套接字集合的宏2021/8/1742FD_ISSET(s,*set):检查s是否set集合的一名成员;如答案是肯定的是,则返回TRUE。FD_SET(s,*set):将套接字s加入集合set。FD_ZERO(*set):将set初始化成空集合。其中,参数s是一个要检查的套接字,参数set是一个fd_set集合类型的指针。例如:调用select函数前,可使用FD_SET宏将指定的套接字加入到fd_read集合中,select函数完成后,可使用FD_ISSET宏,来检查该套接字是否仍在fd_read集合中。2021/8/17105FD_ISSET(s,*set):检查s是否set集合的一3.select模型的操作步骤使用FD_ZERO宏初始化感兴趣的每一个fd_set集合。使用FD_SET宏将要检查的套接字句柄添加到感兴趣的每个fd_set集合中,相当在指定的fd_set集合中,设置好要检查的I/O活动。调用select函数,然后等待。select完成并返回后,会修改每个fd_set结构,删除那些不存在待决I/O操作的套接字句柄,在各个fd_set集合中返回符合条件的套接字。2021/8/171063.select模型的操作步骤2021/8/1744根据select的返回值,使用FD_ISSET宏,对每个fd_set集合进行检查,判断一个特定的套接字是否仍在集合中,便可判断出哪些套接字存在着尚未完成(待决)的I/O操作。知道了每个集合中“待决”的I/O操作之后,对相应的套接字的I/O进行处理,然后返回步骤1,继续进行select处理。2021/8/17107根据select的返回值,使用FD_ISSET宏,对每个f4.举例下面的例子用select管理一个套接字上的I/O操作:SOCKETs; //定义一个套接字fd_setfdread; //定义一个套接字集合变量intret; //返回值//创建一个套接字,并接受连接.......2021/8/171084.举例2021/8/1746//管理该套接字上的输入/输出while(TRUE){

FD_ZERO(&fdread);//调用select()之前要清除套接字集合

FD_SET(s,&fdread);//将套接字s添加到fdread集合中 //调用select()函数,并等待它的完成, //这里只是想检查s是否有数据可读

if((ret=select(0,&fdread,NULL,NULL,NULL))== SOCKET_ERROR) {

……//处理错误的代码

}

2021/8/17109//管理该套接字上的输入/输出2021/8/1747//返回值大于零,说明有符合条件的套接字//对于本例这个简单的情况,select()的返回值应是1。//如果程序处理更多的套接字,返回值可能大于1,//应用程序应检查特定的套接字是否在返回的集合中if(ret>0){if(FD_ISSET(s,&fdread)){

……//对该套接字进行读操作}}}2021/8/17110//返回值大于零,说明有符合条件的套接字2021/8/1748.2WSAAsyncSelect异步I/O模型异步I/O模型通过调用WSAAsyncSelect函数实现。利用这个模型,应用程序可在一个套接字上,接收以Windows消息为基础的网络事件通知。该模型最早出现于Winsock的1.1中,以适应其多任务消息环境。2021/8/171118.2WSAAsyncSelect异步I/O模型1.WSAAsyncSelect函数函数的定义是: intWSAAsyncSelect( SOCKETs, HWNDhWnd

温馨提示

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

评论

0/150

提交评论