16.5基于消息的异步套接字

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);

//运行结果如图所示
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

身影王座

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值