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

来源:互联网 发布:怎么做网络宣传 编辑:程序博客网 时间:2024/06/13 14:13

一、引言

       上一文中我们介绍了使用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 0
原创粉丝点击