网络编程——基于消息的异步套接字编程
Windows套接字在两种模式下执行I/O操作,一种是阻塞模式,一种是非阻塞模式。在阻塞模式下,在I/O操作完成之前,执行I/O操作的Winsock函数会一直等待下去,不会立即返回程序,将控制权交回程序。在非阻塞模式下,Winsock函数都会立即返回。
在阻塞模式下编程,我们虽然可以使用各种技术使程序顺利运行,例如使用多线程技术。但是如果使用阻塞模式,当某一线程一直等待的话,就会一直占用系统的资源。不仅影响程序的运行效率而且浪费了系统资源。为了提高系统资源的利用率,我们可以使用非阻塞模式来进行Windows套接字编程。
在非阻塞模式下编程,使用的技术是基于消息的异步套接字。由于Windows是基于消息的,我们可以让Winsock函数立即返回。当指定的消息到来时,可以通过发送消息,在指定的消息响应函数中完成响应的操作。 Windows Sockets的异步选择函数WSAAsyncSelect()提供了消息机制的网络事件选择,当使用它登记的网络事件发生时,Windows应用程序相应的窗口函数将收到一个消息,消息中指示了发生的网络事件,以及与事件相关的一些信息。我们可以通过这个函数来注册网络事件。
另外:WSAEnumProtocols()函数获得系统中安装的网络协议的相关信息。
还用网络聊天程序实现基于消息的异步套接字编程
一、加载套接字库
在CWinApp::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;
}
二、初始化套接字信息
加载了套接字库后,我们就可以开始正式的Winsock编程了。按照步骤,我们应该先创建套接字,接着讲套接字绑定到指定的IP地址和端口上。那么在基于对话框的程序设计中,我们可以将这一系列操作放到一个对话框类的成员函数中来初始化这些套接字信息。因此我们为对话框类添加成员函数。
创建套接字函数我们使用socket的扩展函数WSASocket()。当创建套接字和绑定完成后,我们就可以通过WSAAsyncSelect()函数注册网络读取事件。该异步选择函数会立即返回,当注册的网络事件发生时,可以通过事件响应函数完成指定的操作(如:数据接收)。实现代码如下:
头文件中:
public:
SOCKET m_socket;
BOOL InitSocket(void);
源文件中:
/*****************************************************************************
初始化套接字
********************************************************************************/
BOOL CChatbeta2Dlg::InitSocket(void)
{
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,(const sockaddr*)&addrSock,sizeof(SOCKADDR)))
{
MessageBox("绑定套接字失败!");
return FALSE;
}
if(WSAAsyncSelect(m_socket,m_hWnd,UM_RECV,FD_READ))
{
MessageBox("注册网络读取事件失败!");
return FALSE;
}
return 0;
}
三、接收和发送消息
当注册的网络时间发生时,就可以通过消息响应函数完成数据读取工作。该消息是一个用户自定义消息。
#define UM_RECV WM_USER+1
读取消息的函数可以用socket的扩展函数WSARecvFrom(),该函数的第二个参数需要一个WSABUF的结构体指针,该结构体保存接收到的信息。
另外,我们要将接收到的信息写入接收消息的编辑框,以前我们用发送方的IP地址标示发送方,由于IP地址记忆不方便,所以我们想用用户名标示该用户。gethostbyaddr()函数可以通过指定的IP地址获得该主机的用户名。该函数的返回值HOSTENT结构体保存了用户的用户名和IP信息。
实现代码如下:
LRESULT CChatbeta2Dlg::OnRecv(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;
HOSTENT* pHost;
if(SOCKET_ERROR==WSARecvFrom(m_socket,&wsaBuf,1,&dwRead,&dwFlag,
(sockaddr*)&addrFrom,&len,NULL,NULL))
{
MessageBox("接收数据失败");
return FALSE;
}
pHost=gethostbyaddr((char*)&addrFrom.sin_addr.S_un.S_addr,4,AF_INET);
str.Format("%s说:%s",pHost->h_name,wsaBuf.buf);
str+="\r\n";
GetDlgItemText(IDC_EDIT_RECV,strTemp);
str+=strTemp;
SetDlgItemText(IDC_EDIT_RECV,str);
break;
}
return TRUE;
}
当单击发送按钮就可以发送数据,我们可以通过IP地址指定发送的目的主机,我们也可以通过用户名,让程序在网络中搜索该用户名的IP地址,然后发送信息。gethostbyname()函数可以通过用户名返回用户的IP信息。该函数的返回值HOSTENT结构体保存了用户的用户名和IP信息。发送消息我们用socket的扩展函数WSASendTo(),该函数的第二个参数需要一个WSABUF的结构体指针,该结构体保存接收到的信息。我们先判断用户是否通过指定的用户名发送信息,如果没有,我们通过指定的IP地址发送信息。实现代码如下:
void CChatbeta2Dlg::OnClickedBtnSend()
{
// TODO: Add your control notification handler code here
DWORD dwIP;
CString strSend;
WSABUF wsaBuf;
DWORD dwSend;
int len;
CString strHostName;
SOCKADDR_IN addrTo;
HOSTENT* pHost;
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]);
}
addrTo.sin_family=AF_INET;
addrTo.sin_port=htons(6000);
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,
(const sockaddr*)&addrTo,sizeof(SOCKADDR),NULL,NULL))
{
MessageBox("发送数据失败!");
return;
}
/*************************************************************
自己要发送的信息业显示出来
***************************************************************/
CString str;
CString strTemp;
char* pName=new char[100];
gethostname(pName,100);
str.Format("%s说:%s",pName,strSend);
str+="\r\n";
GetDlgItemText(IDC_EDIT_RECV,strTemp);
str+=strTemp;
SetDlgItemText(IDC_EDIT_RECV,str);
}
运行结果如下:
该程序用基于消息的异步套接字实现网络聊天程序。关键是用WSAAsyncSelect()函数注册网络事件。后面的实现和网络编程的过程一样。这样可以提高系统资源的利用率。