VisualC++网络编程案例实战之多线程与异步套.ppt

上传人:qwe****56 文档编号:80595947 上传时间:2023-03-23 格式:PPT 页数:108 大小:2.62MB
返回 下载 相关 举报
VisualC++网络编程案例实战之多线程与异步套.ppt_第1页
第1页 / 共108页
VisualC++网络编程案例实战之多线程与异步套.ppt_第2页
第2页 / 共108页
点击查看更多>>
资源描述

《VisualC++网络编程案例实战之多线程与异步套.ppt》由会员分享,可在线阅读,更多相关《VisualC++网络编程案例实战之多线程与异步套.ppt(108页珍藏版)》请在得力文库 - 分享文档赚钱的网站上搜索。

1、第3章 多线程与异步套接字编程在Windows操作系统中,线程是指系统中最小的功能执行单元,其可以独立地完成某一项功能。所以在进行Windows编程中,如果用户使用多线程处理某个功能,那么该功能被处理的效率远比单个线程处理的效率高。在本章中,将向用户介绍使用多线程处理异步套接字编程的相关方法。3.1 多线程技术在Windows操作系统中,所有程序的功能都是由每个程序中的多个线程共同完成。从某种特定的意义上而言,线程才是计算机真正意义上的功能执行者。而从线程执行的数目而言,线程可以分为单线程和多线程。其中,多线程是由多个单线程组成。如果从线程的执行效率而言,多线程比单线程的执行效率高很多。那么,

2、当用户在编程时,使用多线程技术可以提高程序的执行效率。3.1.1 基本概念在本节中,将介绍一些关于计算机进程和线程方面的基本概念。用户通过这些基本概念的学习,将学习到计算机程序的工作原理以及多线程处理方面的基础知识。1计算机进程在计算机操作系统中,进程是指当可执行文件运行时,系统所创建的内核对象。例如,在计算机中,用户可以通过任务管理器查看当前系统中所有的进程,如图3.1所示。图3.1 显示系统中所有的进程在一个以“.EXE”为后缀名的可执行程序中,可以包括一个或多个进程,并且每个进程都有自己的执行地址空间。这些地址空间在逻辑层面上可以被不同的进程重复使用。例如,计算机系统中有两个进程,分别为

3、进程A和进程B。如果进程A在某一地址空间中存放了一个数据,而进程B可以在同一地址空间中存放另一个数据。当两个进程同时在该地址空间中取出各自对应的数据时,程序不会出现非法访问内存等错误信息。这是因为在进程中真正执行某个功能的应该是该进程中的线程,这些线程只是共享同一个进程的地址空间。2计算机线程线程是计算机中最小的执行单元。通常,当Windows应用程序运行时,操作系统都会为其自动创建一个线程,即主线程。通过主线程,用户可以创建多个线程或进程。由于一个进程中的所有线程共享该进程地址空间,所以,在同一个进程中可以实现多个线程间的相互通信。当用户编程时,为了完成某一项功能可以使用多线程技术创建多个线

4、程共同完成这个功能。这种方法比单线程技术实现同一功能的效率快。实际上,现在很多的CPU处理器都只支持单线程技术,但是一个多线程程序为什么仍能运行,这是因为系统程序为系统中的每个线程都分配了执行时间,而且这个执行时间非常短,以至于用户感觉几个线程在同时运行。在本章中,主要是让用户学习多线程技术的编程方法以及它在网络编程中的使用等。所以,关于多线程技术的其他知识,本书将不再进行深入讲解。3.1.2 创建线程用户编程时,使用多线程技术需要首先创建线程,然后再使用这些线程执行相应的功能。如果用户是在VC中编写多线程程序,则可以调用API函数CreateThread()创建线程。该函数原型如下:HAND

5、LE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD lpThreadId );该函数的作用是用于创建一个线程,并将返回该线程的句柄。其中,各个参数含义如下:参数lpThreadAttributes是一个指向结构体SECURITY_ATTRIBUTES的指针,表示指定新建线程的安全属性。该参数可以设置为NULL,表示创建线程时使

6、用默认的安全属性。参数dwStackSize指定线程初始化时地址空间的大小。如果这个参数指定为0,那么新创建线程的地址空间大小与调用该函数的线程地址空间大小一样。参数lpStartAddress将指定该线程的线程函数的地址。当线程创建成功以后,新建线程将调用该线程函数执行某个功能。参数lpParameter表示将要传递给新建线程的命令行参数。新建线程可以根据该命令参数的不同而执行不同的功能。参数dwCreationFlags用于指定新线程创建后是否立即运行。其取值如表3.1所示。表3.1 线程创建标记 注意:当用户创建线程时,将该参数值指定为CREATE_SUSPENDED,则线程将处于暂停状

7、态,直到用户调用相关函数将线程恢复运行为止。参数lpThreadId表示新建线程的ID号。在这里,用户可以将该参数设置为NULL。状状 态 值作作 用用CREATE_SUSPENDED线程程创建成功后建成功后暂停运行停运行0线程程创建成功后立即运行建成功后立即运行例如,用户在程序中,使用该函数分别创建两个线程,并在这两个线程中分别打印出各自的函数信息。首先,用户打开VC,并创建一个基于控制台程序窗口的工程,并将工程名修改为“创建线程”,如图3.2所示。图3.2 创建控制台工程然后,单击“OK”按钮,转到工程设置中将该工程设置为一个空的控制台程序,如图3.3所示。图3.3 指定该工程为空工程最后

8、,单击“Finish”按钮,完成该控制台工程的设置,返回到VC主界面中。用户在该工程中需要添加一个空白的C+源文件以便编写代码。创建线程实例,代码如下:在程序中,用户首先声明了两个线程函数,然后在主函数中创建两个线程,分别为h1和h2。当线程创建成功以后,系统会自动调用相应的线程函数并执行其中的功能。将上面的程序在VC中进行编译、运行,如图3.4所示。图3.4 创建线程实例用户可以从该实例程序中,学习到怎样声明和定义线程函数以及使用函数CreateThread()进行线程的创建。注意:用户从程序运行的结果中可以得知,线程1和线程2并没有按照代码的运行顺序而执行其功能。这时候,用户需要使用线程的

9、同步技术避免类似情况的发生。在3.2节中,将为用户讲解实现线程同步的编程方法。3.2 实现线程同步线程同步是指同一进程中的多个线程互相协调工作达到一致性。当用户编写程序时,有时会使多个代码段同时读取或修改相同地址空间中的共享数据。此时,在操作系统中,可能会出现一个代码段在读取数据,而另一个代码段却正在修改数据。这样的情况会导致程序发生读写错误,造成程序异常退出。用户为了避免出现类似情况,需要使用线程同步技术。即当一个线程程序对资源进行读写时,其他的线程程序则处于等待状态。3.2.1 临界区对象临界区对象是指当用户使用某个线程访问共享资源时,必须使代码段独享该资源,不允许其他线程程序访问该资源。

10、待该代码段访问完资源后,其他程序才能对资源进行访问。这样的模式好比某个用户在试衣间里试衣服,而其他用户则只能等待。当试衣间里的用户出来之后,其他用户才能进入试衣间内。在本节中,将向用户分别介绍如何使用API函数和MFC类对临界区对象进行编程的方法。1使用API函数操作临界区当用户在实际编写程序时,使用临界区对象前必须对临界区进行初始化。在VC中进行编程,用户可以调用函数InitializeCriticalSection()对临界区对象进行初始化。该函数原型如下:void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection

11、);该函数的作用是为应用程序初始化临界区。其中,参数lpCriticalSection是指向结构体CRITICAL_SECTION的指针变量。由于该参数所标识的结构体对象是由操作系统自动进行维护的,所以用户在编程时可以不用理会该结构体变量的具体操作。例如,用户调用该函数对临界区进行初始化。代码如下:CRITICAL_SECTION m_sec;/定义结构体CRITICAL_SECTION变量InitializeCriticalSection(&m_sec);/初始化临界区./省略部分代码在程序中,用户首先定义了结构体CRITICAL_SECTION的变量m_sec,其用于存放临界区的相关信息。

12、然后,调用函数InitializeCriticalSection()对临界区进行初始化。当用户对临界区进行初始化以后,程序便可以进入该临界区并拥有该临界区对象的所有权。而其他程序则只能等待进入临界区的程序释放临界区的所有权后,才能进入临界区进行操作。用户实现这个功能可以调用函数EnterCriticalSection()。该函数原型如下:void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);该函数的作用是使调用该函数的线程程序进入已经初始化的临界区,并拥有该临界区的所有权。如果线程程序获得临界区的所有权成功,则该函数将返

13、回,调用线程继续执行。否则,该函数将一直等待,这样会造成该函数的调用线程也一直等待。注意:当用户使用临界区对象实现线程同步编程时,不应使函数EnterCriticalSection()等待的时间过长。因为这样会造成调用线程的等待,使操作系统出现假死现象。例如,用户调用该函数进入临界区操作被保护的共享资源,同时获取该共享资源的所有权。代码如下:./省略部分代码EnterCriticalSection(&m_sec);/进入已经被初始化的临界区./省略部分代码如果调用该函数的线程成功获得临界区所有权,那么该线程将继续执行代码。否则,该线程将一直等待,直到获得临界区的所有权。当线程使用完共享资源后,

14、则必须离开临界区并释放对该临界区的所有权,以便让其他线程也获得访问该共享资源的机会。函数LeaveCriticalSection()的作用是使已经进入临界区的线程释放对该临界区的所有权并离开临界区。该函数原型如下:void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);调用该函数的线程将离开指定的临界区并释放该临界区的所有权。当用户使用临界区对象进行编程,一定要在程序不使用临界区时,调用该函数释放临界区所有权。否则,程序将一直等待,造成程序假死。例如,当用户使用完被临界区保护的共享资源后,需要调用函数LeaveCritic

15、alSection()释放该临界区的所有权。代码如下:./省略部分代码LeaveCriticalSection(&m_sec);/释放临界区所有权并离开该临界区./省略部分代码如果调用线程释放临界区的所有权之后,用户应该在程序中调用函数DeleteCritical Section()将该临界区从内存中删除。该函数原型如下:void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection );该函数的作用是删除程序中已经被初始化的临界区。如果函数调用成功,则程序会将内存中的临界区删除,防止出现内存错误。例如,用户使用临界区相关的API

16、函数进行编程。代码如下:在程序中,用户首先声明临界区对象Section和两个线程函数(myfun1()和myfun2()),然后在主函数中创建两个线程,即h1和h2。主线程调用函数InitializeCriticalSection()初始化临界区对象并通过函数Sleep()暂停10秒。在线程函数1中,程序调用函数EnterCriticalSection()进入临界区,通过循环使变量a1自加,然后输出结果并离开临界区将共享变量a1的所有权交给线程函数2。在线程函数2中实现同样的功能。程序运行结果,如图3.5所示。图3.5 程序运行结果用户从程序运行的结果中可以看到,线程1和线程2的线程函数交替执

17、行输出结果并且变量a1的值是按照顺序增加的。由于当每个线程进入临界区中操作共享变量时,另一个线程则只能等待。所以,该程序实现了同进程的不同线程之间的相互协调工作。如果用户将临界区相关代码注释起来,再编译执行程序,如图3.6所示。图3.6 不含临界区对象的程序运行界面在图3.6中,用户会发现两个线程函数并没有交替执行,而且输出的变量结果也未按照顺序增加。所以,在线程同步中临界区对象是非常重要的。2使用CCriticalSection类操作临界区CCriticalSection类是MFC中所定义的临界区类,其作用与临界区相关API函数实现的功能一样。本节中,将向用户简要介绍该类在实际编程中的成员函

18、数以及用法。首先,用户编程时为了方便线程函数访问CCriticalSection类对象,必须将该对象定义为全局变量。代码如下:CCriticalSection m_Sec;/定义全局变量m_Secmain()./省略部分代码然后,调用该类中成员函数Lock()对临界区进行锁定。其原型如下:BOOL Lock();/锁定临界区函数Lock()的作用是程序进入临界区执行相关功能并获得该临界区的所有权。如果函数调用成功,则返回true。否则,函数返回false。例如,程序根据该函数的返回值判断锁定临界区是否成功。代码如下:./省略部分代码if(m_Sec.Locket()/调用函数锁定临界区 Mes

19、sageBox(程序锁定临界区成功!);/提示信息else/如果锁定失败 MessageBox(程序锁定临界区失败!);./省略部分代码如果程序不再使用临界区,可以调用成员函数Unlock()离开临界区并释放其所有权。该函数原型如下:virtual BOOL Unlock();/释放临界区该函数调用成功,则返回true。否则,函数将返回false。例如,用户使用CCritical Section类进行临界区编程。代码如下:用户使用CCriticalSection类或者临界区相关的API函数进行临界区编程,都可以使线程之间实现同步。在本小节中,主要讲解了怎样使用临界区对象实现线程同步以及与临界区

20、编程相关的API函数和MFC类。3.2.2 事件对象事件对象是指用户在程序中使用内核对象的有无信号状态实现线程的同步。在本节中,将向用户介绍利用事件对象实现线程同步技术的相关API函数以及MFC类。1使用API函数操作事件对象用户编程时,使用事件对象必须首先创建事件对象。在API函数中,用户可以使用函数CreateEvent()创建并返回事件对象。该函数原型如下:HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL bManualReset,BOOL bInitialState,LPCTSTR lpName);如果该函数

21、调用成功,则返回新创建的事件对象。其参数及意义如下:参数lpEventAttributes是结构体SECURITY_ATTRIBUTES的指针,表示新创建的事件对象的安全属性。如果该参数为NULL,则表示程序使用的是默认安全属性。参数bManualReset表示所创建的事件对象是人工重置还是自动重置。如果该参数为true,则表示程序所创建的事件对象为人工重置对象。如果为false,则表示创建的事件对象为自动重置对象。参数bInitialState表示事件对象的初始状态。如果该参数为true,则表示该事件对象初始时为有信号状态。否则,表示事件对象初始化时为无信号状态。参数lpName表示事件对象

22、的名称。如果该参数为NULL,则表示程序创建的是一个匿名的事件对象。注意:如果参数bManualReset设置为true,则表示当调用线程获得其所有权后,用户需要显式地调用函数ResetEvent()将事件对象设置为无信号状态。如果为自动重置的事件对象,则系统会自动将其设置为无信号状态。所以,一般情况下用户编程均将事件对象设置为自动重置。例如,用户创建一个初始化状态为有信号并且是自动重置的事件对象。代码如下:HANDLE hevent;/定义事件对象Hevent=:CreateEvent(NULL,false,true,NULL);/创建事件对象./省略部分代码在程序中,用户创建了一个初始状态

23、为有信号自动重置并且具有默认安全属性的匿名事件对象。当用户创建事件对象时,如果将其初始状态设置为无信号,则需要用户手动将其设置为有信号状态。实现该功能可以调用函数SetEvent()将指定的事件对象设置为有信号状态。该函数原型如下:BOOL SetEvent(HANDLE hEvent);该函数调用成功,则返回true。否则,将返回false。参数hEvent表示将设置的事件对象句柄。与该函数功能相反,函数ResetEvent()则将指定的事件对象设置为无信号状态,其参数及意义与函数SetEvent()相同。当然,线程也可以通过调用函数WaitForSingleObject()主动请求事件对象

24、。该函数原型如下:DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds );该函数将在用户指定的事件对象上等待。如果事件对象处于有信号状态,函数将返回。否则,函数将一直等待,直到用户所指定的时间到达。各参数及其意义如下:参数hHandle表示函数所等待的事件对象句柄。参数dwMilliseconds表示该函数将在事件对象上的等待时间,如果该参数为INFINITE,则该函数将永远等待。该函数的返回值可以表明引起函数返回的原因,其部分返回值如表3.2所示。表3.2 函数部分返回值例如,用户使用事件对象实现线程同步编程。代码如下:

25、在代码中,用户主要使用了函数WaitForSingleObject()对事件对象进行请求,然后再使用事件对象相关API函数设置其有无信号状态。实例程序根据事件对象的信号状态判断线程的执行顺序以及输出全局变量a的值,如图3.7所示。返返 回回 值返回返回值意意义WAIT_TIMEOUT用用户指定的等待指定的等待时间已已过WAIT_OBJECT_0线程所程所请求的求的对象象为有信号状有信号状态图3.7 事件对象实现线程同步在本小节中,向用户介绍了事件对象编程的相关API函数的原型以及参数意义,并配合实例程序讲解了这些API函数的使用方法。2使用CEvent类实现线程同步CEvent类是MFC中支持

26、事件对象编程的类。本小节将向用户讲解该类实现线程同步技术的部分常用函数以及使用方法。首先,用调用该类构造函数创建对象。构造函数原型如下:CEvent(BOOL bInitiallyOwn=FALSE,BOOL bManualReset=FALSE,LPCTSTR lpszName=NULL,LPSECURITY_ATTRIBUTES lpsaAttribute=NULL);在构造函数中,参数及其意义如下:参数bInitiallyOwn表示事件对象的初始化状态。如果该参数为true,则表示该事件对象为有信号状态。否则,该事件对象为无信号状态。默认为无信号状态。参数bManualReset表示该事

27、件对象是人工重置还是自动重置对象。如果该参数为true,则事件对象为人工重置。否则,事件对象为自动重置。参数lpszName表示用户为该事件对象的命名。默认情况下为NULL。参数lpsaAttribute表示该事件对象的安全属性。一般情况下,创建的事件对象均指定为默认安全属性。例如,用户使用CEvent类创建对象。代码如下:./省略部分代码CEvent event(true,false,NULL,NULL);/创建事件类对象将事件对象设置为有信号或无信号状态可以分别调用函数SetEvent()和ResetEvent()。函数原型如下:BOOL SetEvent();/设置事件对象为有信号BOO

28、L ResetEvent();/设置事件对象为无信号以上两个函数若调用成功,则返回true。否则,将返回false。例如,用户使用CEvent类在程序中实现线程同步。代码如下:在上面的程序中,用户主要使用了CEvent类的相关函数实现线程的同步。本小节主要向用户讲解如何使用API函数或者CEvent类创建事件对象,实现线程同步技术的相关方法。3.2.3 互斥对象互斥对象与前面所学的临界区对象和事件对象的作用一样,均用于实现线程同步。但是,互斥对象还可以在进程之间使用。在互斥对象中,包含一个线程ID和一个计数器。线程ID表示拥有该互斥对象的线程,计数器用于表示该互斥对象被同一线程所拥有的次数。在

29、程序中,同样可以使用API函数或者MFC类操作互斥对象,实现线程同步。1使用API函数操作互斥对象用户可以调用API函数CreateMutex()创建并返回互斥对象。该函数原型如下:HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,BOOL bInitialOwner,LPCTSTR lpName );如果该函数调用成功,将返回新创建的互斥对象句柄。否则,将返回NULL。各参数及其意义如下:参数lpMutexAttributes指定新创建互斥对象的安全属性。如果该参数为NULL,表示互斥对象拥有默认的安全属性。参数bIniti

30、alOwner表示该互斥对象的拥有者。如果为true,则表示创建该互斥对象的线程拥有其所有权。如果为false,表示创建互斥对象的线程不能拥有该互斥对象的所有权。参数lpName表示互斥对象的名称。若该参数为NULL,则表示程序创建的是匿名对象。如果用户为该参数指定值,则在程序中可以调用函数OpenMutex()打开一个命名的互斥对象。例如,用户创建一个匿名的互斥对象,代码如下:HANDLE hmutex;/声明互斥对象句柄hmutex=:CreateMutex(NULL,FALSE,NULL):/创建互斥对象并返回其句柄./省略部分代码线程使用完该互斥对象以后,用户应该调用函数Release

31、Mutex()释放对该互斥对象的所有权,也就是让互斥对象处于有信号状态。函数ReleaseMutex()的原型如下:BOOL ReleaseMutex(HANDLE hMutex);如果该函数调用成功,则返回true。否则,将返回false。参数hMutex表示将释放的互斥对象句柄。例如,用户将上面创建的互斥对象句柄hmutex与调用该句柄的线程进行分离。代码如下:./省略部分代码:ReleaseMutex(hmutex);/释放互斥对象句柄在互斥对象中,线程也可以调用函数WaitForSingleObject()对该对象进行请求。当互斥对象无信号时,该函数将一直等待,直到该互斥对象有信号或用

32、户所指定的等待时间已过。否则,该函数将返回。由于这个函数在3.2.2节中已经详细讲解,所以在这里不再赘述。如果用户对该函数不了解,请复习前面的相关知识。例如,用户使用互斥对象实现线程的同步,代码如下:在程序中,用户首先创建互斥对象并返回其句柄。然后利用该互斥对象句柄实现线程1和线程2的同步并输出全局变量a的值,如图3.8所示。图3.8 使用互斥对象实现线程同步注意:当用户使用互斥对象编程时,应该牢记互斥对象的使用方法是哪个线程拥有其所有权,哪个线程就应该释放该互斥对象。2使用CMutex类CMutex类是MFC中的互斥对象类。该类是由CSyncObject类派生而来,所以使用CMutex类时可

33、以调用其父类CSyncObject中的成员函数实现指定功能。在本小节中,将向用户讲解使用CMutex类实现线程同步技术的方法。首先,创建CMutex类对象是通过其构造函数实现的。构造函数原型如下:CMutex(BOOL bInitiallyOwn=FALSE,LPCTSTR lpszName=NULL,LPSECURITY_ ATTRIBUTES lpsaAttribute=NULL);该函数的作用是构造CMutex类的实例对象。其参数及意义如下:参数bInitiallyOwn表示调用线程是否拥有所创建的互斥对象。如果为true,则表示创建该互斥对象的线程拥有其所有权。如果为false,表示创

34、建互斥对象的线程不能拥有该互斥对象的所有权。参数lpName表示互斥对象的名称。若该参数为NULL,则表示程序创建的是匿名对象。参数lpMutexAttributes指定新创建互斥对象的安全属性。如果该参数为NULL,表示互斥对象拥有默认的安全属性。例如,用户通过CMutex类创建一个互斥对象。代码如下:./省略部分代码CMutex mex(FALSE,NULL,NULL);/创建互斥对象./省略部分代码用户创建互斥对象成功之后,可以在线程函数中调用函数Lock()和Unlock()对该互斥对象所保护的区域进行锁定和解锁,控制其他线程对保护区域的访问权限。这两个函数的原型如下:virtual

35、BOOL Lock(DWORD dwTimeout=INFINITE);virtual BOOL Unlock(LONG lCount,LPLONG lpPrevCount=NULL);其中,函数Lock()的作用是锁定保护区域的数据,避免其他线程对该区域数据进行访问,并且将互斥对象设置为无信号。如果该函数调用成功,则返回true,否则返回false。其参数dwTimeout表示该互斥对象变为有信号状态的时间。如果为INFINITE,则表示该函数将一直等待,直到互斥对象变为有信号状态。函数Unlock()的作用是解除对保护区域数据的锁定,并将互斥对象设置为有信号状态。如果该函数调用成功,则返回

36、true,否则返回false。其参数lCount是默认参数,用户在使用时可以不为其指定值。参数lpPrevCount也是默认参数,默认为NULL。例如,用户在程序中使用CMutex类创建互斥对象实现线程同步。代码如下:在程序中,用户首先定义了CMutex类的全局对象,然后创建两个线程并在线程函数中调用函数Lock()和Unlock()实现线程同步。该程序的运行结果如图3.9所示。图3.9 使用CMutex类实现线程同步以上程序实现了在同一进程中的线程同步,但是在前面的内容中向用户介绍过互斥对象还可以在进程之间使用。如果用户在进程中通过创建互斥对象实现程序实例的唯一运行,代码如下:在代码中,用户

37、首先创建互斥对象,然后使用函数GetLastError()获取错误信息。如果获取到的错误信息是ERROR_ALREADY_EXISTS,则说明程序已经有一个实例在运行了。该程序的运行结果如图3.10所示。图3.10 实例程序运行唯一性在本小节中,主要向用户介绍了使用API函数和CMutex类创建互斥对象以及使用互斥对象实现线程同步的方法。3.3 进程间通信进程间通信是指在系统中两个或多个进程之间通过第三方进行数据共享。用户在实际编程中,除了可以使用套接字进行网络通信以外,还可以使用进程间的通信方式实现网络通信。例如,邮槽、命名管道等。在Windows操作系统中,当每个进程启动时,系统都会为其分

38、配大约4GB的私有地址空间。由于每个进程的地址空间是私有的,所以进程之间不能互相访问对方的数据。但是,在Windows操作系统中已经为用户提供了多种进程通信机制,如:邮槽、匿名管道等。在本节中,将主要向用户介绍这些通信机制的用法以及实现方法等。3.3.1 邮槽邮槽是Windows系统提供的一种单向通信的机制。即进程中的一方只能写入或读取数据,而另一方则只能读取或写入数据。通过邮槽,用户可以实现一对多或跨网络的进程之间的通信。但是,邮槽能传输的数据非常小,一般在400KB左右。如果用户操作的数据过大,可能会导致邮槽不能正常工作。1创建邮槽用户在实际编程时,可以使用Windows邮槽实现进程间通信

39、。但是,用户必须首先创建邮槽。在Windows操作系统中,用户可以通过函数CreateMailslot()创建邮槽。该函数原型如下:HANDLE CreateMailslot(LPCTSTR lpName,DWORD nMaxMessageSize,DWORD lReadTimeout,LPSECURITY_ATTRIBUTES lpSecurityAttributes);该函数的作用是创建邮槽并返回该邮槽的句柄。如果该函数调用成功,将返回创建邮槽的句柄。否则,函数将返回INVALID_HANDLE_VALUE,表示创建邮槽失败。其参数及意义如下:参数lpName表示邮槽的名称。邮槽名称的格式

40、为“.mailslotname”。其中,name表示邮槽的名称。用户在VC中使用该参数时,应该将其指定为“.mailslot name”。如果用户是在不同的主机上运行程序,则需要将名称字符串中的“.”换成对方主机名称。参数nMaxMessageSize指定将通过邮槽发送或接收的消息大小的最大值。用户在实际编程时,一般将该参数设置为0,表示消息的大小为任意值。参数lReadTimeout表示程序读取操作的超时时间。如果该参数值为0,则当邮槽中没有任何消息时,该函数将立即返回。如果该参数值为MAILSLOT_WAIT_ FOREVER,则表示该函数将等待,直到邮槽中有消息,函数才会返回。参数lpS

41、ecurityAttributes是指向结构体SECURITY_ATTRIBUTES的指针,表示邮槽的安全属性。一般情况下,用户将该参数值指定为NULL,表示邮槽使用默认的安全属性。一般情况下,函数CreateMailslot()常被使用在进程通信的服务器方。在客户端则使用函数CreateFile()打开指定的邮槽之后,再进行相关的操作。注意:在本节中,将通过邮槽读取数据的通信一方称为服务器,而通过邮槽写入数据的一方称为客户端。例如,用户分别在服务器和客户端上创建邮槽和打开邮槽。服务器端创建邮槽的代码如下:用户首先在服务器方创建邮槽“.mailslotmysolt”。然后,再在客户端创建并打开

42、与该邮槽相关联的文件。注意:如果用户需要在程序中既能读取数据又能写入数据,则只需要在程序中同时实现服务器与客户端的功能即可。2操作邮槽用户对邮槽进行操作包括将数据写入邮槽和从邮槽中读取数据等。在实际编程时,用户操作邮槽与操作文件一样,都是通过调用函数ReadFile()和WriteFile()进行读写操作。例如,用户在服务器方通过邮槽读取数据,而在客户端通过邮槽写入数据。代码如下:在以上代码中,用户实现了简单的邮槽操作。用户在程序中使用完邮槽之后,必须调用函数CloseHandle()将创建的邮槽关闭。3邮槽实例首先,在VC中创建一个基于控制台程序的窗口工程,名称为“邮槽实例”。然后,在该工程

43、中添加一个C+源文件,名称修改为“服务器”并添加代码。邮槽服务器代码如下:将上面的代码编译后生成邮槽服务器程序。再在工程中添加一个C+源文件,名称修改为“客户端”并添加代码。客户端代码如下:当用户实现了邮槽服务器和邮槽客户端的相关功能,便可以编译并运行邮槽服务器和邮槽客户端,如图3.11所示。图3.11 邮槽实例程序运行结果注意:在邮槽实例中,用户必须首先打开服务器程序创建邮槽。然后,再使用客户端打开邮槽写入数据。如果用户将两个程序打开的顺序弄反,则会导致程序功能发生错误。3.3.2 命名管道命名管道是一种不但能在同一机器上实现两个进程通信,还能在网络中不同机器上的两个进程之间通信的机制。与邮

44、槽不同,命名管道传输数据是采取基于连接并且可靠的传输方式,所以命名管道传输数据只能一对一进行传输。在本节中,将主要向用户介绍命名管道的使用方法。1创建命名管道用户创建命名管道可以调用函数CreateNamedPipe()进行创建。该函数原型如下:HANDLE CreateNamedPipe(LPCTSTR lpName,DWORD dwOpenMode,DWORD dwPipeMode,DWORD nMaxInstances,DWORD nOutBufferSize,DWORD nInBufferSize,DWORD nDefaultTimeOut,LPSECURITY_ATTRIBUTES

45、lpSecurityAttributes );如果该函数调用成功,则返回创建的命名管道句柄。否则,该函数返回INVALID_ HANDLE_VALUE。各参数及其意义如下:参数lpName表示创建的命名管道名称。该名称格式为“.pipepipename”。但是,用户在实际编程时,应该将该名称修改为“.pipepipename”。如果用户希望在不同计算机的两个进程之间进行通信,则需要将名称字符串中的符号“.”修改为远程计算机的名称即可。参数dwOpenMode表示名管道的打开模式,包括访问模式、管道句柄的安全访问模式以及重叠方式等。该参数取值,如表3.3所示。表3.3 命名管道打开模式取值参数d

46、wPipeMode表示句柄管道的类型、读取以及等待方式。该参数的具体取值,如表3.4所示。模模 式式 取取 值意意 义PIPE_ACCESS_DUPLEX指定双向模式,即服指定双向模式,即服务器与客器与客户端都可以从命名管道中端都可以从命名管道中读取或写入数据取或写入数据PIPE_ACCESS_INBOUND命命名名管管道道的的数数据据只只能能从从客客户端端到到服服务器器,即即用用户指指定定该模模式式表表示示服服务器器只只能能读取取数数据而客据而客户端只能写入数据端只能写入数据PIPE_ACCESS_OUTBOUND命命名名管管道道的的数数据据只只能能从从服服务器器到到客客户端端,即即用用户指

47、指定定该模模式式表表示示服服务器器只只能能写写入入数数据而客据而客户端只能端只能读取数据取数据FILE_FLAG_WRITE_THROUGH允允许写写直直通通模模式式。当当用用户指指定定该值时,写写入入数数据据的的一一方方要要等等到到写写入入的的数数据据到到达达另另一一方的数据方的数据缓冲区之后,才会成功返回冲区之后,才会成功返回FILE_FLAG_OVERLAPPED允允许使使用用重重叠叠模模式式。采采用用该模模式式可可以以使使一一些些耗耗费时间的的操操作作在在后后台台执行行,在在重重叠叠模模式下,一个式下,一个线程可以在多个管道程可以在多个管道实例上同例上同时处理理输入与入与输出操作出操作

48、WRITE_DAC调用用线程程对命名管道的任意命名管道的任意访问控制列表都可以控制列表都可以进行写入操作行写入操作WRITE_OWNER调用者用者对命名管道的所有者可以命名管道的所有者可以进行写入操作行写入操作ACCESS_SYSTEM_SECURITY调用者用者对命名管道的安全命名管道的安全访问控制列表可以控制列表可以进行写入操作行写入操作表3.4 管道句柄、读取以及等待方式参数nMaxInstances表示管道能够创建实例的最大数目。其取值范围在1PIPE_ UNLIMITED_INSTANCES。如果将该值设为PIPE_UNLIMITED_INSTANCES,则创建的管道实例数目仅限于操

49、作系统。注意:一个客户端只能与一个管道实例进行通信。参数nOutBufferSize表示输出缓冲区的大小。参数nInBufferSize表示输入缓冲区的大小。参数nDefaultTimeOut表示超时值,使用同一管道的不同实例必须将该参数取同样的超时值。参数lpSecurityAttributes是指向结构体SECURITY_ATTRIBUTES的指针,表示命名管道的安全属性。取取 值意意 义PIPE_TYPE_BYTE数据以字数据以字节流的形式写入管道流的形式写入管道PIPE_TYPE_MESSAGE数据以消息流的形式写入管道数据以消息流的形式写入管道PIPE_READMODE_BYTE以字

50、以字节流的形式从管道中流的形式从管道中读取数据取数据PIPE_READMODE_MESSAGE以消息流的形式从管道中以消息流的形式从管道中读取数据取数据PIPE_WAIT允允许阻塞模式阻塞模式PIPE_NOWAIT允允许非阻塞方式非阻塞方式例如,用户使用该函数创建一个命名管道。代码如下:./省略部分代码HANDLE hpip;hpip=CreateNamedPipe(.pipepipename,PIPE_ACCESS_DUPLEX,PIPE_TYPE_BYTE,PIPE_UNLIMITED_INSTANCES,1024,1024,0,NULL);/创建命名管道./省略部分代码2连接命名管道当用

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 应用文书 > 汇报体会

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知得利文库网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号-8 |  经营许可证:黑B2-20190332号 |   黑公网安备:91230400333293403D

© 2020-2023 www.deliwenku.com 得利文库. All Rights Reserved 黑龙江转换宝科技有限公司 

黑龙江省互联网违法和不良信息举报
举报电话:0468-3380021 邮箱:hgswwxb@163.com