您的位置:首页 > 理论基础 > 计算机网络

网络编程(53)—— Windows下使用WSAAsyncSelect实现窗口处理socket消息

2017-01-05 08:23 501 查看

一、引言

       上一文中我们介绍了使用WSAEventSelect实现异步通知IO的方法,本文我们主要讨论下使用WSAAsyncSelect处理socket的方法。本文的主要目标,是创建一个带界面的回声服务端,接收并返回客户端传过来的字符串,并在界面上显示该字符串。为此,我们将采用MFC的编程环境,建立如下的对话框程序:



二、WSAAsyncSelect函数

        WSAAsyncSelect也是windows下一种异步的select,它可以注册IO事件,当发生注册的IO事件时,它会发送一个我们自定义的消息给我们的窗口,而我们在窗口的消息处理函数中就可以处理这些消息了。它的原型如下:

int WSAAsyncSelect(
__in SOCKET s,
__in HWND hWnd,
__in unsigned int wMsg,
__in long lEvent
);
s —— 用于监视的socket。

hWnd —— 用来接收socket消息的窗口。

wMsg —— 是我们自定义的消息,一般情况下使用“WM_USER+ 数字”的形式进行定义。

lEvent —— 进行监视的IO事件,包含如下几种:

FD_READ:套接字可读通知。

FD_WRITE:可写通知。

FD_ACCEPT:服务器接收连接的通知。

FD_CONNECT:有客户连接通知。

FD_OOB:外带数据到达通知。

FD_CLOSE:套接字关闭通知。

FD_QOS:服务质量发生变化通知。

FD_GROUP_QOS:组服务质量发生变化通知。

FD_ROUTING_INTERFACE_CHANGE:与路由器接口发生变化的通知。

FD_ADDRESS_LIST_CHANGE:本地地址列表发生变化的通知。

 

返回值 —— 正常情况下返回0,出现错误时返回错误码。

        我们知道Windows系统中消息都会携带两个参数wParam和lParam,我们在进行窗口的消息处理时往往会利用这两个参数。WSAAsyncSelect发送的socket消息也不例外,它的wParam参数携带的是发生IO事件的socket。而lParam低字节表明已发生的事件。高字节包含错误代码。我们可以使用WSAGETSElECTERROR宏来读取lParam的高字节获取错误码,使用WSAGETSELECTEVENT宏读取lParam的低字节来获取发生的事件类型。

三、编程步骤

3.1 自定义消息

        我们先自定义一个socket消息以用WSAAsyncSelect发送:

#define WM_SOCKET WM_USER+1

3.2 注册服务端的socket

       我们先经过一般步骤创建服务端的socket,并然后用WSAAsyncSelect进行注册:

     WSAStartup(MAKEWORD(2,2),&m_wsaData);
m_servSock=socket(AF_INET,SOCK_STREAM,0);

memset(&m_servAddr,0,sizeof(m_servAddr));
m_servAddr.sin_family=AF_INET;
m_servAddr.sin_addr.s_addr=htonl(INADDR_ANY);
m_servAddr.sin_port=htons(atoi("8888"));

bind(m_servSock,(SOCKADDR*)&m_servAddr,sizeof(m_servAddr));

listen(m_servSock,5);

WSAAsyncSelect(m_servSock,this->m_hWnd,WM_SOCKET,FD_ACCEPT|FD_READ);
        最后一句代码中,我们使用WSAAsyncSelect注册了服务端的socketm_servSock,WSAAsyncSelect的第二个参数填写了我们窗口的m_hWnd值,第三个参数是我们自定义的消息,而第四个参数填写的是注册的IO事件,分别表示接受链接和有数据可读。注册完成之后,一旦有新的客户端连接,系统就会发送一个WM_SOCKET消息给我们的窗口。

3.3 处理socket消息

        为了能够处理socket消息,我们先引入窗口的消息处理函数,从类向导中添加虚函数OnWndMsg,在OnWndMsg中我们进行socket消息的处理:
BOOL CWSAAsyncSelectServDlg::OnWndMsg(UINT message,WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
switch(message)
{
case WM_SOCKET:
{
SOCKETsock = (SOCKET) wParam;
if(m_servSock == sock)
{
//获?取¨?
m_clntAddrSz=sizeof(m_clntAddr);
m_clntSock=accept(m_servSock,(SOCKADDR*)&m_clntAddr,&m_clntAddrSz);
WSAAsyncSelect(m_clntSock,this->m_hWnd,WM_SOCKET,FD_READ);
//将?m_clntSock和¨ªm_clntAddr存ä?到Ì?映®3射¦?中D
m_sockMap.SetAt(m_clntSock,m_clntAddr);
PrintClientConnectMsg();

}
else
{
m_strLen= recv(sock,buf,BUF_SIZE - 1,0);
send(sock,buf,m_strLen,0);
buf[m_strLen]=0;
PrintClientSendMsg(sock);
}
}
break;
default:
break;
}

return CDialogEx::OnWndMsg(message, wParam, lParam,pResult);
}        从上述函数的WM_SOCKETswitch分支可以看到,我们通过wParam获取到了发生IO事件的socket,如果有需要还可以通过lParam获取到错误码或者事件类型。获取到socket之后,先判断socket是不是服务端的m_servSock。如果是,表明有新的客户端链接,接收连接并打印客户端信息;如果不是则是由客户端传来了数据,调用recv接收数据并send给客户端,最后打印数据。
        下面是用来打印客户端信息和客户端数据的两个函数:
void CWSAAsyncSelectServDlg::PrintClientConnectMsg(void)
{
CString str;
CTimelocTime=CTime::GetCurrentTime(); //获?取¨?当Ì¡À前¡ã时º¡À间?
CStringstrTime=locTime.Format("%Y-%m-%d%H:%M:%S");//将?当Ì¡À前¡ã时º¡À间?转Áa换?成¨¦CString类¤¨¤型¨ª
str.Format("%s Client %s:%d Connectted",strTime,inet_ntoa(m_clntAddr.sin_addr),m_clntAddr.sin_port);
this->m_list.AddString(str);
}

void CWSAAsyncSelectServDlg::PrintClientSendMsg(SOCKET sock)
{
CString str;
m_clntAddr=m_sockMap[sock];
CTimelocTime=CTime::GetCurrentTime();
CStringstrTime=locTime.Format("%Y-%m-%d%H:%M:%S");//将?当Ì¡À前¡ã时º¡À间?转Áa换?成¨¦CString类¤¨¤型¨ª
str.Format("%s Client %s:%d say:%s",strTime,inet_ntoa(m_clntAddr.sin_addr),m_clntAddr.sin_port,buf);
this->m_list.AddString(str);
}
运行程序,效果如下,服务端不单返回了客户端传过来的信息,还对信息进行了打印。
客户端1:



客户端2:

 


 服务端:



 Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
git clone git@github.com:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL53

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: