糖儿飞教你学C++ Socket网络编程——7. Win32 API网络通信程序

对于WinSock编程的初学者来说,由于控制台程序不涉及Windows的界面及消息响应机制,因此能更容易理解WinSock编程的流程,但目前大多数应用程序都是Windows界面的,因此需要学习将控制台程序改造成Windows界面的程序,而改成Windows界面程序的关键是将WinSock编程的代码嵌入到Windows界面程序的合适位置中。

3.1 Windows对话框程序

对于Windows界面的程序来说,可分为2种,一种是对话框程序,一种是窗口程序,其中窗口程序又可分为SDI(单文档窗口)和MDI(多文档窗口)。

对话框与窗口的区别在于:窗口的上方有菜单栏和工具按钮栏,下方有状态栏。例如Word软件,就是一个窗口程序;而对话框没有菜单栏和工具栏等,例如QQ的登录界面就是一个对话框。因此,对话框其实是一种简单的窗体,对于简单的Windows界面程序,一般都采用对话框程序来制作(本书所有的Windows程序都是基于对话框的)。

3.1.1 新建对话框程序

在VC6中,执行菜单命令“文件→新建”,将打开如图3-1所示的新建工程对话框中,要新建Windows界面的应用程序,有以下两种方法:

1)选择Win32 Application,这种方法是使用Windows API函数开发图形界面的应用程序,API是应用程序接口(Application Programming Interface)的意思,Windows API编程就是指调用Windows的接口函数来进行程序的编写,例如MessageBox()就是一个API函数或者说接口函数。微软提供了上千个标准的API函数,这些函数定义在微软的SDK(Software Development Kit,软件开发包)提供的头文件、库文件、工具之中。因此Windows API编程也称为Windows SDK编程。

2)选择MFC APPWizard(exe),这是使用MFC类库开发图形界面的应用程序。为了降低API函数编写Windows应用程序的难度,微软开发了微软基础类库(Microsoft Foundation Class, MFC),MFC采用面向对象技术,将几乎所有的SDK API函数封装在不同的类之中,从而提供了一套专门用于开发Windows应用程序的开发框架。

图3-1 新建工程对话框

一般来说,初学者学习Windows界面编程,应先学习Windows API编程,有了一定基础后再学习MFC编程,这样可以降低学习难度,且能更深入地理解MFC的实现原理,因此本章专门介绍Windows API编程。

MFC在新建工程的第二步提供了一个“基本对话框”选项,专门用来创建对话框程序,而Win32 Application在新建工程时没有提供新建对话框程序选项,需要手工修改“一个典型的Hello World程序”的代码,具体步骤如下:

1. 使用Win32 Application新建对话框程序

1)新建工程,选择“Win32 Application”,输入工程名(如Dem),单击“下一步”,在图3-2所示的对话框中,选择“一个典型的Hello World程序”,单击“完成”。

图3-2 建立Win32 Application

如果要在VS2010中新建项目,则执行菜单命令“文件→新建→项目”,在Visual C++中,选择“Win32项目”,在下方“名称”后输入项目名,在下一步中单击“完成”即可。

2)直接运行该工程。在图3-3所示的工作空间窗口,单击Build(F7),再单击Execute(Ctrl+F5),就会弹出如图3-4所示的Windows窗体程序,执行该窗体中的菜单命令“Help→About”,图3-4中将出现About对话框。可见该程序包含一个窗体和一个对话框。

图3-3 工作空间窗口

图3-4 Hello World程序运行效果

在图3-3中,选择底部的ResourceView选项卡,可以查看该窗口程序对应的资源文件,如图3-5所示。其中,IDD_ABOUTBOX是图3-4中弹出对话框的ID,双击它可对该对话框资源进行编辑。另外,由于该程序是一个窗口程序,所以具有菜单(Menu)资源。

在图3-3中,选择ClassView选项卡,可以查看该程序中的所有类,如图3-6所示。由于该程序中没有创建任何类(class),因此程序中所有的函数和变量在Globals目录下,表示它们是全局函数或全局变量。可见,全局函数就是不属于任何类的函数。

         

图3-5 ResourceView选项卡      图3-6 ClassView选项卡

3)接下来我们要去掉图3-4的窗体界面让程序直接弹出About对话框。方法是:在图3-3的左侧工作区中选择FileView选项卡,找到该工程的源文件(工程名.cpp,如Dem.cpp),将WinMain函数中的第1行(文件的第25行)开始到DialogBox上一行(147行)的代码全部删除,再从DialogBox下一行(149行)到return 0的上一行之间的代码也全部删除。也就是只保留WinMain函数中DialogBox一行(148行)和return 0; 一行,再将DialogBox函数中第3个参数由hWnd改为NULL。此时WinMain函数的代码如下:

int APIENTRY WinMain(……){

   DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, NULL, (DLGPROC)About);

   return 0;

}

其中:

WinMain()函数是Windows应用程序的入口函数,相当于控制台程序中的main函数。

DialogBox函数的功能是创建一个对话框,hWnd是调用该对话框的窗口的句柄变量名,由于窗口对应的所有代码已经被删除,因此将调用该对话框的窗口句柄改成NULL,这样启动程序时,会直接加载对话框。

DialogBox()函数有4个参数,含义如下:

① hInst:设置本对话框属于当前进程,因为HINSTANCE是窗口进程的句柄;

② IDD_ABOUTBOX:设置本对话框使用哪个对话框的资源;

③ NULL:指定本对话框的父窗口是哪个,如果没有父窗口,则设为NULL;

④ About:指定本对话框的消息处理函数,用来处理对话框初始化及按钮等产生的消息。

提示:句柄(handle)是Windows编程中的重要概念,句柄是Windows中各个对象的一个唯一的、固定不变的ID;作用上,Windows使用句柄来标识应用程序中的不同对象或同类中的不同实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。逻辑上,句柄相当于Windows对象指针的指针;Windows通过对象的地址来访问对象,但Windows采用虚拟内存机制,对象的地址是经常发生变化的,为此,需要使用一个内存区域来记录对象的地址,这块内存区域的地址在程序的运行中是不会变化的,而句柄的值就是这块内存区域的地址,因此,句柄相当于Windows对象指针的指针。

4)在对话框中添加控件,修改后对话框界面如图3-7所示,方法是:

①单击图3-8所示的控件工具栏中的“选择控件”工具,按住Shift键依次选中图3-4About对话框左侧的图片框和2个静态文本,按Del键将其删除。

②在图3-8所示的控件工具栏中选择相应的控件,向对话框中添加1个静态文本控件,2个编辑框和1个按钮。

③ 在图3-9所示的属性面板中设置控件的属性。方法是先选中控件,按右键单击属性(或按Alt+Enter键),将弹出如图3-9所示的控件属性面板,将各控件的ID设置为如图3-10所示。再设置静态文本控件、两个按钮和对话框的标题如图3-7所示,并在属性面板中将图3-7中下方编辑框的样式选项勾选为“只读”。

图3-7 对话框的界面

图3-8 控件工具栏

图3-9 控件的属性面板

图3-10 对话框各控件的ID值

提示:

1)在图3-9的属性面板中,ID是程序访问控件的依据,例如GetDlgItem(hDlg, IDC_ EDIT2),就能让程序获取到IDC_EDIT2这个编辑框,因此,除静态文本外,其他控件的ID名都不能重复。而标题属性用来设置控件上显示的内容,因此,静态文本和按钮通常必须设置标题属性,而编辑框等可供用户输入内容的控件是没有标题属性的。

2)如果图3-8所示的控件工具箱不见了,可在VC6主界面的工具栏的空白处上按右键,在右键菜单中勾选“控件”就能显示控件工具箱。

3)单击图3-9属性面板左上角的“图钉”按钮,可以让属性面板总是保持显示。

3.1.2 处理Windows消息

Windows程序是通过系统发送的消息来响应用户的输入的。例如,用户单击按钮、单击鼠标、移动鼠标等程序运行时所产生的动作,都称为事件,应用程序的初始化也是一个事件,每个事件都会产生一个对应的消息,应用程序通过接收消息、分发消息、处理消息来和用户进行交互。

Windows消息可分为标准Windows消息、控件通知和命令消息等类型。其中,命令消息是指用户单击按钮、菜单项、工具栏按钮产生的消息,其名称为WM_COMMAND。除WM_COMMAND外,所有以“WM_”为前缀的消息都是标准Windows消息,例如,WM_INITDIALOG表示对话框及其所有子控件都创建完毕(完成初始化),另外,还能自定义Windows消息,如WM_SOCKET。实际上,每个Windows消息都对应一个常量值。

在Win32 Application中,对消息处理的代码都要写在回调(CALLBACK)函数中,所谓回调函数,是指一个通过函数指针调用的函数。与普通函数不同,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件触发时由其他的函数调用,用于对该事件或条件进行响应。

例如DialogBox函数的作用是从一个对话框资源中创建一个模态对话框。该函数一直到指定的回调函数通过调用EndDialog函数中止模态对话框才能返回控制,当对话框初始化时或单击对话框上的按钮,都会产生一个Windows消息,在消息发生时,DialogBox函数就会调用指定的回调函数对消息进行处理。DialogBox函数的第4个参数用来指定它的回调函数,例如:

DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, NULL, (DLGPROC)About); 

其中,第4个参数(DLGPROC)About指定了该函数的回调函数是About()。

因此,我们可在名为About的回调函数中对该对话框的消息进行处理,示例代码如下:

LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)            //4个参数为:窗口句柄,消息名,消息参数1,消息参数2

       switch (message) {                     //处理感兴趣的消息

              case WM_INITDIALOG:     //处理对话框初始化消息

                            return TRUE;

              case WM_COMMAND:       //处理按钮消息

                     if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {

                            EndDialog(hDlg, LOWORD(wParam));      //关闭对话框

                            return TRUE;                }

                     break;     }

return FALSE;       }

可见,一个消息由一个消息名称(UINT)和两个参数(WPARAM,LPARAM)组成。

当用户单击某个按钮就会产生一个WM_COMMAND的通知消息,这条消息的wParam参数的低位字节中含有该控件的ID。因此通过LOWORD(wParam)的值就能判断用户单击的是哪个按钮,wParam参数的高位字节则为通知代码,lParam参数则是指向控件的句柄。

因此要处理用户单击确定按钮(IDC_QD)产生的通知消息,只要对case WM_COMMAND中的代码作如下扩充即可。

case WM_COMMAND:

       if (LOWORD(wParam) == IDOK) {           //处理单击退出按钮的消息

              EndDialog(hDlg, LOWORD(wParam));      //关闭对话框

       }

       if (LOWORD(wParam) == IDC_QD) {           //处理单击确定按钮的消息

                      ……

       }

提示:回调函数的返回值类型用LRESULT CALLBACK定义,LRESULT是一个数据类型,L表示long(长整型),result表示结果,说明这个函数的返回值是某个结果。CALLBACK是一个空宏,只是为了表示这是一个回调函数。

3.1.3 获取和设置控件的内容

图3-11所示程序的功能是,当用户在编辑框1中输入内容,单击“确定”按钮,编辑框2中就会显示“欢迎您,编辑框1的内容”。因此单击确定按钮的程序流程是:①获取用户在编辑框1中输入的文本内容,②将该文本内容进行修改后显示到编辑框2中。

因此程序的关键是获取和设置编辑框的内容。Windows API提供了如下2个函数:

1)GetDlgItemText():获取编辑框中的内容,例如:

GetDlgItemText(hDlg,IDC_EDIT1,user,50);

表示获取对话框hDlg中编辑框IDC_EDIT1中的内容,并将该内容保存到变量user中,最多获取50个字符。

2)SetDlgItemText():设置编辑框中的内容,例如:

SetDlgItemText(hDlg,IDC_EDIT2,wel);

表示将hDlg中编辑框IDC_EDIT2中的内容设置为字符数组变量wel的值。

因此,3.1.2节中处理单击确定按钮消息的代码如下:

char user[50];               //声明字符数组变量用于保存编辑框的内容

char wel[60]="欢迎您:";

if (LOWORD(wParam) == IDC_QD) {              //处理单击确定按钮的消息

       GetDlgItemText(hDlg,IDC_EDIT1,user,50);       //获取IDC_EDIT1中的内容

       strcat(wel,user);                          //连接两个字符串,连接后的值保存在wel中

       SetDlgItemText(hDlg,IDC_EDIT2,wel);            //设置IDC_EDIT2的内容

}

图3-11 本实例效果

3.2 Windows API程序示例

3.2.1 计算器程序

图3-12是一个计算器程序,制作方法是:首先按照3.1.1节新建对话框程序的方法新建该工程,然后绘制对话框界面,该对话框有3个编辑框,1个组合框和2个按钮,各个控件的ID如图3-12所示。

图3-12 计算器程序界面

在绘制组合框时,应注意先单击组合框右侧的下三角箭头,此时虚线框会增大(如图3-13所示),表示下拉列表的显示范围,将下方的控制点往下拖动可保证下拉菜单能完全显示出来。

图3-13 调整组合框的下拉列表范围

1. 实现仅有加法功能的计算器

计算器程序的编程步骤是:当单击计算按钮时,先获取IDC_EDIT1和IDC_EDIT2的内容,分别保存到两个变量中,然后对两个变量值进行相加运算,再把IDC_EDIT3的值设置为计算结果值。代码如下:

if (LOWORD(wParam) == IDC_CALC) {                 //如果单击了计算按钮

int nLeft=GetDlgItemInt(hDlg,IDC_LEFT,NULL,TRUE); //获取IDC_EDIT1的值

int nRight=GetDlgItemInt(hDlg,IDC_RIGHT,NULL,TRUE);     //获取IDC_EDIT2的值

SetDlgItemInt(hDlg,IDC_RESULT,nLeft+nRight,TRUE);          //设置IDC_EDIT3的值

}

本例中,获取和设置控件的内容使用了两个新的Win32 API函数:

GetDlgItemInt()和SetDlgItemInt(),它们能将获取的值自动转换为整型。

2. 扩充计算器的功能

为了实现具有加减乘除功能的计算器,可以在对话框中添加一个组合框IDC_COMBO1,在对话框初始化时对IDC_COMBO1进行初始化,也就是将四个运算符号添加到它的列表项中,为组合框添加列表项的方法如下:

SendMessage(GetDlgItem(hDlg, IDC_COMBO1), CB_ADDSTRING, 0, (LPARAM)fh[i]);

其中,第1个参数表示获取hDlg中的IDC_COMBO1控件,第2个参数表示为组合框添加列表项,第4个参数是要添加的内容。

当用户单击计算按钮时,必须获取用户在组合框中选择的符号,获取组合框的选中项的方法如下:

int cursel=SendDlgItemMessage(hDlg,IDC_COMBO1,CB_GETCURSEL,0,0);

其中,第3个参数CB_GETCURSEL表示获取组合框的选中项,CB_表示ComboBox。

下面是具有加减乘除功能和小数计算功能的计算器的完整代码。

#include "stdafx.h"

#include "resource.h"

#include <stdio.h>                       //sprintf函数需要这个头文件

HINSTANCE hInst;                    

LRESULT CALLBACK  About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

                     LPSTR lpCmdLine, int nCmdShow){

       DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, NULL, (DLGPROC)About);

       return 0;

}

bool isnum(char *str1) {                     //函数功能:判断输入是否为数字

       int i, flag=1; 

       for(i=0;i<strlen(str1);i++)  { 

              if ( (str1[i]<'0' || str1[i]>'9')&&str1[i]!='.'&&str1[i]!='.')  //如果str1中有非数字

              {     flag=0;           return FALSE;              } 

       } 

       return TRUE;  //   否则返回TRUE

LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam){

       char * fh[4]={"+","-","*","/"};             //声明字符串数组

       int i;

       switch (message)   {                   //选择处理感兴趣的消息

       case WM_INITDIALOG:            //对话框初始化消息

              for (i=0; i<4; i++)   {  //向组合框中添加运算符

       SendMessage(GetDlgItem(hDlg, IDC_COMBO1), CB_ADDSTRING, 0, (LPARAM)fh[i]);

              }

              return TRUE;        

       case WM_COMMAND:

              if (LOWORD(wParam) == IDOK) {          //单击了退出按钮        

                     EndDialog(hDlg, LOWORD(wParam));

              }

              if (LOWORD(wParam) == IDC_CALC)            //单击了计算按钮

              {

                     TCHAR str1[256],str2[256]; 

                     GetDlgItemText(hDlg,IDC_EDIT1,str1,sizeof(str1));  //得到输入的字符串 

                     GetDlgItemText(hDlg,IDC_EDIT2,str2,sizeof(str2)); 

                     if (isnum(str1) && isnum(str2)) {       //如果获得的字符串是数值                 

                            double i1=atof(str1);             //将字符串转换成浮点数

                            double i2=atof(str2); 

                            double i3;                    //用于保存运算结果

                                   //获取下拉框当前选中项

                     int cursel=SendDlgItemMessage(hDlg,IDC_COMBO1,CB_GETCURSEL,0,0);

                            switch(cursel)      { 

                            case 3:  i3=i1+i2;        break;            //选中项是+号      

                            case 2:  i3=i1/i2;         break; 

                            case 1:  i3=i1*i2;        break; 

                            case 0:  i3=i1-i2;         break; 

                            } 

                            TCHAR str3[256]; 

                            sprintf(str3,"%8.3lf",i3);      //将浮点数i3转换为字符串

                            SetDlgItemText(hDlg,IDC_EDIT3,str3);    //设置IDC_EDIT3的内容

                     } 

                     else  MessageBox(hDlg,"请输入数字","输入非法",MB_OK);          } 

              break;     }

    return FALSE;        }

提示:

程序中sprintf()函数常用于将数值型数据转换为字符串,也可用于多个字符串的连接:

sprintf(sbuff,"%s:%d说:%s", strip,strport,buff);     

而strcat()函数只能用于2个字符串的连接,例如:strcat(strip,buff);。

3.2.2 获取主机名、IP和时间的程序

在WinSock中,用gethostname()函数可获取本机的主机名,而用gethostbyname()函数可以通过主机名获取本机的IP地址。利用这两个函数,将获取的主机名、IP和时间显示在静态文本框上,就能制作出如图3-14所示的程序。

具体步骤是:首先按照3.1.1节新建对话框程序的方法新建一个Win32 Application工程,然后绘制如图3-14对话框界面,添加6个静态文本,左侧3个的标题分别为“主机名”、“IP”和“时间”。右侧3个从上到下设置ID值依次是IDC_HOST、IDC_SHOWIP和IDC_TIME。最后编写如下代码。

图3-14 程序界面

#include "stdafx.h"

#include "resource.h"

#include "winsock2.h"

#include "iostream.h"

#include "time.h"                  //时间函数需要的头文件

#include "stdio.h"

#pragma comment(lib,"ws2_32.lib")

 

HINSTANCE hInst;      

LRESULT CALLBACK  About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

         LPSTR   lpCmdLine, int       nCmdShow){

      WSADATA wsaData;    

       WSAStartup(MAKEWORD(2,2), &wsaData);            //初始化协议栈

       DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, NULL, (DLGPROC)About);

   return 0;

}

LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam){

       char hostname[100];

       hostent *hst = NULL;

       in_addr inaddr;

    char *pp=NULL;

       time_t tt = time(NULL);               //这句返回的只是一个时间戳

       tm* t= localtime(&tt);                  //返回当地格式的时间,保存在变量t中

       char szTime[20];

       switch (message)   {

       case WM_INITDIALOG:

              gethostname(hostname,sizeof(hostname)); //获取主机名,保存在变量hostname中

              hst =gethostbyname(hostname);   //获取IP地址,保存在变量hst中

              memcpy((char*)(&inaddr),hst->h_addr_list[0],hst->h_length);

              pp=inet_ntoa(inaddr);                   //将IP地址格式转换为本机字节

              sprintf(szTime,"%d-%02d-%02d %02d:%02d:%02d", t->tm_year + 1900,t->tm_mon + 1,t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);               //设置时间格式

              SetDlgItemText(hDlg,IDC_HOST, hostname);            //显示主机名

              SetDlgItemText(hDlg,IDC_SHOWIP, pp);                  //显示IP

              SetDlgItemText(hDlg,IDC_TIME, szTime);               //显示时间

              return TRUE;

              case WM_COMMAND:

                     if (LOWORD(wParam) == IDOK)       {            //退出按钮

                            EndDialog(hDlg, LOWORD(wParam));      //关闭对话框

                            return TRUE;                }

                     break;

       }

return FALSE;              }

总结:要获取本机IP地址,第一步是使用gethostname()函数获取本机的名称,第二步是使用gethostbyname()函数,并将本机名称作为参数,该函数返回值是一个hostent结构体的指针,IP地址存放在该结构体中的数组h_addr_list的第0个数组元素中,因此第三步是使用memcpy()函数将第0个数组元素取出并复制到inaddr变量中,第四步是使用inet_ntoa()将IP地址由in_addr无符号长整型转换成点分十进制的表示形式。

3.3 Win32 API版本的TCP通信程序

在项目2.2中,已经实现了一个能互相发送消息的TCP通信程序,但由于是控制台程序,不仅界面不够友好,还存在客户端和服务器端每次只能发送一条消息给对方的不足。本节我们将控制台程序转换成Windows对话框界面的程序。读者主要掌握控制台程序转换成Windows对话框程序的方法。该程序服务器端和客户端的运行界面如图3-15所示。

 

图3-15 Win32 API版本的TCP通信程序(左图为服务器端,右图为客户端)

3.3.1 控制台程序改造成Windows程序的方法

在图3-15的服务器端程序中,主要涉及如下4个Windows消息:

① 窗口初始化消息(WM_INITDIALOG);

② “发送数据”按钮消息(WM_COMMAND中的IDC_SEND);

③ “接收数据”按钮消息(WM_COMMAND中的IDC_RECV);

④ “退出”按钮消息(WM_COMMAND中的IDC_QUIT)。

如果要转换成Windows程序,控制台版TCP通信程序中各个函数对应的语句应分别放到上述4个消息的处理函数中。如图3-16所示。

 

socket()

bind()

accept()

获取编辑框中的内容

send()

 

listen()

WM_INITDIALOG

IDC_SEND

清空编辑框中的内容

将已发送的消息添加到列表框中

recv()

 

IDC_RECV

将接收到的消息添加到列表框中

WSAStartup()

设置编辑框初始显示的内容

closesocket ()

 

IDC_QUIT

EndDialog()

WSACleanup() ()

 

 

图3-16 TCP通信程序的WinSock函数调用流程

另外,控制台程序中只有一个main函数,而改造成Windows界面程序后,存在多个函数,这些函数都需要使用Socket及SOCKADDR_IN等套接字和地址变量,因此应该将套接字和套接字地址的声明写在所有函数之外,使它们成为公共变量,让所有的函数都可使用。

一般来说,编写Windows对话框程序的方法如下:

① 将公共变量的声明(包括声明套接字和套接字地址)写在所有函数之外,使所有函数都可以使用这些公共变量。

② 将初始化WinSock协议栈的代码写到WM_INITDIALOG初始化事件中。

③ 将创建套接字、以及监听、接受连接的代码写到“创建服务器”的按钮消息中。

④ 将获取用户消息、发送消息的代码写在“发送”按钮的消息处理函数中。

⑤ 将接收消息、并显示消息的代码写在“接收”按钮的消息处理函数中。

⑥ 将关闭套接字的代码写到“退出”按钮或WM_QUIT的消息处理函数中。

3.3.2 服务器端程序制作步骤

1)新建工程,选择“Win32 Application”,输入工程名(如WinApi_Server),然后选择“一个典型的Hello World程序”。

2)在左侧选择FileView选项卡,找到对应的源文件(如WinApi_Server.cpp),将WinMain函数中DialogBox一行(148行)和return 0; 保留,其他代码全部删除,再将DialogBox中第3个参数HWnd改为NULL。

3)切换到ResourceView选项卡,找到Dialog下的“IDD_ABOUTBOX”,将对话框的界面改为如图3-17所示,并设置各个控件的ID值。

图3-17 服务器端程序界面设计和控件ID值

提示:对于ListBox列表框控件,在其属性面板“样式”选项卡中,“分类”应取消勾选,可保证添加的消息从上到下按顺序显示。

4)打开WinApi_Server.cpp文件,编写如下代码:

#include "stdafx.h"

#include "resource.h"

#include<WINSOCK2.h>

#pragma comment(lib,"ws2_32.lib")

HINSTANCE hInst;      

LRESULT CALLBACK  About(HWND, UINT, WPARAM, LPARAM);

 

SOCKET sockSer,sockConn;                     //服务器端要创建两个套接字

SOCKADDR_IN addrSer, addrCli;

int iIndex=0;   int len =sizeof(SOCKADDR);

char sendbuf[256],recvbuf[256];

char clibuf[999]="客户端: >",serbuf[999]="服务器: >";

 

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow){  

       DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, NULL, (DLGPROC)About);

       return 0; 

}

LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam,

LPARAM lParam){

       char ip[16],port[5];      

       switch (message)   {

       case WM_INITDIALOG:                    //对话框初始化

              SetDlgItemText(hDlg,IDC_IP,"127.0.0.1");  //设置ip文本框的内容

              SetDlgItemText(hDlg,IDC_PORT,"5566");   //设置端口文本框的内容

              WSADATA wsaData;

              if(WSAStartup(MAKEWORD(2,2), &wsaData)) {

                     MessageBox(hDlg,"Winsock加载失败","警告",0);

                     WSACleanup();

                     return -1;        }

              sockSer=socket(AF_INET,SOCK_STREAM,0);        

              return TRUE;        

       case WM_COMMAND:{

                     switch(LOWORD(wParam) )

                     { case IDC_QUIT:        //单击了退出按钮

                     EndDialog(hDlg, LOWORD(wParam));

                     closesocket(sockSer);    //关闭套接字 

                     WSACleanup();             //卸载WinSock协议栈

                     return TRUE;

                    

                     case IDC_CREATE:              //单击了创建服务器按钮

                            GetDlgItemText(hDlg,IDC_IP,ip,16);    //获取编辑框中的IP值

                            GetDlgItemText(hDlg,IDC_PORT,port,5);

                            EnableWindow(GetDlgItem(hDlg,IDC_CREATE),FALSE); //禁用按钮

                            addrSer.sin_family=AF_INET;

                            addrSer.sin_port=htons(atoi(port));

                            addrSer.sin_addr.S_un.S_addr=inet_addr(ip);

                            bind(sockSer,(SOCKADDR*) &addrSer,len);     //绑定套接字

                            listen(sockSer,5);                 //监听

                            sockConn=accept(sockSer,(SOCKADDR*) &addrCli,&len);//接受连接

                            if(sockConn!=INVALID_SOCKET)

                                   SendDlgItemMessage(hDlg,IDC_RECVBUF,LB_ADDSTRING,0,

                            (LPARAM)"客户端已经连接上");

                            break;

                     case IDC_SEND:                        //单击了发送按钮

                            GetDlgItemText(hDlg,IDC_SENDBUF,sendbuf,256);

                            send(sockConn,sendbuf,strlen(sendbuf)+1,0);     //发送消息

                            SetDlgItemText(hDlg,IDC_SENDBUF,"");          //清空编辑框

                            strcat(serbuf,sendbuf);

                                   //将已发送的消息添加到列表框中

              SendDlgItemMessage(hDlg,IDC_RECVBUF,LB_ADDSTRING,0,(LPARAM)serbuf);

                     strcpy(serbuf, "服务器: >");

                            break;

                           

                     case IDC_RECV:                  //单击了接收按钮

                            recv(sockConn,recvbuf,256,0);

                            strcat(clibuf,recvbuf);

                     //将接收到的消息添加到列表框中

       SendDlgItemMessage(hDlg,IDC_RECVBUF,LB_ADDSTRING,0,(LPARAM)clibuf);

              strcpy(clibuf, "客户端: >");                       

                            break;

                     }            }            }    

       return FALSE;        }

提示:向列表框中添加内容,需要使用的函数如下:

SendDlgItemMessage(hDlg,IDC_RECVBUF,LB_ADDSTRING,0,(LPARAM)clibuf);

其中,第1个参数是对话框的句柄,第2个参数是列表框的ID,第3个参数是控件消息名,其中LB_表示Listbox,ADDSTRING表示添加字符串,第5个参数是要添加的内容。

LB_SETTOPINDEX的作用是,当列表框中的内容很多显示不下时,该消息能让列表框自动滚动使当前项能显示出来。

3.3.3 客户端程序制作步骤

1)新建工程,选择“Win32 Application”,输入工程名(如Win_client),然后选择“一个典型的Hello World程序”。

2)在左侧选择FileView选项卡,找到对应的源文件(如Win_client.cpp),将WinMain函数中DialogBox一行和return 0; 保留,其他代码全部删除,再将DialogBox中第3个参数HWnd改为NULL。

3)切换到ResourceView选项卡,找到Dialog下的“IDD_ABOUTBOX”,将对话框的界面改为如图3-18所示,并设置各个控件的ID值。

图3-18 客户端程序界面设计和控件ID值

4)打开Win_client.cpp文件,编写代码如下:

#include "stdafx.h"

#include "resource.h"

#include<WINSOCK2.h>

#pragma comment(lib,"ws2_32.lib")

HINSTANCE hInst;

LRESULT CALLBACK  About(HWND, UINT, WPARAM, LPARAM);

 

SOCKET sockCli;                //声明客户端套接字

SOCKADDR_IN addrSer, addrCli;

 

int APIENTRY WinMain(HINSTANCE hInstance,   HINSTANCE hPrevInstance,  

LPSTR  lpCmdLine, int  nCmdShow){    

       DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, NULL, (DLGPROC)About);

       return 0;

}

LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam,

LPARAM lParam){

       char ip[16], port[5];

       char sendbuf[256],recvbuf[256]; 

       char serbuf[256]="服务器: >", clibuf[256]="客户端: >";

       int len =sizeof(SOCKADDR);

 

       switch (message){

       case WM_INITDIALOG:                          //对话框初始化

              SetDlgItemText(hDlg,IDC_IP,"127.0.0.1");

              SetDlgItemText(hDlg,IDC_PORT,"5566");

              WSADATA wsaData;

              WSAStartup(MAKEWORD(2,2), &wsaData);            //加载协议栈

              sockCli=socket(AF_INET,SOCK_STREAM,0);          //创建套接字

              break;

             

       case WM_COMMAND:

              {     switch(LOWORD(wParam)) {

       case IDC_QUIT:                                //单击了退出按钮

              EndDialog(hDlg, LOWORD(wParam));

              closesocket(sockCli);

              WSACleanup();

              break;

             

       case IDC_CONN:                              //单击了“连接服务器”按钮

              GetDlgItemText(hDlg,IDC_IP,ip,16);           //获取ip

              GetDlgItemText(hDlg,IDC_PORT,port,5);

              EnableWindow(GetDlgItem(hDlg,IDC_CONN),FALSE);    //禁用连接按钮

              addrSer.sin_family=AF_INET;

              addrSer.sin_port=htons(atoi(port));

              addrSer.sin_addr.S_un.S_addr=inet_addr(ip);            

              if(connect(sockCli,(SOCKADDR*)&addrSer,sizeof(SOCKADDR)))

                     MessageBox(NULL,"客户端连接服务器失败","警告",0);                      

              else 

                     MessageBox(NULL,"客户端连接服务器成功","通知",0);        

              break;

             

       case IDC_SEND:          //单击了“发送”按钮

              GetDlgItemText(hDlg,IDC_SENDBUF,sendbuf,256);         //获得发送框的内容

              send(sockCli,sendbuf,strlen(sendbuf)+1,0);        //发送消息

              SetDlgItemText(hDlg,IDC_SENDBUF,"");          //清空发送框

              strcat(clibuf,sendbuf);                       

              SendDlgItemMessage(hDlg,IDC_RECVBUF,LB_ADDSTRING,0,(LPARAM)clibuf);

              strcpy(clibuf, "客户端: >");  //重新给字符串赋值   

              break;

 

       case IDC_RECV:                 //单击了“接收”按钮

              recv(sockCli,recvbuf,256,0);               //接收消息

              strcat(serbuf,recvbuf);

              SendDlgItemMessage(hDlg,IDC_RECVBUF,LB_ADDSTRING,0,(LPARAM)serbuf);

              strcpy(serbuf, "服务器: >");  //重新给字符串赋值

              break;            }

              }            }    

return FALSE;              }

习题

1. 每个按钮的ID属性存放在WM_COMMAND消息的        中。       (           )

A. wParam的高位字节中                         B. wParam的低位字节中

C. lParam的高位字节中                           D. lParam的低位字节中

2.下列哪个函数可以返回本机的IP地址:                                (             )

A. gethostbyname()                            B. gethostname()

C. gethostbyaddr()                             D. gethostaddr()

3. WM_COMMAND表示                         ,WM_INITDIALOG表示                       

4. 要设置编辑框显示的内容,应使用                                  函数。

5. 要获取编辑框显示的内容,应使用                                  函数。

6. 要向列表框中添加一行文本,应使用                       函数。

7. 在Windows API程序中,对话框是通过                           函数创建的。

8. 在VC6中,怎样保证向列表框Listbox中添加消息时,消息总是从上到下按时间顺序显示?

9. (实验)为3.3节的程序添加显示消息发送时间的功能。

光 盘 说 明 为了方便读者习,本书附带了一张光盘,下面对光盘内容及使用方法进行简要的介绍。 1.光盘的运行环境 硬件环境:CPU的主频在200MHz以上、内存在64MB以上。 软件平台:操作系统为Windows 98/Me/NT/2000/XP(推荐使用Windows 2000/XP),调试环境为Visual C++ 6.0及其以上版本(如果不做说明,则默认为Visual C++ 6.0)。 2.光盘的使用方法及注意事项 将本书的源代码拷入硬盘,用Visual C++打开项目文件,即可编译运行。 3.光盘的主要内容 光盘存放的是书涉及的所有实例的源代码和经过编译后的应用程序。所有程序均经过测试,仅供读者习时使用,不能用作其他商业用途。如果使用时遇到任何问题,请发Email至Fourwei@zj.com,我们将提供全面的解答。 4.光盘目录 (1)基本网络编程实例 Winsock实现网络聊天室【\chap1\ChatRoom(Winsock)】 CSocket实现聊天室【\chap1\ChatRoom(Csocket)】 (2)本地计算机网络编程实例 获取计算机的名称和IP地址【\chap2\Local】 获取计算机的子网掩码【\chap2\ Local】 获取计算机的DNS设置【\chap2\ Local】 获取计算机的网卡地址【\chap2\ Local】 获取计算机安装的协议【\chap2\ Local】 获取计算机提供的服务【\chap2\ Local】 获取计算机的所有网络资源【\chap2\ Local】 修改本地计算机的所有网络设置【\chap2\ Local】 获取计算机TCP/IP协议的所有信息【\chap2\ Local】 (3)局域网网络通信编程实例 获取网上邻居【\chap3\Neighbor】 lIP地址和计算机名之间的转换【\chap3\Neighbor】 l映射网络驱动器【\chap3\Neighbor】 l消息发送程序Net Send【\chap3\Neighbor】 l获取局域网内其他计算机的信息【\chap3\ NeighborInfo】 (4)IE编程实例 简单的浏览器的实现【\chap4\MyBrowser】 删除IE相关历史记录【\chap4\DelHistory】 将应用程序加入到IE工具栏【\chap4\AddToToolBar】 超级链接的实现【\chap4\HyperLink】 禁止IE的弹出窗口【\chap4\StopPopup】 禁止浏览某些网站【\chap4\StopTravel】 IE收藏夹【\chap4\ MyBrowser】 创建桌面快捷方式和活动桌面【\chap4\ShortCut】 (5)基本网络编程实例 点对点文件传输【\chap5\Transfer】 大型文件传输【\chap5\Transfer】 端口扫描程序【\chap5\ MyPortScanner】 Finger编程【\chap5\MyFinger】 Sniff编程【\chap5\MySniff】 Internet文件下载【\chap5\ InternetDownload】 (6)网络通信协议编程 FTP协议【\chap6\FTP】 Email协议【\chap6\Email】 ICMP协议【\chap6\ICMP】 RAS协议【\chap6\RAS】 TAPI协议【\chap6\TAPI】 Telnet协议【\chap6\Telnet】 HTTP协议 【\chap6\HTTP】 (7)Modem /串口通信编程 Modem编程【\chap7\Modem】 MSCOMM控件编程【\chap7\MSCOMM】 串口通信API编程【\chap7\MySerialCom】 (8)代理服务器编程实例 Socks 5协议编程【\chap8\Socks5】 HTTP代理服务器【\chap8\HTTP代理服务】 (9)高级网络通信编程实例 串口通信编程实例【\chap9\SerialPort】 网络流量监控【\chap9\NetTraffic】 网站下载【\chap9\ Snag】 网络五子棋系统【\chap9\FiveChess】 语音聊天【\chap9\ ChatRoom】 远程监控【\chap9\RemoteControl】 赠送实例 类似网络蚂蚁的断点续传程序【\Appendix\NetAnts】 网络多播程序【\Appendix\BroadCast】 界面美观的文字聊天程序【\Appendix\Chat】 语音电话【\Appendix\PhoneCall】
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值