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

一、引言

       上一文中我们介绍了使用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

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值