前面两讲为大家介绍了编写传统socket程序的两种方法,今天将为大家介绍一种使用Windows消息机制编写socket客户端程序的方法。使用Windows消息机制编写socket程序主要有以下的好处:一是我们可以将大部分的recv操作以及close操作放到消息处理函数里面,以利于代码的维护;二是当有数据可读的时候,本地程序会接到相应的消息,我们可以在这时候读取数据。大家可以想像一下,在传统的socket程序中,如果一个远程程序在你没有向它发送请求的时候给你传送数据的话,如果本地程序没有进行相应的检测(一种方法是通过计时器进行检测),是不能及时根据它发送给你的数据进行相应的操作的。而如果使用消息机制的话,就能很好的解决这个问题;三是可以轻松的检测出远程程序主动关闭和意外退出的情况。如果使用传统方式的话,我们必须定时进行相应的检测才能知道该情况的发生。
为了使用消息机制驱动的网络程序,我们必须使用WSAAsyncSelect函数注册Windows消息以及我们感兴趣的网络事件。WSAAsyncSelect函数的函数原型如下所示:
int WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent)
-
s
- [in] 需要获取相应网络事件的套接字。 hWnd [in] 需要获取相应网络事件的窗口。 wMsg [in] 当网络事件发生时候将会收到的消息。 lEvent [in] 可以使用位域组合的网络事件。 Return Values 成功返回0,失败返回SOCKET_ERROR。在返回SOCKET_ERROR的时候,你可以使用WSAGetLastError来获取相应的错误代码。 做为一个客户端程序,我们主要对以下Winsocket中定义的网络事件感兴趣。 FD_CONNECT 当连接完成的时候希望收到我们注册的网络消息。 FD_READ 当有数据可读时希望收到我们注册的网络消息。 FD_WRITE 当可以向对方写数据时希望收到我们注册的网络消息。 FD_CLOSE 当套接字关闭的时候希望收到我们注册的网络消息。 假设我们已经在应用程序中创建了套接字s,获取了窗口句柄hwnd以及定义了WM_SOCKET消息,我们可以按照如下的方式调用WSAAsyncSelect函数,以使我们可以在有数据可读和套接字关闭的时候收到WM_SOCKET消息。
- int iRet = WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
- int iRet = WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_READ);
- iRet = WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_CLOSE);
- int iRet = WSAAsyncSelect(s, hwnd, 0, 0);
- WORD wEvent = WSAGETSELECTEVENT(lParam);
- WORD wError = WSAGETSELECTERROR(lParam);
- LRESULT OnSocket(WPARAM wParam, LPARAM lParam)
- {
- SOCKET s = wParam;
- WORD uEvent = WSAGETSELECTEVENT(lParam);
- WORD uError = WSAGETSELECTERROR(lParam);
- switch(uEvent)
- {
- case FD_CONNECT:
- // do something
- break;
- case FD_READ:
- // do something
- break;
- case FD_WRITE:
- // do something
- break;
- case FD_CLOSE:
- // do something
- break;
- default:
- break;
- }
- return 0;
- }
- m_sktSession = socket(AF_INET, SOCK_STREAM, 0);
- int iRet = WSAAsyncSelect(m_sktSession, GetSafeHwnd(), WM_SOCKET, FD_CONNECT);
- ASSERT(SOCKET_ERROR != iRet);
- // 初始化连接套接字地址信息
- const char szIP[] = "127.0.0.1";
- const unsigned short uPort = 10001;
- sockaddr_in adrServ; // 表示网络地址
- ZeroMemory(&adrServ, sizeof(sockaddr_in));
- adrServ.sin_family = AF_INET; // 初始化地址格式,只能为AF_INET
- adrServ.sin_port = htons(uPort); // 初始化端口,由于网络字节顺序和主机字节顺序相反,所以必须使用htons将主机字节顺序转换成网络字节顺序
- adrServ.sin_addr.s_addr = inet_addr(szIP); // 初始化IP, 由于网络字节顺序和主机字节顺序相反,所以必须使用inet_addr将主机字节顺序转换成网络字节顺序
- iRet = connect(m_sktSession, (sockaddr*)&adrServ, sizeof(adrServ));
- ASSERT(WSAEWOULDBLOCK == WSAGetLastError()); // iRet总是返回SOCKET_ERROR, 所以通过判断WSAEWOULDBLOCK来确定连接是否成功
- LRESULT CClientWinSelectDlg::OnSocket(WPARAM wParam, LPARAM lParam)
- {
- SOCKET s = wParam;
- unsigned short uEvent = WSAGETSELECTEVENT(lParam);
- unsigned short uError = WSAGETSELECTERROR(lParam);
- int iRet = SOCKET_ERROR;
- const size_t uBufLen = 128;
- char szBuf[uBufLen] = {0};
- switch(uEvent)
- {
- case FD_CONNECT:
- ASSERT(0 == uError);
- iRet = WSAAsyncSelect(s, GetSafeHwnd(), WM_SOCKET, FD_WRITE | FD_CLOSE); // 在连接成功后,会收到携带FD_WRITE事件的WM_SOCKET消息
- ASSERT(0 == iRet);
- UpdateData();
- m_strRecv = "Connection OK. Now you can send message to server/r/n";
- UpdateData(FALSE);
- break;
- case FD_READ:
- ASSERT(0 == uError);
- iRet = recv(s, szBuf, uBufLen - 1, 0);
- ASSERT(SOCKET_ERROR != iRet);
- UpdateData();
- m_strRecv.Format("%s%s/r/n", m_strRecv, szBuf);
- UpdateData(FALSE);
- break;
- case FD_WRITE: // FD_WRITE事件不同于FD_READ, 我们只能通过FD_WRITE事件判断是否可以向对方发送数据, 而不是在事件处理中向对方发数据
- ASSERT(0 == uError);
- iRet = WSAAsyncSelect(s, GetSafeHwnd(), WM_SOCKET, FD_READ | FD_CLOSE);
- ASSERT(0 == iRet);
- break;
- case FD_CLOSE:
- while (0 < recv(s, szBuf, uBufLen - 1, 0));
- iRet = WSAAsyncSelect(s, GetSafeHwnd(), 0, 0);
- ASSERT(SOCKET_ERROR != iRet);
- closesocket(s);
- m_sktSession = INVALID_SOCKET;
- UpdateData();
- m_strRecv.Format("%sconnection has clsoed/r/n", m_strRecv);
- UpdateData(FALSE);
- break;
- default:
- break;
- }
- return 0;
- }
- void CClientWinSelectDlg::OnClickedButtonSend()
- {
- // TODO: 在此添加控件通知处理程序代码
- int iRet = WSAAsyncSelect(m_sktSession, GetSafeHwnd(), WM_SOCKET, FD_WRITE | FD_CLOSE);
- ASSERT(0 == iRet);
- UpdateData();
- iRet = send(m_sktSession, (LPCSTR)m_strSend, m_strSend.GetLength(), 0);
- m_strSend.Empty();
- UpdateData(FALSE);
- ASSERT(SOCKET_ERROR != iRet);
- }
- CClientWinSelectDlg::~CClientWinSelectDlg()
- {
- int iRet = SOCKET_ERROR;
- if (INVALID_SOCKET != m_sktSession)
- {
- iRet = shutdown(m_sktSession, SD_SEND);
- ASSERT(SOCKET_ERROR != iRet);
- }
- }
OnSocket函数中的代码如下所示:
发送数据的代码如下所示:
关闭操作如下所示:
程序的运行结果如下所示:
-
程序源代码地址:http://e.ys168.com/?shining100 密码为123456
以上就是使用消息机制创建简单客户端程序的全部过程以及内容了。Winsocket入门教程这个系列也暂时告一段落了,本人也是因为工作才学的菜鸟呀,希望在有时间深入的学习以后,能为大家带来更多精彩的内容。