《vcMFC基础教程共443页课件》由会员分享,可在线阅读,更多相关《vcMFC基础教程共443页课件(443页珍藏版)》请在金锄头文库上搜索。
1、vc+MFC基础教程1.2 Visual C+6.0界面介绍为了便于说明,首先创建一个IDE的MFC项目(具体创建步骤,后面会详细介绍),如下图所示。1.2 Visual C+6.0界面介绍从上图中可以看出,Visual C+的界面被分成了7部分。上面依次标题栏、菜单栏和工具栏。中间左侧部分是工作区窗口,右侧部分是编辑区。最下方是输出窗口和状态栏。1.2.1 工作区窗口和输出窗口工作区窗口和输出窗口是在一个程序编译时使用最多的停靠式窗口。工作区窗口中显示了当前程序中的所有类、资源和文件信息。工作区窗口分为3个部分:Class View(类视图)、Resource View(资源视图)和File
2、 View(文件视图)。Class View:显示当前工作区中所有工程定义的C+类。双击某个类名,Visual C+会自动打开这个类的文件,并将编辑区定位到该类的定义处。双击类中的成员函数和变量,编辑区则定位到该成员函数或变量的定义处。1.2.1 工作区窗口和输出窗口Resource View:显示当前工作区中所有的资源。这些资源包括快捷键、对话框、图标、菜单、字符条编辑器、工具栏和版本信息。双击其中的ID号,会显示相应的资源信息。File View:显示属于当前工程的所有文件,包括C/C+源文件、头文件、资源文件等。输出窗口与工作区一样,分为多个选择卡。其中最常用的选项卡被放在了最外面,分别
3、为Build、Debug、Find in Files 1和Find in Files 2。1.2.1 工作区窗口和输出窗口Build:Build显示工程在创建中,经过的每一个步骤及相应信息。如果出现编译链接错误,那么出现错误的文件、原因、行号等内容会显示在Build中。双击该错误信息行,编辑区则定位在该错误出现的行。Debug:工程通过编译后,运行调试版本时,Debug选项卡中会显示具体的调试信息。1.2.1 工作区窗口和输出窗口Find in Files 1和Find in Files 2:两个选项卡的作用相同,用于显示从多个文件中查找字符串的结果。当用户想要查看某个函数或变量出现在哪些文件
4、中,单击【Edit】|【Find in Files】命令,弹出【Find in Files】对话框,如下图所示。在【Find in Files】对话框中的【Find what】后的编辑框中,输入想要查找的内容,单击【Find】按钮即可。查找到的内容会输出到Find in Files选项卡中。1.2.2 菜单栏和工具栏菜单栏位于集成开发环境的顶部。菜单栏由9个菜单项组成:File(文件)、Edit(编辑)、View(视图)、Insert(插入)、Project(工程)、Build(编译)、Tools(工具)、Windows(窗口)、Help(帮助)。每一个菜单项都有一个下拉式菜单,其中的菜单项用
5、于完成特定的功能。工具栏位于菜单栏的下面。工具栏可以称作是美化的菜单栏,其由许多按钮构成。其中的按钮用于完成特定的功能。工具栏可以任意拖动,也可以成为一个浮动窗口。1.2.3 编辑区在Visual C+中,编写应用程序代码的位置就是编辑区。该编辑区的功能十分强大,智能化程度也非常高。在编辑区内,除了能编写C/C+语言外,还能编写SQL、HTML和VBScript等其他编程语言。如下图所示。1.2.4 联机帮助Visual C+6.0不像其他集成开发环境一样把帮助系统集成在开发环境内部,而是提供了一个专门为Visual C+设计的MSDN类库。MSDN虽然是一个独立的帮助系统,但却能很好地和Vi
6、sual C+6.0集成在一起。MSDN的使用方式有以下几种:单击【Help】|【Contents】命令;单击【Help】|【Search】命令;单击【Help】|【Index】命令;按下【F2】键。通过上述几种命令方式,即可运行MSDN。1.2.4 Visual C+中的文件扩展名打开程序Hello World所在的文件夹,看到该文件夹自动生成了许多扩展名不同的文件,如下图所示。1.2.6 Visual C+中的文件扩展名了解这些不同的扩展名文件,对于理解Visual C+6.0如何组织和管理项目文件是很有必要的。有关这些文件扩展名及其说明,如下表所示。第2章 MFC与应用程序框架在Visu
7、al C+集成开发环境下,使用微软基础类库MFC,可以开发出功能强大的Windows应用程序。另外,通过MFC AppWizard自动生成的MFC应用程序框架,还可以很方便地开发自己想要实现的功能。本章将先介绍有关MFC的基础知识,然后对MFC应用程序框架作具体介绍。2.1 微软基础类库MFCMFC是一种重要的编程方法,它是微软公司的特定的应用程序包装接口。本节将讲解MFC概述及其类库结构。2.1.1 MFC概述MFC的英文全称是Microsoft Foundation Classes,即微软的基础类库。MFC的本质就是一个包含了许多微软公司已经定义好的对象的类库。虽然开发人员要编写的程序在功
8、能上各有不同,但是从结构上讲,都可以化分为对用户界面的设计、对文件的操作、对数据库的访问及对多媒体的使用等一些最主要的方面。这一点正是微软提出MFC类库最重要的原因。在MFC类库中,大约有200个类。在进行程序设计时,只需简单地调用已有的类及类中的方法即可。另外,还可以利用“继承”方法从已有类中派生出自己想要的类。这时,派生出来的类不但拥有父类中的方法和属性,还可以根据自己的需求,自定义一些特殊的属性和方法,使得派生类功能更加强大。MFC有较好的移植性,可应用于众多平台。2.1.2 MFC类库结构MFC中类可划分为基类、应用程序结构类、窗口类、OLE类、数据库类等10大类,而且在其中的一些大类
9、的基础上又派生出许多子类。MFC的类库结构的层次图如下图所示。2.1.2 MFC类库结构从上图中可以看出,CObject是一个原始基类。绝大多数MFC类的最终基类都是CObject。原始基类下面,主要包括以下几种类:MFC应用程序结构类,窗口、对话框和控件类,输出(设备文本)和绘图类,简单数据类型类,数组、列表和映射类,文件和数据库类,Internet和网络类,OLE类以及高度和异常类。MFC的应用程序结构类分为CWinApp和CWinThread。使用MFC创建的每一个应用程序都包含一个由类CWinApp派生而来的应用程序对象。该对象是一个全局对象。应用程序对象主要用于处理应用程序的初始化,
10、同时也处理应用程序事件的消息循环。CCmdTarget和CCmdUI为MFC中常用的有关发送命令的类。CDocument为MFC中常用的应用程序文档的基类。CDocTemplate为文档模版类,通常是应用程序的单文档或多文档的基类。CView类是常用的视图类。2.2 MFC应用程序框架分析在前面介绍过如何创建一个基于单文档的应用程序。对于如何选择性地创建基于多文档或是基于对话框的应用程序,将会在后续章节详细介绍。本节主要对MFC应用程序框进行简单的概括,使读者了解MFC应用程序框架的结构与工作机制。2.2.1 入口函数入口函数就是指一个程序的入口点。WinMain函数是Windows程序的入口
11、函数。为了便于讲解,首先要创建一个MFC应用程序,程序名命名为sample0201。具体创建步骤不再详细介绍。从创建好的sample0201程序中,并不能找到WinMain函数。这是因为典型Windows程序的大部分初始化工作都是标准化的,因此把WinMain函数隐藏在应用程序的框架中。当一个程序编译时,会自动将该函数链接到程序中。在计算机中找到Visual C+的安装目录,笔者安装在F盘,则按照下面这个路径依次打开文件夹,“F:Program FilesMicrosoft Visual StudioVC98MFCSRC”。打开后,会发现一个源文件“WinMain.cpp”。该文件中则定义了M
12、FC应用程序的入口函数AfxWinMain。文件的源代码如下:2.2.1 入口函数2.2.1 入口函数当一个应用程序启动时,会自动调用应用程序框架内部的AfxWinMain函数。根据其前缀Afx就知道WinMain是一个全局的MFC函数。从上述代码中可以看出,WinMain函数会查找该应用程序的一个全局构造对象。该对象是由CWinApp的派生类创建,因此有程序启动时,它就被创建好了。然后WinMain对该应用程序进行初始化,在此过程调用的是该程序全局构造对象的InitApplication()和InitInstance()函数。完成初始化后,WinMain调用Run()函数,运行应用程序的消息
13、循环。最后结束应用程序时,WinMain调用AfxWinTerm()函数,做一些清理工作。2.2.2 InitInstance()函数InitInstance()函数的作用是初始化程序。每次启动一个应用程序时,Winmain函数会自动调用InitInstance()函数。打开创建的程序sample0201,在该程序的CSample0201App类中,可以看到该程序对InitInstance()函数进行了重载。该重载代码如下:2.2.2 InitInstance()函数从上述代码中可以看出,在ShowWindow和UpdateWindos之前,程序要做两个动作,一个是注册窗口类,另一个是构建窗口
14、类。InitInstance()函数规定了生成的应用程序是基于单文档的、基于多文档的或是基于对话框的。因此在CWinApp中必须重载InitInstance()函数。2.2.3 应用类Run()函数与查找WinMain函数类似,在Visual C+的安装目录下,按照下面这个路径依次打开文件夹,“F:Program FilesMicrosoft Visual StudioVC98MFCSRC”。打开后,会发现一个源文件“THRDCORE.CPP”。该文件中定义了应用类Run()函数,源代码如下:2.2.3 应用类Run()函数从上述代码中可以看出,如果消息队列没有消息,则调用OnIdle( )函
15、数,并递增iIdleCount计数标志,该计数标志表示在两次消息处理过程只共调用了多少次OnIdle()函数。bIdle是消息队列空闲的标志,当消息队列有消息时,则调用PumpMessage()函数,进行消息翻译和消息派发。其中PreTranslateMessage(&m_msgCur)对消息进行翻译,DispatchMessage(&m_msgCur)把消息m_madCur发送到应用程序主框架窗口。注意:在应用程序sample0201的CSample0201App的Run函数继承了CWinApp的虚函数Run()。而CWinApp的Run()函数调用了CWinThread的Run()函数。2
16、.2.4 消息映射表当MFC应用程序类中的Run()函数把消息交给主窗口的窗口函数后,窗口函数将如何处理这些消息。在Win32程序中,处理窗口消息的窗口函数WinProc()函数通过switchcase结构对消息进行判断并分别进行处理。但在MFC应用程序的主窗口类对消息的处理并没有沿袭Win32程序的做法。MFC应用程序中进行消息处理,是为每一个要处理的消息添加一个消息处理函数。这种定义消息和消息处理函数的对照表,称为消息映射表。MFC的消息映射表采用映射宏的方式,将消息和消息处理函数一一对应起来。以应用程序的主框架为例,在类的声明文件MainFrame.h中添加声明消息映射的宏。2.2.4
17、消息映射表在类的实现文件MainFrame.cpp中,添加消息映射宏的定义语句。其中,ID_MY_MESSAGE为自定义的菜单项命令ID号,OnMymessage()为响应菜单命令的成员函数。2.2.5 MFC消息分类MFC应用程序对消息的描述一般分为3类:窗口消息、命令消息和控件消息。1窗口消息窗口消息一般与创建窗口、绘制窗口、移动窗口和销毁窗口等操作有关。另外,当使用鼠标、键盘等与操作窗口有关的动作时,也会产生窗口消息。窗口消息的一般的表示形式是以“WM_”开头的消息。常见的窗口消息如下所述。WM_CHAR:使用键盘时产生的消息。其对应的消息处理函数为OnChar(UINT nChar,
18、UINT nRepCnt, UINT nFlags)。WM_CREATE:创建窗口时产生的消息,用于窗口的初始化。其对应的消息处理函数为OnCreate(LPCREATESTRUCT lpCreateStruct)。WM_PAINT:通知窗口对自身进行绘制。一般在移动窗口、改变窗口大小、绘制图形时使用。其对应的消息处理函数为OnPaint()。1窗口消息WM_LBUTTONDOWM:使用鼠标左键时产生的消息。通知窗口单击了左键。其对应的消息处理函数为OnLButtonDown(UINT nFlags, CPoint point)。WM_MOUSEMOVE:移动鼠标时产生的消息。其对应的消息处理
19、函数为OnMouseMove(UINT nFlags, CPoint point)。WM_CLOSE:关闭窗口时产生的消息。其对应的消息处理函数为OnClose()。WM_DESTROY:销毁窗口时产生的消息。其对应的消息处理函数为OnDestroy()。2命令消息命令消息一般与处理用户的某个请求或执行用户的某个命令有关。一般通过选择菜单项、单击工具栏按钮和按加速键产生命令消息。在MFC应用程序中,凡是从基类CCmdTarget派生出来的子类,都能处理命令消息。另外,文档类、视图类和应用程序类都能处理命令消息。创建命令消息时,可以使用MFC Class Wizard建立消息映射和消息处理函数之
20、间的关系。例如,应用程序类发出文件打开命令,假设打开文件对应的菜单资源ID为ID_FILE_IPEN,则其命令消息如下:3控件消息普通的控件都是子窗口,因为其都继承自CWnd。它们通过向其父窗口(一般为对话框)发关消息,响应用户的动作(如移动鼠标,单击等)。控件消息一般是由按钮(BN_)、编辑框(EN_)、下拉列表框(LBN_)和组合框(CBN_)等控件产生的。其消息映射宏是在消息名前加上ON_,例如:第3章 C+语言基础要使用Visual C+进行Windows应用程序的开发,就要掌握面向对象的思想和C+语言。本章先讲述一个简单的C+程序,然后根据这个程序,向读者介绍C+中语言基础。3.2
21、C+的基本数据类型及数据数据类型是对数据的一种抽象描述。在计算机程序中能操作的数据有很多种,不同的数据所需要的存储空间有所不同。将数据按照类型进行分类,有助于程序员对于存储空间的分配。本节将具体介绍有关C+中的数据及其所属的数据类型。3.2.2 变量变量是一种特殊的标识符,在变量中可以存储数据。变量中存储的数据可以根据程序的需要而改变,因此称为变量。1定义变量在C+中,使用一个变量必须先定义该变量。C+中定义变量的语法代码如下:定义一个变量需要说明两点,一是变量的类型,二是变量的名称。其中,变量的类型是C+中的数据类型。变量名是用户为变量起的名称。3.2.2 变量C+的变量名由字符及数字等组成
22、。变量名必须满足以下几个条件。变量名只能由字母、数字和下划线(_)组成。变量名必须以字母或下划线开头。变量名不能包含空白字符(换行符、空格和制表符称为空白字符)。变量名不能与保留字名相同。变量名区分大小写。3.2.2 变量2变量赋值如果想要使用一个变量,就要为其进行赋值。如果没有对定义的变量赋值,Visual C+会为该变量默认一个值。例如,如果是一个int类型的变量且没有赋值,Visual C+将默认其值为0。C+中为变量赋值的方法有两种:一种是在定义变量的同时赋值,另一种是在定义变量后赋值。在定义变量的同时赋值,代码如下:在定义变量后赋值,代码如下:3.2.3常量常量与变量相反,是一个不随
23、时间和程序变化而变化的值。C+中,常量的命名规则和变量的大体相同。不同的是,常量名称中的字母都为大写。C+中定义符号常量的语法代码如下:例如,在计算圆形面积的时候,经常用到PI。为了避免重复地输入PI的实际取值,而用下面的形式声明PI的取值。这样,在程序中编译时,会将程序中出现的所有字符串PI全部置换成3.14。如果想要修改程序中PI的值,只需在头文件处修改,全部PI的取值都会发生变化。3.2.3常量C+中定义静态常量的语法代码如下:在C+中,同声符号常量一样,在声明静态常量时,也要对其进行初始化,代码如下:注意:在符号常量中,PI没有类型,不占有存储单元,且容易出错。而在Const常量中,P
24、I有数据类型,并且占有存储单元,有地址,因此可以使用指针指向它。3.3 C+的运算符及表达式运算符和表达式是一种程序语言的基础。运算符的作用是操作变量或表达式。C+中的运算符包括赋值运算符、算术运算符、逻辑运算符、关系运算符、位运算符、逗号运算符、条件运算符等。本节将介绍这些运算符及其所组成的表达式。3.3.1 表达式表达式是C+程序中不可缺少的一部分。表达式是由运算符、操作数(变量、常量或函数等)和标点符号,按照一定规则组成的一个有意义的语句。例如:3.3.2 运算符C+中的运算符就是一种符号,该符号可以用于处理数据。平时有数学计算中所使用的“+”、“-”、“”、“”都属于运算符。只是这些运
25、算符在C+中的表现形式可能与日常生活中有所不同。下面将对C+中的运算符作具体介绍。1赋值运算符赋值运算符是用于为变量或常量指定数值的运算体符。其操作符号为“”,示例代码如下:上述表达式的意义是,把b的值赋值给a。其中,b可以是一个单纯的变量,也可以是一个表达式。3.3.2 运算符2算术运算符算术运算符是用于进行数学运算的运算符。例如,加、减、乘、除等就是算术运算符。操作完成后,返回一个数字型的值。算术运算符包括加法运算符(+)、减法运算符(-)、乘法运算符(*)、除法运算符(/)、模运算符()。上述算术运算符都是二元运算符,该运算符两端的数据必须是数字。3.3.2 运算符3逻辑运算符逻辑运算符
26、,即用于处理逻辑值的运算符。逻辑运算符通常用在条件判断语句或循环语句中,如if、while语句等。C+中的逻辑运算符包括逻辑与运算符(&)、逻辑或运算符(|)、逻辑非运算符(!)。由逻辑运算符构成的表达式,称为逻辑表达式。逻辑表达式的返回值为逻辑值(true或false),一般情况下,1代表true,0代表false。逻辑与运算符可以进行与操作,其操作方法为:如果逻辑与运算符前的数为false(或是可以得出false的逻辑表达式),则返回false,否则返回true;当逻辑与运算符前后两个数都为true时,才返回true。逻辑或运算符可以进行或操作,其操作方法为:只要逻辑或运算符前后的数据中有
27、一个为true(或是可以得出true的逻辑表达式),则返回true;当逻辑或运算符前后两个数都为false时,才返回false。逻辑非运算符要求要操作的数据必须是逻辑值,或是能够转换成逻辑值的逻辑表达式。逻辑非运算符可以进行非操作,其操作方法为:如果要操作的数据为true,则返回false;如果要操作的数据为false,则返回true。3.3.2 运算符4关系运算符关系运算符,即用于比较两个数据关系大小的运算符,并根据比较的结果返回一个逻辑值。关系运算符包括大于运算符()、大于等于运算符(=)、小于运算符()、小于等于运算符(”一起使用。使用cin的语法代码如下:例如,想要从键盘输入一些数据,
28、将使用下述代码:如果想要一次性输入多个数据,不是使用逗号作为分隔符,而应该用“”分隔,应该写成:3.4.1 C+的输入输出2输出语句C+的输出语句用cout表示。其中,cout必须和“”一起使用,使用cout的语法代码如下:例如,想要从键盘输出一些数据,将使用下述代码:如果想要一次性的输出多个数据,同样不是使用逗号作为分隔符,而是每项数据之间用“Close();/关闭记录集m_pConnection-Close();/关闭连接第13章 多线程编程Windows是一个多任务的操作系统。多线程运行可以提高系统的运行效率,因此使用比较广泛。本章将对进程与线程进行简单介绍。内容包括:进程与线程、线程的
29、使用、线程的终止与通信等。通过本章的学习,读者可以在程序中调用其他应用程序,可以实现进程间的通信,可以实现线程的同步。下面对多线程技术进行详细介绍。 13.1 进程与线程进程(Process)是应用程序的执行实例。每个进行都可以访问进程中的所有资源。一个进程是由一个或多个进行、代码、数据和应用程序在内存中的其他资源组成。当一个应用程序启动,相应的一个进程进行也会启动,这个进行称为父进程。一个应用程序还可以启动其他进程,被启动的其他进程称为子进程。想要查看进程,可以打开Windows的任务管理器。单击【进程】标签,在【进程】选择卡中可以查看当前系统中的各个进程,如右图所示。进程是资源的分配单位,
30、每个进程都拥有自己的地址空间和上下文环境。13.1 进程与线程进程是线程的容器,线程是进程内部的一个执行单元。一个进程可以有一个或是多个线程,但这些线程仅生存于该进程中。也就是说,线程是在它所属的进程地址空间里执行代码,并在进程的地址空间对数据进行操作。线程用于描述进程中的运行路径。当一个进程被启动时,系统就要创建一个主线程。该主线程是应用程序需要的唯一线程,但进程也可以创建其他线程来完善进程的其他操作。13.2 线程的分类在MFC中,线程分为两类:用户界面线程(User-Interface Thread)和工作者线程(Worker Thread)。用户界面线程:该线程通常用来处理用户的输入并
31、响应用户生成的事件和消息。用户界面线程是从CWinThread类派生而来的。在MFC中,CWinApp对象就是一个用户界面线程。通常情况下,用户界面线程都是主线程。当应用程序启动时,用户界面线程随之启动。当应用程序退出时,用户界面线程也会随之终止。工作者线程:该线程通常不需要用户进行输入,由后台进行处理。因此,工作者线程又被称为后台线程。工作者线程和用户界面线程的区别在于,工作者线程不用从CWinThread类派生。13.3 线程类(CWinThread)在MFC上,CWinThread类封装了对线程的操作。下面对CWinThtead类中的成员函数和成员变量作具体介绍。其中,CWinThrea
32、d类中常用的函数如下:(1)调用CreateThread()函数可以创建一个新的线程,该函数的原型如下:dwCreateFlags:表示线程创建的标志。nStackSize:表示线程堆栈的大小。lpSecurityAttrs:表示线程的安全属性。13.3 线程类(CWinThread)(2)调用SetThreadPriority()函数可以设置线程的优先级,该函数的原型如下:其中,nPriority参数定义了线程的优先级。其取值如下表所示。13.3 线程类(CWinThread)(3)调用GetThreadPriority()函数可以获取线程的优先级,该函数原型如下:(4)调用PostThre
33、adMessage()函数可以向另一个CWinThread对象发送消息,该函数的原型如下:message:表示用户定义消息的标识。wParam:表示消息的第一个参数。lParam:表示消息的第二个参数。13.3 线程类(CWinThread)(5)调用SuspendThread()函数可以将线程的挂起计数加1,当线程的挂起计数大于0时,该线程将暂停执行,称之为挂起状态。该函数的原型如下:(6)调用ResumeThread()函数可以将线程的挂起计数减1。当线程的挂起计数减少到0时,恢复线程的执行。该函数的原型如下:13.3 线程类(CWinThread)在CWinThread类中,还需要重载一
34、些函数来完成对线程的操作。(1)重载InitInstance()函数用于控制用户界面线程实例的初始化工作,该函数的原型如下:(2)重载ExitInstance()函数用于控制清理操作,该函数的原型如下:(3)重载OnIdle()函数用于控制线程空闲处理操作,该函数的原型如下:lCount:表示计数器。此外,CWinThread类中有以下几个重要的成员变量。m_hThread:表示当前线程的句柄。m_nThreadID:表示当前线程的ID。m_pMainWnd:表示指向应用程序主窗口的指针。13.4 线程的使用在MFC中,创建一个新的线程时,可以使用全局函数AfxBeginThread()。Af
35、xBeginThread()有两种原型,分别用于创建用户界面线程和工作者线程。13.4.1 启用用户界面线程当启用用户界面线程时,调用AfxBeginThread()的原型如下:pThreadClass:表示用户界面线程对象运程时类结构指针。pParam:表示传递给控制函数的参数。nPriority:表示线程的优先级。nStackSize:表示线程堆栈的大小。dwCreateFlags:表示线程创建的标志。lpSecurityAttrs:表示安全属性(SECURITY_ATTRIBUTES)结构指针。13.4.1 启用用户界面线程另外,启用用户界面线程还可以先创建一个CWinThread类的对
36、象,然后调用CreateThread()函数。代码如下:13.4.2 启用工作者线程当启用工作者线程时,调用AfxBeginThread()的原型如下:pfnThreadProc:表示工作者线程的控制函数指针。pParam:表示传递给控制函数的参数。nPriority:表示线程的优先级。nStackSize:表示线程堆栈的大小。dwCreateFlags:表示线程创建的标志。lpSecurityAttrs:表示安全属性(SECURITY_ATTRIBUTES)结构指针。注意:函数的返回值是一个线程指针。一般情况下,需要保存该指针,以便于以后根据该指针来终止该线程。13.4.3 用户界面线程在实
37、际应用过程中,有时需要在线程中创建对话框。此时,可以使用用户界面线程来实现。在MFC中,创建用户界面线程的具体步骤如下:(1)从CWinThread类派生一个子类。(2)在子类的InitInstance()函数中设置线程的主窗口。(3)调用AfxBeginThread()函数创建用户界面线程。13.4.3 用户界面线程【示例13-1】创建用户界面线程。在一个基于对话框的MFC应用程序中,创建一个用户界面线程用来启动另外一个对话框。(1)创建一个基于对话框的MFC应用程序sample1301。(2)设计对话框资源,如下图所示。13.4.3 用户界面线程(3)单击【Insert】|【Resourc
38、e】命令,弹出【Insert Resource】(插入资源)对话框。选择Dialog结点,如左图所示。(4)设计新插入的对话框资源,如右图所示。13.4.3 用户界面线程(5)双击新添加的对话框资源,为其添加相应的类CSubDlg,如左图所示。(6)单击【Insert】|【New Class】命令,弹出【New Class】对话框。添加一个CWinThread类的子类CSubThread,如右图所示。13.4.3 用户界面线程(7)添加并修改CSubThread类中的数据成员和成员函数。其中,SubThread.h文件中的代码如下:SubThread.cpp文件中的代码如下:13.4.3 用户
39、界面线程(8)在对话框类CSample1301Dlg的【创建用户界面线程】按钮中添加如下代码:在【取消】按钮中添加如下代码:13.4.3 用户界面线程(9)运行程序sample1301,弹出主对话框。单击【创建用户界面线程】按钮,弹出【用户界面线程】对话框。当运行程序sample1301的同时,可以打开系统的任务管理器进行查看。当主对话框弹出时,进程中多了一个sample1301.exe进程。当弹出【用户界面线程】对话框时,没有相应的进程出现。从上述内容中可以看出,本例成功地创建了一个用户界面线程。13.4.4 工作者线程创建工作者线程不需要使用CWinThread类的派生类,而是需要实现控制
40、函数。在工作者线程的控制函数中,定义了线程的生命周期。当进入控制函数时,线程将被启动。而当退出函数时,线程将被终止。【示例13-2】创建工作者线程。在一个基于对话框的MFC应用程序中,创建工作者线程,并在对话框进行初始化时启用。(1)创建一个基于对话框的MFC应用程序sample1302。(2)设计对话框资源,如下图所示。13.4.4 工作者线程(3)添加成员变量。为两个ListBox控件添加成员变量,分别为m_list1和m_list2。其中,类别为Control,类型为CListBox。(4)为CSample1302Dlg类添加两个自定义函数,分别如下:(5)为控制函数添加代码。其中,Fi
41、rstThread()函数中添加的代码如下:SecondThread()函数中添加的代码如下:13.4.4 工作者线程(6)在对话框类CSample1302Dlg的OnInitDialog()函数中添加如下代码:(7)修改Sample1302Dlg.h文件中的函数声明,将线程函数声明为static函数。 13.4.4 工作者线程(8)运行程序sample1302,结果如图13.8所示。注意:如果不按照第(7)步骤对Sample1302Dlg.h文件进行修改,会出现如下错误提示:AfxBeginThread : none of the 2 overloads can convert parame
42、ter 1 from type unsigned int (void *)。13.5 线程的终止当一个线程终止时,关闭该线程所属的所有对象句柄,将该线程对象状态变为有信息号状态,并将该线程对象终止状态STILL_ACTIVE改为退出码。通常情况下,线程终止分为两种:正常终止和异常终止。13.5.1 正常终止线程对于用户界面线程而言,使其正常终止只需在用户界面线程中调用PostQuitMessage()即可。该函数的原型如下:nExitCode:表示线程的退出码。对于工作者线程而言,当控制函数正常执行到函数的结束点(return语句)后,该线程也就正常终止了。另外,用户也可以选择使用AfxEnd
43、Thread()函数来终止该线程。AfxEndThread()函数的原型如下:13.5.2 异常终止线程异常终止是指线程内部出现自身无法终止的情况,在其他线程中强行终止该线程。异常终止用于在紧急情况下安全退出控制。如果想要终止线程,可以使用TerminateThread()函数。该函数的原型如下:hThread:表示将要终止的线程句柄。该参数可以使用创建线程返回时返回的CWinThread从m_hThread成员变量中得到。dwExitCode:表示线程的退出码。13.5.3 线程的退出码根据线程的退出码,可以判断线程的执行情况。使用GetExitCode()函数可以获取工作者线程或用户界面线
44、程退出码。该函数的原型如下:hThread:表示线程句柄。该参数可以使用创建线程返回时返回的CWinThread从m_hThread成员变量中得到。lpExitCode:表示接收返回的终止状态的地址。13.5.3 线程的退出码如果线程没有被终止,则终止状态返回STILL_ACTIVE。如果线程已经被终止,则返回的终止状态可以是以下取值中的一个:在ExitThread()或TerminateThread()函数中指定的退出值。在线程函数的return语句中的返回值。线程所属进程中的退出值。13.5.3 线程的退出码想要获取线程的退出码,还要做一些其他工作。一般线程终止后,线程对象将被删除,用户不
45、能获取m_hThread句柄。想要解决这个问题,可以采用以下两个办法。(1)在创建线程后,设置线程对象的m_bAutoDelete成员变量为FALSE。这样,CWinThread对象在线程终止后仍将存在。在退出应用程序之前,还需要手动添加删除CWinThread线程对象的代码。(2)单独选择保存线程句柄。在创建线程后,使用Win32函数DuplicateHandle()将m_hThread复制到另一个句柄。这样,尽管对象被自动删除了,但使用复制的句仍然可以获取线程的退出码。注意:采用第二种方法时,在复制句柄之前,线程不能终止。最安全的方法是在创建线程进使用CREATE_SUSPENDED挂起,
46、复制好后,调用ResumeThread()函数恢复线程的执行。13.6 线程的通信通常情况下,在次线程是为主线程服务的,这就预示着主线程和次线程之间会进行及时的通信。下面对线程间的通信作简单介绍。13.6.1 通信原理在线程间进行通信的过程中,需要用到以下几个函数。其中,调用PostMessage()函数可以进行线程间的通信。该函数的原型如下:hWnd:表示消息发送的目的窗口句柄。Msg:表示将要发送的消息。wParam:表示消息的第1个参数。lParam:表示消息的第2个参数。13.6.1 通信原理调用PostThreadMessage()函数可以向某个线程发送消息,该函数的原型如下:idT
47、hread:表示线程标识。Msg:表示将要发送的消息。wParam:表示消息的第1个参数。lParam:表示消息的第2个参数。13.6.1 通信原理如果想要对消息进行外理,用户界面线程和工作者线程的处理方式不同。在用户界面线程中,有两种方式对消息进行处理。(1)使用消息映射宏ON_THREAD_MESSAGE。该宏的定义格式如下:message:表示消息标识ID。memberFxn:表示CWinThread消息处理函数的名称。(2)使用PreTranslateMessage()函数,直接对消息进行处理。该函数的原型如下:pMeg:表示包含消息的MSG结构指针。13.6.1 通信原理在工作者线程
48、中,可以使用GetMessage()函数对消息进行处理。该函数的原型如下:lpMsg:表示消息MSG结构指针。hWnd:表示窗口句柄。wMsgFilterMin:表示第1个消息。wMsgFilterMax:表示最后一个消息。13.6.2 用户界面线程通信下面通过一个实例来介绍用户界面线程间的通信。【示例13-3】实现用户界面线程间的通信。(1)创建一个基于对话框的MFC应用程序sample1303。(2)设计对话框资源,如下图所示。(3)添加成员变量。其中,从上到下,各文本框对应的成员变量分别为m_edit1、m_edit2和m_edit3。其中,m_edit1、m_edit2的类别为Valu
49、e,类型为int。m_edit3的类别为Control,类型为CEdit。13.6.2 用户界面线程通信(4)添加CWinThread类的派生类。单击【Insert】|【New Class】命令,弹出【New Class】对话框。添加一个CWinThread类的子类CWinThread,如图13.10所示。13.6.2 用户界面线程通信(5)在CompareThread.h文件中添加如下代码:在CompareThread.cpp文件中添加如下代码:(6)在CSample1303Dlg类中添加一条自定义消息,代码如下:13.6.2 用户界面线程通信(7)重载CCompareThread类的Pre
50、TranslateMessage()函数。右击CCompareThread类,在弹出的弹出式菜单中选择【Add Virtual Function】菜单项,弹出【New Virtual Override for Class CCompareThread】对话框。从左边的列表框中双击【PreTranslateMessage】选项,将其移动到右边的列表框中,如下图所示。13.6.2 用户界面线程通信(8)单击【Add and Edit】按钮,进入编辑区。在PreTranslateMessage()函数中添加代码如下:13.6.2 用户界面线程通信(9)为对话框类CSample1303Dlg添加一个成
51、员变量,如下图所示。该变量用于保存线程的标识。(10)在对话框类CSample1303Dlg的初始化函数中创建并启动用户界面线程,代码如下:13.6.2 用户界面线程通信(11)在【比较】按钮的消息响应函数中添加如下代码:(12)运行程序sample1303,结果如下图所示。在运行程序时,单击【比较】按钮,并没有直接对a、b的值进行比较,而是发送一条WM_CPMPARE的消息,交由CCompareThread类的PreTranslateMessage()函数进行处理。13.7 线程的同步在编写多线程应用程序时,经常会出现线程间同步访问共享资源的情况。多个线程同时访问同一个共享资源将可能发生无法
52、预知的错误。例如,一个线程正在更新数据集,而同时另外一个线程正在读取数据。那么,第二个线程只会读取一部分正确的数据。为了解决这个问题,Visual C+6.0提供了同步类。同步类包括同步对象和同步访问对象。其中,同步对象分为4种,分别是CSemaphore(信号量)、CCriticalSection(临界区)、CMutex(互斥量)和CEvent(事件)。下面对这些同步类分别进行介绍。13.7.1 同步访问对象同步访问对象提供了对同步对象的封装。同步访问对象分为两种,分别是CSingleLock和CMultiLock。1CSingleLock如果一次只需要等待一个同步对象,可以使用CSingl
53、eLock类的对象。CSingleLock类的构造函数如下:pObject:表示同步对象指针。该参数不能为NULL。bInitialLock:表示是否在初始化时对同步对象进行访问。如果想要获取同步对象,可以使用Lock()函数。该函数的原型如下:dwTimeOut:表示同步对象等待的时间。13.7.1 同步访问对象如果想要释放同步对象,可以使用Unlock()函数。该函数的原型如下:lCount:表示要释放的对象数。lPrevCount:表示同步对象接收的前一次的个数。如果想要判断等待的同步对象是否被锁定,可以使用IsLocked()函数。该函数的原型如下:13.7.1 同步访问对象2CMul
54、tiLock如果在某个特定的时刻要使用多个同步对象,可以使用CMultiLock类的对象。CMultiLock类的构造函数原型如下:ppObjects:表示同步对象数组指针。dwCount:表示同步对象数组元素的个数。bInitialLock:表示是否在初始化时对同步对象进行访问。13.7.1 同步访问对象如果想要获取同步对象,可以使用Lock()函数。该函数的原型如下:dwTimeOut:表示同步对象等待的时间。bWaitForAll:表示是否等待所有的同步对象。dwWakeMask:指定其他放弃等待的条件。13.7.1 同步访问对象如果想要释放同步对象,可以使用Unlock()函数。该函数
55、的原型如下:lCount:表示要师范的对象数,该参数必须大于0。lPrevCount:表示同步对象接收的前一次的个数。如果想要判断等待的同步对象是否被锁定,可以使用IsLocked()函数。该函数的原型如下:13.7.2 使用信号量实现线程同步CSemaphore类对象代表一个“信号量”,可以允许一定数目的线程访问某个共享资源。信号量对象中保存了当前同时访问某个共享资源的线程计数。如果该计数值为0,则所有对这个CSemaphore类对象所控制资源的访问将被放到一个队列中进行等待,直到超时或计数值不为0。13.7.2 使用信号量实现线程同步CSemaphore类的构造函数原型如下:lInitia
56、lCount:表示信号量对象初始化计数。该参数的值决定了在信号量对象建成后,能同时访问其中资源的最大线程数目。该参数的取值在0IMaxCount之间。lMaxCount:表示信号量对象的最大计数。该参数必须大于0。pstrName:表示要创建的信号量对象的名称。lpsaAttributes:表示信号量对象的安全属性的指针。该参数一般设置为NULL。13.7.3 使用临界区对象实现线程同步CCriticalSection类对象代表一个“临界区”。当多个线程访问一个独占性共享资源时,可以使用临界区对象。在同一时刻,只允许一个线程可以拥有临界区对象,进而访问资源或是代码段,而其他线程需要等待。CCr
57、iticalSection类的构造函数原型如下:13.7.4 使用互斥量对象实现线程同步CMutex类对象代表一个“互斥量”。互斥量对象属于系统内核对象,它能够使线程拥有对某个资源的绝对访问权限。CMutex类的构造函数原型如下:bInitiallyOwn:表示互斥量对象的初始状态。lpszName:表示互斥量对象的名称。lpsaAttribute:表示互斥量对象的安全属性。13.7.5 使用事件对象实现线程同步CEvent类对象代表一个“事件”。事件对象是最基本的内核对象,在处理线程同步时,其使用的最为广泛。事件对象主要分为两种:人工重置事件对象和自动重置事件对象。对于人工重置事件对象,可以
58、同时拥有多个线程等待到事件对象,成为可调度线程。对于自动重置事件对象,等待该事件对象的多个线程只能有一个线程成为可调度线程。CEvent类的构造函数原型如下:bInitiallyOwn:表示初始化时是否允许同步访问对象拥有该对象。bManualReset:指定事件对象类型,即人工重置事件对象和自动重置事件对象。lpszName:表示事件对象的名称。lpsaAttribute:表示事件对象的安全属性。13.7.5 使用事件对象实现线程同步Windows系统提供了CreateEvent()函数用于创建事件对象。该函数的原型如下:lpEventAttributes:表示事件对象的安全属性。bManu
59、alReset:指定事件对象类型,即人工重置事件对象和自动重置事件对象。bInitialState:表示事件对象的初始状态。lpName:表示事件对象的名称。13.7.5 使用事件对象实现线程同步在事件对象创建后,可以使用SetEvent()函数设置事件对象为可用,并释放所有的等待线程。该函数的原型如下:hEvent:表示事件对象句柄。使用ResetEvent()函数设置事件对象为无信号状态。该函数的原型如下:hEvent:表示事件对象句柄。第14章 WinSock网络通信开发Windows应用程序可以有无限的网络功能,都是建立在WinSock接口的基础上。WinSock是Windows So
60、ckets的简称,也称为Windows套接字,是微软根据BSD UNIX操作系统中流行的Berkeley套接字规范而实现的一套Micosoft Windows下的网络编程接口。本章将具体介绍在VC中,基于Winsock接口进行网络通信程序的开发的基础知识。14.1 网络通信与WinSock基础网络通信程序是指应用程序需要与网络中其他系统上的应用程序之间进行通讯。在介绍网络通信程序的开发之前,首先简单介绍一下网络通信和WinSock的基础知识和基本概念。14.1.1 WinSock的基本概念Windows下网络编程的规范Windows Sockets(简称WinSock)是Windows下得到广
61、泛应用的、开放的、支持多种协议的网络编程接口。它经过不断完善,在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已成为Windows网络编程的事实上的标准。Windows Sockets规范意图在于提供给应用程序开发者一套简单的API,并让各家网络软件供应商共同遵守。任何能够与WinSock兼容实现协同工作的应用程序就被认为是具有WinSock接口。称这种应用程序为WinSock应用程序。WinSock规范定义并记录了如何使用API与Internet协议族(IPS,通常指的是TCP/IP)连接,尤其要指出的是所有的WinSock实现都支持流套接
62、字和数据报套接字。14.1.1 WinSock的基本概念应用程序则通过调用WinSock提供的API实现相互之间的通讯,而WinSock又利用下层的网络通讯协议功能和操作系统调用实现实际的通讯工作。在ISO的OSI网络七层协议中,WinSock主要负责的是控制数据的输入和输出,也就是传输层和网络层。它屏蔽了数据链路层和物理层,给Windows下的网络编程带来了巨大的变化。14.1.2 TCP/IP协议与WinSockInternet是建立在TCP/IP协议基础之上,采用了TCP/IP的网络体系结构。TCP/IP不是一个简单的协议,而是一组小的、专业化协议,包括TCP、IP、UDP、ARP、IC
63、MP以及其它的一些被称为子协议的协议。大部分网络管理员将整组协议称为TCP/IP,有时简称为IP。其中的几个重要协议介绍如下:TCP(Transmission Control Protocol,传送控制协议):这是一种提供给用户进程的可靠的全双工字节流面向连接的协议。它要为用户进程提供虚电路服务,并为数据可靠传输建立检查。大多数网络用户程序使用TCP。UDP(User Datagram Protocol,用户数据报协议):这是提供给用户进程的无连接协议,用于传送数据而不执行正确性检查。IP(Internet Protocol,网间协议):负责主机间数据的路由和网络上数据的存储,同时为ICMP,
64、TCP,UDP提供分组发送服务,用户进程通常不需要涉及这一层。TCP/IP协议的核心部分是传输层协议(TCP、UDP),网络层协议(IP)和物理接口层,这三层通常是在操作系统内核中实现,因此用户一般不涉及。14.1.2 TCP/IP协议与WinSock编程时,编程界面有两种形式:一是由内核心直接提供的系统调用;二是使用以库函数方式提供的各种函数。前者为核内实现,后者为核外实现。用户服务要通过核外的应用程序才能实现,所以要使用套接字(WinSock)来实现。TCP/IP协议核心与应用程序关系如下图所示。14.1.3 WinSock通信与C/S结构Windows Sockets通信的基础是套接字(
65、Socket)。与文件操作类似,当要读写一个文件时,必须用一个文件对象(文件指针或文件句柄)执行这个文件。而Socket就是在应用程序之间用来读(接收信息)或写(发送信息)的一个网络对象。Windows Sockets支持两种类型的套接字:流式套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)。流式套接字定义了一种可靠的面向连接的服务,实现了无差错无重复的顺序数据传输。数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠、无差错。14.1.3 WinSock通信与C/S结构对于要求精确传输数据的Windows Sockets通信程序,
66、一般采用流式套接字。采用流式套接字通信的一个最典型的应用就是客户机/服务器(C/S)模型,这也是在TCP/IP网络中,两个进程间的相互作用的主要模式。客户机/服务器模式在操作过程中采取的是主动请示方式,其具体工作流程如下图所示。14.1.3 WinSock通信与C/S结构首先服务器方要先启动,并根据请示提供相应服务,具体过程如下:(1)打开一通信通道(Socket)并告知本地主机,它准备在某一个地址上接收客户的连接请求。(2)进入监听状态,等待客户请求到达该端口。(3)接收到客户服务请求,处理该请求并发送应答信号。(4)返回第二步,等待另一客户请求。(5)关闭服务器。14.1.3 WinSoc
67、k通信与C/S结构而客户方的工作过程如下:(1)打开一通信通道(Socket),并连接到服务器所在主机的特定端口。(2)向服务器发送服务请求报文,等待并接收应答,继续提出请求。(3)请求结束后关闭通信通道并终止。14.1.4 MFC中WinSock的封装类当然,在VC6.0中,程序员可以直接使用Windows Sockets API函数进行WinSock网络程序的开发。Windows Sockets API是以Berkeley Sockets API为模型,提供了一个标准的API,Windows程序员可以使用它来编写网络应用程序。Windows Sockets API函数共包括三大类:套接字函
68、数、数据库函数和针对Microsoft Windows的扩展函数。其具体使用本书不作详细介绍。MFC为套接字提供了封装类CAsyncSocket和CSocket,它们封装了Windows Sockets的API,从而程序员可以用面向对象的方法调用Socket。14.1.4 MFC中WinSock的封装类CAsyncSocket类是在一个较低的层次上封装了Windows Sockets API,它封装了异步、非阻塞Socket的基本功能,提供了Socket的基本操作,用它做常用的网络通信软件很方便。CSocket类由CAsyncSocket派生,是Windows Sockets API的高层抽象
69、,提供了更高层次的功能阻塞式的访问方式。有关函数的原型及使用,在下面将结合具体的实例开发来介绍。CSocket类新提供的主要成员函数及其功能如下表所示。14.1.5 WinSock网络编程的常用术语对于许多初学者来说,网络通信程序的开发,普遍的一个现象就是觉得难以入手。许多概念,诸如同步(Sync)、异步(Async)、阻塞(Block)、非阻塞(Unblock)等,初学者往往迷惑不清,这里对一些常用术语作一下简单介绍。1、套接字套接字也就是前面多次提到的Socket,是可以被命名和寻址的通信端点,是网络的基本构件。实际上,套接字可以理解为在计算机中提供的一个通信端口,可以通过这个端口与任何一
70、个具有Socket接口的计算机通信。应用程序在网络上传输、接收的信息都通过这个Socket接口来实现的。使用中的每一个套接字都有其类型和一个与之相连的进程。WINDOWS SOCKET 1.1 版本支持两种类型的套接字:流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)。2、同步/异步同步方式指的是发送方不等接收方响应,便接着发下个数据包的通信方式;而异步指发送方发出数据后,等收到接收方发回的响应,才发下一个数据包的通信方式。3、阻塞模式/非阻塞模式CSocket类创建的套接字支持阻塞模式,阻塞模式简单来说就是服务端与客户端之间的通信处于同步状态下。所谓阻塞套接字是指执行
71、此套接字的网络调用时,直到成功才返回,否则一直阻塞在此网络调用上。比如调用Receive函数读取网络缓冲区中的数据,如果没有数据到达,程序将一直停止在Receive这个函数调用上,直到读到一些数据,此函数调用才返回。在非阻塞模式下利用Socket事件的消息机制,服务端与客户端之间的通信处于异步状态下。即执行非阻塞套接字的网络调用时,不管是否执行成功,都立即返回。比如调用Receive函数读取网络缓冲区中数据,不管是否读到数据都立即返回,而不会一直停止在此函数调用上。14.2 无连接通信开发使用Socket进行通信,有两种主要的方式:面向连接的流方式和无连接的数据报文方式。进行无连接通信时,通信
72、的两台计算机像是把数据放在一个信封里,通过网络寄给对方,信在传送的过程种有可能会残缺,也有可能后发的先到。该种方式支持双向的数据流,但并不保证是可靠、有序、无重复的。使用Socket进行无连接通信开发时,需要创建数据报套接字(SOCK_DGRAM)。14.2.1 Socket无连接通信机制在IP中,无连接通信是通过UDP/IP协议完成的。UDP不能确保可靠的数据传输,但能将数据发送到多个目标,或者接收多个源数据。如果一个客户端向服务器发送数据,则该数据会立刻被传输,不管服务器是否准备接收这些数据。如果服务器接收到客户端发送的数据,也不需要向客户端发送消息以确认数据被收到。UDP/IP的数据传输
73、使用数据报,即离散的信息包。使用无连接通信时,服务器端和客户端之间差别不大,通信双方处于完全对等状态,不存在监听与连接过程。这里就分别以发送端和接收端来区分双方,通过CSocket类实现Socket,简单介绍一下无连接通信的通信流程。1接收端对于在一个无连接的套接字上接收数据来说,并不是很复杂。具体流程可表示如下:(1)初始化WinSock的动态连接库后,首先构造CSocket套接字对象,使用Creat函数创建数据报格式的套接字。(2)通过Bind函数在将该套接字与准备接收数据的接口绑定在一起。(3)使用Receive函数接收对方发送的数据,此时程序处于阻塞状态,直到接收到数据自动返回。(4)
74、接收数据完毕,使用Close函数关闭套接字。2发送端在一个无连接的套接字上发送数据的具体流程可表示如下:(1)首先构造CSocket套接字对象,使用Creat函数创建数据报格式的套接字。(2)通过Bind函数在将该套接字与准备发送数据的接口绑定在一起。(3)使用Send函数向对方发送数据。(4)发送完毕,使用Close函数关闭套接字。14.2.2 主要功能函数介绍前面已经介绍过,在MFC中,CAsyncSocket和CSocket类封装了Windows Sockets API函数。本节将结合套接字的创建和使用过程,介绍无连接通信中,使用的CAsyncSocket类的主要成员函数(CSocket
75、类由CAsyncSocket类派生)。1WinSock环境的初始化在使用WinSock MFC类之前,必须为应用程序初始化WinSock环境。其实现只要调用全局函数AfxSocketInit即可。如下面的代码:同时,在“stdafx.h”文件中添加如下代码:如果在使用MFC AppWizardEXE创建MFC工程时,在MFC AppWizard Step 2对话框选择“Windows Sockets”选项,则程序回自动添加上面的代码,实现WinSock的初始化。2创建Socket创建Socket,首先需要构造Socket对象,而后调用Create函数创建Socket。Create函数原型如下:
76、各参数含义如下:nSocketPort:为使用的端口号,默认为0,表示由系统自动选择,通常在客户端都使用这个选择。nSocketType:为使用的协议族,默认为SOCK_STREAM,表示使用面向连接的流服务;为SOCK_DGRAM,表示使用无连接的数据报服务。lpszSocketAddress:为本地的IP地址,可以使用点分法表示如“127.0.0.1”。2创建Socket也可以通过使用Bind函数设置Socket的地址和端口号,如下:表示该Socket对象的地址为“168.0.1”,端口为4800。通过Socket提供的send()和Receive()函数可以实现任何类型数据的发送和接收。
77、3. 发送、接收数据通过Socket连接发送和接收数据比较简单。可以用Socket发送任何类型的数据,只需要一个指向存放数据的缓冲区指针即可。发送时,缓冲区存放待发送的数据;接收时,接收的数据将拷贝到缓冲区。(1)发送数据可以使用Send函数通过Socket连接发送数据,函数的原型如下:各参数含义如下:lpBuf:指向发送数据缓冲区的指针,如果数据为CString变量,可使用LPCTSTR操作符把CString变量作为缓冲区传送。nBufLen:指明缓冲区要发送数据的长度。nFlags:该参数是可选的,用于控制消息的发送方式。函数执行成功,返回发送到对方应用程序的数据总量。如果有错误产生,函数
78、返回SOCKET_ERROR。3. 发送、接收数据(2)接收数据Socket接收数据时,就需要调用Receive函数。Receive函数原型如下:Receive函数的参数与Send函数基本相同,lpBuf为缓冲区指针,指明接收数据存储的位置,参数nBufLen是缓冲区的长度,指示Socket能存储多少数据。nFlags为标记位,收发双方需要指明相同的标记。执行成功后,Receive函数也返回接收到的数据的数据量。如果有错误产生,函数返回SOCKET_ERROR。3. 发送、接收数据有一点需要说明,在接收数据时,最后一个字符后面最好设置一个NULL字。因为缓冲区中可能会有一些垃圾数据,如果接收的
79、数据后面不加NULL,应用程序可能会把这些垃圾数据作为接收数据的一部分。如下面的实现代码:对于无连接通信,即数据报类型的Socket,发送和接收数据还可以使用SendTo和ReceiveFrom函数,其功能和使用与Send和Receive函数基本相同。4. 关闭Socket连接当应用程序完成与对端的所有通信之后,就可以调用Close函数关闭这次连接。Close函数不带任何参数,调用方式如下:有时可能需要在Socket关闭之前就让其停止运行,这时可调用Shutdown函数。Shutdown函数原型如下:4. 关闭Socket连接该函数只需要一个整形参数nHow,指明是否关闭此Socket数据的发
80、送或接收,如下表所示。需要注意的是,调用Shutdown函数并不关闭网络连接,也不能释放Socket所占用的任何资源,程序完成后,仍然需要调用Close函数关闭Socket。14.2.3 无连接通信接收端的实现无连接通信发送和接收方处于相同的地位没有主次之分。常用于对数据可靠性要求不高的通信,如实时的语音、图像传送和广播消息等。14.2.4 无连接通信发送端的实现要在一个无连接的套接字上发送数据,最简单的方法就是创建一个套接字,然后调用SendTo函数向指定接口发送数据即可。14.3 面向连接通信开发与无连接通信不同,面向连接通信要求两个通信的应用程序之间首先要建立一条连接链路,而后数据才能被
81、正确接收和发送。面向连接通信的特点是通信可靠,对数据有重发和校验机制,通常用来做数据文件的传输如FTP,Telnet等。使用Socket进行面向连接通信开发时,需要创建流式套接字(SOCK_STREAM)。14.3.1 Socket面向连接通信机制在IP中,面向连接地通信是通过TCP/IP协议完成地,TCP提供两个计算机间可靠无误地数据传输。应用程序使用TCP通信时,在源计算机和目标计算机之间,会建立起一个虚拟连接。建立连接之后,计算机之间便能以双向字节流的方式进行数据交换。与无连接通信不同,面向连接通信,必需有一方扮演服务器的角色,等待另一方(客户端)的连接请求。服务器方需要首先创建一个监听
82、套接字,在此套接字上等待连接。当连接建立后会产生一个新的套接字用于与客户端通信。以CSocket类实现Socket通信为例,简单介绍一下面向连接通信的通信流程。1服务器端面向连接通信服务器端的具体实现流程可表示如下:(1)创建监听Socket对象。初始化WinSock的动态连接库后,需要在服务器端建立一个监听的Socket,即使首先构造一个CSocket对象,而后通过调用Create函数创建一个流套接字。(2)绑定监听Socket的端口。使用Bind函数为服务器端定义的这个监听的Socket指定一个地址及端口,这样客户端才知道要连接哪一个地址的哪个端口。(3)进入监听状态。使用Listen函数
83、使服务器端的Socket进入监听状态,并设定可以建立的最大连接数。(4)接受用户的连接请求。服务器进入到监听模式后,便已经做好了可以接受客户端连接的准备了。下面就可以通过Accept函数来接受用户的连接请求。(5)与客户端进行通信。Accept函数执行后,将新建一个通信Socket与客户端的Socket相通,原先的监听Socket继续进入监听状态,等待他人的连接要求。通信Socket就可以通过Read和Write函数与客户端进行通信。(6)关闭服务。当要关闭服务器时,使用Close函数关闭监听套接字和通信套接字即可。2客户端面向连接通信客户端网络连接的创建相对服务器端要简单,其具体实现流程可表
84、示如下:(1)创建客户端Socket。初始化WinSock的动态连接库后,首先构造CSocket套接字对象,使用Creat函数创建套接字。与服务器端的Socket不同的是,客户端的Socket可以调用Bind函数,由自己来指定IP地址及端口号,也可以不调用Bind,而由Winsock来自动设定IP地址和端口。(2)提出连接请求。客户端的Socket使用Connect函数来提出与服务器端的Socket建立连接的申请。(3)与服务器通信。服务器端接受客户Socket的连接请求后,便可以通过Read和Write函数与服务器端进行通信。(4)断开连接。使用Close函数关闭客户端Socket即可实现断
85、开与服务器的连接。14.3.2 主要功能函数介绍在上节介绍了面向连接通信程序开发的基本流程和使用的Socket类相关的成员函数。其中大部分函数在前面已有详细介绍,本节将详细介绍一下面向连接通信特有的有关连接的几个函数的使用。1. 监听函数Listen服务器端的监听Socke通过调用Listen成员函数,使Socket处于监听状态,监听对方(客户端)的连接请求。Listen函数原型如下:参数nConnectionBacklog为Socket同时可以接受的连接数,默认值为5,也是最大值。函数成功执行则返回一个非0值。Listen函数的典型调用代码如下:2. 连接函数Connect客户端Socket
86、通过调用Connect函数连接服务器,其原型如下:两个参数分别为要连接的计算机(服务器)的IP地址和端口号。Connect函数的典型调用代码如下:当执行CSocket类的Connect函数时,在连接成功之前不会返回,即程序处于阻塞状态,只有成功连接或者出了故障不能进行连接才返回。3. 接受连接函数Acceptt服务端Socke通过调用Accept函数接受客户端连接请求。Accept函数的原型如下:其中,参数rConnectedSocket为一个新的套接字,用于与连接方通信;参数lpSockAddr为SOCKADDR结构的指针,用于记录连接方(客户端)的IP地址信息;参数lpSockAddrLe
87、n则存储lpSockAddr信息的长度。3. 接受连接函数Acceptt当连接建立后,一个新的套接字将被创建,该套接字用于与对方应用程序连接。Accept函数的典型应用代码如下:当使用CSocket类时,Accept函数直到收到连接请求并接受后,函数才返回。14.3.3 面向连接通信服务器端的实现与无连接通信的实例相对应,本节将给出一个面向连接通信服务器端的实例。实例将实现在固定端口创建监听套接字,等待客户端的连接。当有客户端连接时,通过新建的通信套接字向客户套接字定时(间隔1.5秒)发送数据,并显示当前的发送状态和发送的数据量。14.4 Socket非阻塞模式及开发当有多个客户端Socket
88、与服务端Socket连接及通信时,服务端采用阻塞模式就显得不适合了,应该采用非阻塞模式,利用Socket事件的消息机制来接受多个客户端Socket的连接请求并进行通信。本节将重点介绍Socket事件的处理机制,并给出一个网络聊天室的开发实例。14.4.1 CSocket阻塞模式在前面介绍的的面向连接通信服务器端程序开发中,监听套接字采用的是CSocket的阻塞模式。 很显然,如果没有来自客户端Socket的连接请求,Socket就会不断调用Accept函数产生循环阻塞,直到有来自客户端Socket的连接请求而解除阻塞。阻塞解除后,表示服务端Socket和客户端Socket已成功连接,服务端与客
89、户端彼此可相互调用Send和Receive方法开始通信。14.4.1 CSocket阻塞模式如果服务端用于监听的Socket在主线程中运行,这将导致主线程的阻塞。因此,当有多个客户连接服务器时,需要分别为其创建一个新的线程,以运行Socket服务,如下图所示。显然,采用这种方式增加了程序设计的复杂性。14.4.2 CSocket非阻塞模式事件处理非阻塞模式是指服务端与客户端之间的通信处于异步状态下,可以通过Socket事件的消息机制来实现。使用CSocket(或者CAsyncSocket)类创建的客户Socket与服务器Socket进行连接通信时,会触发许多事件。如当客户端Socket成功连接
90、服务器时,会触发FD_CONNECT事件,而服务器端接受用户连接,会触发FD_ACCEPT事件等等。为了响应这些事件,在CAsyncSocket类中,定义了一系列可重载的函数,如OnConnect、OnAccept等。14.4.2 CSocket非阻塞模式事件处理各事件及其对应的响应函数如下表所示。14.4.2 CSocket非阻塞模式事件处理这样,用户就可以从CSocket类(或者CAsyncSocket)派生一个新类,通过重载这些Socket事件的响应函数,实现非阻塞模式的面向连接通信。简单来讲,如前面介绍的面向连接的服务器端的例程,采用的是CSocket的阻塞模式,其启动按钮响应函数如下
91、:14.4.2 CSocket非阻塞模式事件处理Accept函数阻塞了主线程,等待用户的连接。而如果这里采用Socket事件机制,便可以实现非阻塞方式。简单来说,就是从CSocket类派生新的监听Socket类CListenSocket,在其中重载OnAccept函数,在OnAccept函数中实现创建客户套接字与客户端进行通信。此时,启动按钮响应函数可表示如下: 31、只有永远躺在泥坑里的人,才不会再掉进坑里。黑格尔32、希望的灯一旦熄灭,生活刹那间变成了一片黑暗。普列姆昌德33、希望是人生的乳母。科策布34、形成天才的决定因素应该是勤奋。郭沫若35、学到很多东西的诀窍,就是一下子不要学很多。洛克