16.5基于消息的异步套接字
//在很多情况下,阻塞方式会影响应用程序的性能,所以有时需要采用非阻塞方式实现网络应用程序,有很多机制可以实现这种方式。采用异步套接字能够有效提高应用程序的性能。
16.5.1相关函数的说明
//WSAAsyncSelect函数
int WSAAsyncSelect (
SOCKET s, //套接字描述符
HWND hWnd, //接收消息的窗口句柄
unsigned int wMsg, //窗口收到的消息
long lEvent //指定应用程序感兴趣的网络事件
);
//该函数为指定套接字请求基于Windows消息的网络事件通知,并自动该套接字设置为非阻塞模式
//WSAEnumProtocols
int WSAEnumProtocols (
LPINT lpiProtocols, //如果NULL表示返回所有可用协议的信息
LPWSAPROTOCOL_INFO lpProtocolBuffer, //作为返回值使用
ILPDWORD lpdwBufferLength //第二个参数缓冲区的长度
);
//该函数可以获得系统中安装的网络协议的相关信息
//WSAStartup
int WSAStartup (
WORD wVersionRequested, //套接字库的版本
LPWSADATA lpWSAData //用来接收WindowsSocket实现的细节
);
//该函数将初始化进程使用的WS2_32.DLL。
//WSACleanup
int WSACleanup (void);
//该函数将终止程序对套接字库(WS2_32.DLL)的使用
//WSASocket
SOCKET WSASocket (
int af,
int type,
int protocol,
LPWSAPROTOCOL_INFO lpProtocolInfo, //定义所创建套节字特性
GROUP g, //保留
DWORD dwFlags //指定套接字的属性
);
//该函数将创建套接字,前三个参数与socket函数前三个参数一致。
//WSARecvFrom
int WSARecvFrom (
SOCKET s, //套接字描述符
LPWSABUF lpBuffers,//WSABUF结构体,包括缓冲区指针和缓冲区长度
DWORD dwBufferCount, //WSABUF结构体的数目
LPDWORD lpNumberOfBytesRecvd, //接收字节数的指针
LPDWORD lpFlags, //指向标志位的指针
struct sockaddr FAR * lpFrom, //指向重叠操作完成后存放源地址的缓冲区
LPINT lpFromlen, //lpFrom缓冲区的大小
LPWSAOVERLAPPED lpOverlapped, //指向WSAOVERLAPPED结构体
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE
);
//接收数据报类型的数据,并保存数据发送方的地址
//WSASendTo
int WSASendTo (
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags, //指示影响操作为标志
const struct sockaddr FAR * lpTo, //指向目标套接字地址
int iToLen, // lpTo中地址的长度
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE
);
16.5.2网络聊天程序的实现
//创建一个基于对话框的工程:Chat。并添加一些控件,与15章Chat程序界面完全相同。
//1、加载套接字库。与之前加载套接字库函数不同的是,本程序需要加载套接字库2.0版本,因此应该调用WSAStartup函数初始化程序所使用的套接字库。即在CChatApp类的InitInstance函数开始位置加载套接字。
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested=MAKEWORD(2,2);
err=WSAStartup(wVersionRequested,&wsaData);
if(err!=0)
{
return FALSE;
}
if(LOBYTE(wsaData.wVersion)!=2||HIBYTE(wsaData.wVersion)!=2)
{
WSACleanup();
return FALSE;
}
//然后再stdafx.h文件中添加:#include<winsock2.h>,同时还需要为该工程链接ws2_32.lib文件
//2、创建并初始化套接字
//与上一章程序一样,为对话框类添加一个SOCKET类型的成员变量:m_socket,然后将其权限设置为私有。同时为对话框类添加一个BOOL类型的成员函数:InitSocket函数,用来初始化套接字变量:
BOOL CChatDlg::InitSocket()
{
m_socket=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,0);
if(INVALID_SOCKET==m_socket)
{
MessageBox("创建套接字失败");
return FALSE;
}
SOCKADDR_IN addrSock;
addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSock.sin_family=AF_INET;
addrSock.sin_port=htons(6000);
if(SOCKET_ERROR==bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR)))
{
MessageBox("绑定失败!");
return FALSE;
}
if(SOCKET_ERROR==WSAAsyncSelect(m_socket,m_hWnd,UM_SOCK,FD_READ))//第三个参数是一个用户自定义信息
{
MessageBox("注册网络读取事件失败");
return FALSE;
}
retun TRUE;
}
//然后在OnInitDialog函数最后return语句之前添加:InitSocket();然后在CChatDlg头文件中定义自定义消息:UM_SOCK。
#define UM_SOCK WM_USER+1
//3.实现接收端的功能
//首先添加UM_SOCK消息响应函数原型的声明:
//}}AFX_MSG
afx_msg void OnSock(WPARAM,LPARAM);
DECLARE_MESSAGE_MAP()
//添加UM_SOCK消息映射
//}}AFX_MSG_MAP
ON_MESSAGE(UM_SOCK,OnSock)
END_MESSAGE_MAP()
//消息响应函数的实现
void CChatDlg::OnSock(WPARAM wParam,LPARAM lParam)
{
switch(LOWORD(lParam))
{
case FD_READ:
WSABUF wsabuf;
wsabuf.buf=new char[200];
wsabuf.len=200;
DWORD dwRead;
DWORD dwFlag=0;
SOCKADDR_IN addrFrom;
int len=sizeof(SOCKADDR);
CString str;
CString strTemp;
if(SOCKET_ERROR==WSARecvFrom(m_socket,&wsabuf,1,&dwRead,&dwFlag,(SOCKADDR*)&addrFrom,&len,NULL,NULL))
{
MessageBox("接收数据失败");
delete[] wsabuf.buf;
return;
}
str.Format("%s说%s",inet_ntoa(addrFrom.sin_addr),wsabuf.buf);
str+="\r\n";
GetDlgItemText(IDC_EDIT_RECV,strTemp);
str+=strTemp;
SetDlgItemText(IDC_EDIT_RECV,str);
delete[] wsabuf.buf;
break;
}
}
//为了让编辑框支持回车换行,还需要设置Multiline属性
//4、实现发送端功能
//双击发送按钮,生成一个按钮单击命令响应函数:OnBtnSend,然后在此函数中添加代码实现数据发送功能:
void CChatDlg::OnButtonSend()
{
// TODO: Add your control notification handler code here
DWORD dwIP;
CString strSend;
WSABUF wsabuf;
DWORD dwSend;
int len;
SOCKADDR_IN addrTo;
((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
addrTo.sin_family=AF_INET;
addrTo.sin_port=htons(6000);
addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
GetDlgItemText(IDC_EDIT_SEND,strSend);
len=strSend.GetLength();
wsabuf.buf=strSend.GetBuffer(len);
wsabuf.len=len+1;
SetDlgItemText(IDC_EDIT_SEND,"");
if(SOCKET_ERROR==WSASendTo(m_socket,&wsabuf,1,&dwSend,0,(SOCKADDR*)&addrTo,sizeof(SOCKADDR),NULL,NULL))
{
MessageBox("发送数据失败!");
return;
}
}
//运行就能实现与相应的功能了。
//5、终止套接字库的使用
//最后还需要为CChatApp类增加一个析构函数,主要是要在此函数中调用WSACleanup函数,终止对套接字库的使用。首先在头文件中声明该函数,然后在源文件中添加该函数的定义:
CChatApp::~CChatApp()
{
WSACleanup();
}
//同样地为对话框类添加一个析构函数,调用closesocket函数关闭套接字,释放套接字相关资源:
CChatDlg::~CChatDlg()
{
if(m_socket)
{
closesocket(m_socket);
}
}
//6、利用主机名实现网络访问
//程序中需要将主机名转换为IP地址,可以通过gethostbyname函数完成这种转换:
struct hostent FAR * gethostbyname (
const char FAR * name
);
//该函数只有一个指向空终止的字符串一个参数。并且返回一个hostent结构体类型的指针。
struct hostent {
char FAR * h_name;
char FAR * FAR * h_aliases;
short h_addrtype;
short h_length;
char FAR * FAR * h_addr_list;
};
//该结构体最后一个成员h_addr_list是一个指针数组,它的每一个元素存放的是一个以网络字节顺序表示的主机IP地址。因为利用主机名查询IP地址时,可能返回多个IP地址,所以需要用一个指针数组来进行存放。
//首先在Chat程序的对话框中添加一个编辑框,允许用户输入对方的主机名,并将其ID设置为IDC_EDIT_HOSTNAME。然后再OnBtnSend函数中定义一个CString对象类型的对象strHostName,用来保存用户输入的主机名;定义一个HOSTENT结构类型的指针,以便gethostbyname函数使用:
CString strHostName;
HOSTENT* Phost;
//并将OnBtnSend函数中代码修改为:
if(GetDlgItemText(IDC_EDIT_HOSTNAME,strHostName),strHostName=="")
{
((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
}
else
{
pHost=gethostbyname(strHostName);
addrTo.sin_addr.S_un.S_addr=*((DWORD*)pHost->h_addr_list[0]);
}
//如果获得的主机名为空,那么就像先前一样,获得IP地址控件上的IP地址;如果主机名不为空,就会获得主机名对应的IP地址,因为指针数组里面存放的元素是char类型,需要转换成DWORD类型,再用*取出指针所指向的内存中的数据。
//在接收到对方发送的数据之后,需要将对方的IP地址转换成对方机器的主机名,这里可以通过另一个函数:gethostbyaddr函数来完成:
struct HOSTENT FAR * gethostbyaddr (
const char FAR * addr,
int len,
int type
);
//第一个参数是指向网络字节顺序表示的地址的指针,第二个参数是地址的长度,最后一个是地址类型,windows平台下必须设置为AF_INET。返回值就是HOSTENT结构体,该结构体中h_name数据成员就是主机名。
//接着在接收数据的消息响应函数:OnSock函数中,添加和修改如下代码:
//str.Format("%s说%s",inet_ntoa(addrFrom.sin_addr),wsabuf.buf);
HOSTENT *pHost; pHost=gethostbyaddr((char*)&addrFrom.sin_addr.S_un.S_addr,4,AF_INET);
str.Format("%s说%s",pHost->h_name,wsabuf.buf);
//运行结果如图所示