在4.2节中使用Win32 API方法制作了一个TCP异步通信的程序,本节将4.2节的程序用MFC框架重新编写,改写后程序的界面如图6-15所示,功能与4.2节的程序完全相同。
图6-15 MFC版TCP异步通信程序的界面
6.3.1 服务器端程序的制作
1)创建一个MFC工程:新建工程,选择“MFC APPWizard(exe)”,输入工程名(如TCPzdy),单击“下一步”,在步骤1选择“基本对话框”,单击“完成”按钮。
2)在ResourceView选项卡,找到Dialog下的“IDD_TCPZDY_DIALOG”,将对话框的界面改为如图6-16所示,并设置各个控件的ID值。
图6-16 服务器端程序的界面
3)按“Ctrl+W”键,或者在对话框界面上按右键,选择“建立类向导”,打开“MFC类向导”对话框,在“Member Variables”选项卡中为控件设置成员变量如图6-17所示。
图6-17 设置成员变量
4)在*dlg.h文件中,添加如下引用头文件和声明自定义消息的代码。
#include "winsock2.h"
#define WM_SOCKET WM_USER+1 //自定义消息
#pragma comment(lib,"ws2_32.lib")
5)在*dlg.h文件中,声明套接字和地址变量。
class CTCPzdyDlg : public CDialog{
public:
CTCPzdyDlg(CWnd* pParent = NULL); // standard constructor
SOCKET sockSer, sockConn; //声明两个套接字变量
SOCKADDR_IN addrSer, addrCli;
……}
6)在*dlg.cpp文件中,为对话框的OnInitDialog()函数加入对话框初始化代码:
BOOL CTCPzdyDlg::OnInitDialog(){
m_ip="127.0.0.1";
m_port="5566";
UpdateData(FALSE);
c_send.EnableWindow(FALSE);
return TRUE; }
7)双击“创建服务器”按钮,为“创建服务器”按钮编写如下代码:
void CTCPzdyDlg::OnCreate() {
WSADATA wsaData;
WORD sockVersion=MAKEWORD(2,2);
if(WSAStartup(sockVersion,&wsaData)) {
AfxMessageBox(_T("初始winsock函数库失败!"),MB_OK|MB_ICONSTOP);
}
HWND hwnd = AfxGetMainWnd()->m_hWnd; //获得窗口句柄
sockSer=socket(AF_INET,SOCK_STREAM,0);
//设置异步方式
WSAAsyncSelect(sockSer, hwnd,WM_SOCKET,FD_ACCEPT |FD_READ |FD_CLOSE);
UpdateData();
addrSer.sin_family=AF_INET;
addrSer.sin_port=htons(atoi(m_port));
addrSer.sin_addr.S_un.S_addr=inet_addr(m_ip);
int len =sizeof(SOCKADDR);
bind(sockSer,(SOCKADDR*) &addrSer,len); //绑定地址
listen(sockSer,5); //监听
c_send.EnableWindow(TRUE);
c_create.EnableWindow(FALSE);
}
8)从本步骤到第10)步,将采用自定义消息的方式实现程序异步接收消息功能。在*dlg.h文件中,声明消息处理函数,在“//{{AFX_MSG(CTCPzdyDlg)”下添加下面一行:
afx_msg LRESULT OnRecvData(WPARAM wParam,LPARAM lParam);
9)在*dlg.cpp文件中,创建消息映射,在BEGIN_MESSAGE_MAP(CTCPzdyDlg, CDialog)下面添加如下消息映射代码:
ON_MESSAGE(WM_SOCKET,OnRecvData)
10)在*dlg.cpp文件中,编写自定义消息的处理函数,代码如下:
CString clibuf="客户端: >"; CString serbuf="服务器: >";
int len =sizeof(SOCKADDR);
LRESULT CTCPzdyDlg::OnRecvData(WPARAM wParam,LPARAM lParam) {
SOCKET s=wParam; //发生事件的套接字标识符由wParam通知
char recvbuf[256];
if(WSAGETSELECTERROR(lParam)) { //如果选择事件出错
closesocket(s);
return false; }
switch (WSAGETSELECTEVENT(lParam)) { //事件名由lParam通知
case FD_ACCEPT: { //接收请求事件
sockConn=accept(sockSer,(SOCKADDR*) &addrCli,&len);
}
break;
case FD_READ: { //可读事件
recv(sockConn,recvbuf,256,0);
clibuf+=recvbuf;
c_recvbuf.AddString(clibuf);
clibuf="客户端: >"; //重新给字符串赋值
}
break;
case FD_CLOSE: { //关闭连接事件
AfxMessageBox( "客户端已断开连接"); }
break;
default:
break; }
return true;
}
11)双击“发送”按钮,为“发送”按钮编写发送消息的代码:
void CTCPzdyDlg::OnSend() {
char buff[200];
char * ct;
CTime time = CTime::GetCurrentTime(); //获取当前时间
CString t = time.Format(" %H:%M:%S"); //设置时间显示格式
ct=(char*)t.GetBuffer(0); //cstring 转 char*
c_sendbuf.GetWindowText(buff,200);
c_sendbuf.SetWindowText(NULL);
CString Ser="服务器: >";
strcat(buff,ct);
send(sockConn,buff,strlen(buff)+1,0);
c_recvbuf.AddString(Ser+buff);
}
总结:
本程序中使用了自定义消息WM_SOCKET,在MFC中自定义消息可分为4步:
第1步:自定义消息,例如:#define WM_SOCKET WM_USER+1
第2步:声明消息处理函数
afx_msg LRESULT OnRecvData(WPARAM wParam, LPARAM lParam)
(以上两步都写在*dlg.h头文件中)
第3步:创建消息映射
ON_MESSAGE(WM_SOCKET,OnRecvData)
写在:BEGIN_MESSAGE_MAP(CTCPzcliDlg, CDialog)一行的下面。
第4步 编写消息处理函数
LRESULT CTCPzdyDlg::OnRecvData(WPARAM wParam,LPARAM lParam)
{ …… }
(以上两步都写在*dlg.cpp文件中)
与4.2节的Win32 API版程序相比,本节的程序必须在OnRecvData()函数中,获取套接字标识符,方法是:SOCKET s=wParam;。这样才能在自定义函数中访问套接字。
提示:如果是VS,则在“MFC类向导”对话框的消息选项卡中,单击“添加自定义消息”,在弹出的对话框中输入自定义消息名和消息处理程序名,就可自动生成上述自定义消息的代码。
6.3.2 客户端程序的制作
1)创建一个MFC工程:新建工程,选择“MFC APPWizard(exe)”,输入工程名(如TCPzcli),单击“下一步”,在步骤1选择“基本对话框”,单击“完成”按钮。
2)在ResourceView选项卡,找到Dialog下的“IDD_TCPZCLI_DIALOG”,将对话框的界面改为如图6-18所示,并设置各个控件的ID值。
图6-18 客户端程序界面及控件ID
3)按“Ctrl+W”键,或者在对话框界面上按右键,选择“建立类向导”,打开“MFC类向导”对话框,在“Member Variables”选项卡中为控件设置成员变量如图6-19所示。
图6-19 设置成员变量
4)在*dlg.h文件中,添加如下引用头文件和声明自定义消息的代码。
#include "winsock2.h"
#define WM_SOCKET WM_USER+1 //自定义消息
#pragma comment(lib,"ws2_32.lib")
5)在*dlg.h文件的对话框类中,声明套接字变量和地址变量。代码如下
class CTCPzcliDlg : public CDialog{
public:
CTCPzcliDlg(CWnd* pParent = NULL); // standard constructor
SOCKET sockCli; //客户端套接字
SOCKADDR_IN addrSer, addrCli;
…… }
6)在*dlg.cpp文件中,为对话框的OnInitDialog()函数加入对话框初始化代码:
BOOL CTCPzcliDlg::OnInitDialog(){
m_ip="127.0.0.1"; //!!!
m_port="5566";
UpdateData(FALSE);
c_send.EnableWindow(FALSE);
return TRUE; // return TRUE unless you set the focus to a control
}
7)双击“连接服务器”按钮,为“连接服务器”按钮编写如下代码:
void CTCPzcliDlg::OnConn() {
WSADATA wsaData;
WORD sockVersion=MAKEWORD(2,2);
if(WSAStartup(sockVersion,&wsaData)) {
AfxMessageBox(_T("初始winsock函数库失败!"),MB_OK|MB_ICONSTOP);
}
HWND hwnd = AfxGetMainWnd()->m_hWnd;
sockCli=socket(AF_INET,SOCK_STREAM,0);
WSAAsyncSelect(sockCli, hwnd,WM_SOCKET,FD_CONNECT |FD_READ | FD_CLOSE); //设置异步方式
UpdateData();
addrSer.sin_family=AF_INET;
addrSer.sin_port=htons(atoi(m_port));
addrSer.sin_addr.S_un.S_addr=inet_addr(m_ip);
int res=connect(sockCli,(SOCKADDR*)&addrSer,sizeof(SOCKADDR));
if(res==0){ AfxMessageBox("客户端连接服务器失败");}
else
AfxMessageBox("客户端连接服务器成功");
c_send.EnableWindow(TRUE);
c_conn.EnableWindow(FALSE);
}
8)在*dlg.h文件中,声明消息处理函数,在“//{{AFX_MSG(CTCPzcliDlg)”下添加下面一行:
afx_msg LRESULT OnRecvData(WPARAM wParam,LPARAM lParam);
9)在*dlg.cpp文件中,创建消息映射,在BEGIN_MESSAGE_MAP(CTCPzdyDlg, CDialog)下面添加下面一行:
ON_MESSAGE(WM_SOCKET,OnRecvData)
10)在*dlg.cpp文件中,编写自定义消息的处理函数,代码如下:
CString serbuf="服务器: >";
LRESULT CTCPzcliDlg::OnRecvData(WPARAM wParam,LPARAM lParam){
SOCKET s=wParam;
CString strContent;
char recvbuf[256];
if(WSAGETSELECTERROR(lParam)) {
closesocket(s);
return false; }
switch (WSAGETSELECTEVENT(lParam)) {
case FD_CONNECT: //接收请求事件
{ }
break;
case FD_READ: { //可读事件
recv(sockCli,recvbuf,256,0);
serbuf+=recvbuf;
c_recvbuf.AddString(serbuf);
serbuf="客户端: >"; //重新给字符串赋值
}
break;
case FD_CLOSE: //关闭连接事件
{AfxMessageBox( "正常关闭连接"); }
break; }
return true;
}
11)双击“发送”按钮,为“发送”按钮编写如下代码:
void CTCPzcliDlg::OnSend() {
char buff[200];
char * ct;
CTime time = CTime::GetCurrentTime(); //获取当前时间
CString t = time.Format(" %H:%M:%S"); //设置时间显示格式
ct=(char*)t.GetBuffer(0); //cstring 转 char*
c_sendbuf.GetWindowText(buff,200);
c_sendbuf.SetWindowText(NULL);
CString Cli="客户端: >";
strcat(buff,ct);
send(sockCli,buff,strlen(buff)+1,0);
c_recvbuf.AddString(Cli+buff);
}
12)当单击“退出”按钮时,关闭套接字,实现方法是:在“工程名.cpp”文件中,找到按钮IDOK的消息处理代码,修改如下:
int nResponse = dlg.DoModal();
if (nResponse == IDOK) {
closesocket(dlg.sockCli); //这句是添加的代码,用来关闭套接字
}
总结:将Win32API程序转换成MFC WinSock程序的步骤如下:
① 将Win32 API程序中引用的头文件都放到*dlg.h文件中;
② 创建的套接字变量和地址变量放到*dlg.h文件:class *Dlg{}类的public变量中;
③ WSAStartup() socket() bind() listen()放到”启动服务器”按钮或OnInitDialog() 函数中;
④ accept()函数放到FD_ACCEPT事件(异步)或OnInitDialog() 函数中(同步);
⑤ send()函数放到“发送”按钮中;
⑥ recv()函数放到“接收”按钮或FD_READ事件中。
习题
1. 在c_send.EnableWindow(TRUE)中,c_send是变量(填控件或值)。
UpdateData(FALSE)的作用是,UpdateData()的作用是。
2. 在MFC中,要获取单个编辑框中的文本,可使用函数,要设置单个编辑框的文本,可使用。
3. 对于m_result=itoa(result,temp,10);,itoa函数的功能是,其中第3个参数10表示。
4. 在MFC中,要向列表框中添加内容,需要使用函数。
5. MFC中,要弹出一个打开文件对话框,需要用到类。
6. 要弹出一个模态对话框,需要使用对话框类的对象的方法。
7. IDC_btn.EnableWindow(TRUE);表示。
8. 在MFC中,怎样把字符数组转换为CString字符串型数据?