今天讲解一下基于消息的异步套接字编程的情况。
我们知道windows套接字有两种模式执行I/O操作:阻塞和非阻塞模式:
- 阻塞模式,会阻塞程序运行,从而导致调用线程暂停运行;
- 非阻塞模式,winsock函数无论如何都会立即返回,在该函数执行的操作完成之后,系统会采用某种方式将操作结果通知给调用线程,后者根据通知消息判断操作执行度。
windows socket的异步选择函数WSAAsyncSelect 提供了消息机制的网络事件选择,当通过它登记网络事件发生时,Windows应用程序相应的窗口函数将收到一个消息,消息中指示了发生的网络事件,以及与该事件相关的一些信息。该函数原型:
1 WSAAsyncSelect( 2 SOCKET s, 3 HWND hWnd, 4 u_int wMsg, 5 long lEvent 6 );
- 套接字描述符;
- 事件发生时接收消息的窗口句柄;
- 网络事件发生时窗口将接收到的消息; 网络事件,其定义如下:
-
1 /* 2 * WinSock 2 extension -- bit values and indices for FD_XXX network events 3 */ 4 #define FD_READ_BIT 0 5 #define FD_READ (1 << FD_READ_BIT) 6 7 #define FD_WRITE_BIT 1 8 #define FD_WRITE (1 << FD_WRITE_BIT) 9 10 #define FD_OOB_BIT 2 11 #define FD_OOB (1 << FD_OOB_BIT) 12 13 #define FD_ACCEPT_BIT 3 14 #define FD_ACCEPT (1 << FD_ACCEPT_BIT) 15 16 #define FD_CONNECT_BIT 4 17 #define FD_CONNECT (1 << FD_CONNECT_BIT) 18 19 #define FD_CLOSE_BIT 5 20 #define FD_CLOSE (1 << FD_CLOSE_BIT) 21 22 #define FD_QOS_BIT 6 23 #define FD_QOS (1 << FD_QOS_BIT) 24 25 #define FD_GROUP_QOS_BIT 7 26 #define FD_GROUP_QOS (1 << FD_GROUP_QOS_BIT) 27 28 #define FD_ROUTING_INTERFACE_CHANGE_BIT 8 29 #define FD_ROUTING_INTERFACE_CHANGE (1 << FD_ROUTING_INTERFACE_CHANGE_BIT) 30 31 #define FD_ADDRESS_LIST_CHANGE_BIT 9 32 #define FD_ADDRESS_LIST_CHANGE (1 << FD_ADDRESS_LIST_CHANGE_BIT) 33 34 #define FD_MAX_EVENTS 10 35 #define FD_ALL_EVENTS ((1 << FD_MAX_EVENTS) - 1)
- WSSocket函数是winsock库中创建套接字扩展函数,和socket函数一样;
- WSARecvFrom函数,也是同上扩展函数。值得一提的是参数lpBuffer,是一个指向WSABUF结构体素质指针,该结构体包含两个成员变量,buffer长度变量和指向buffer的指针变量;
- SWASendTo函数,意义同上,也是扩展函数.
- 接下来,我们开始创建对话框,并添加相应的控件:
- 同样的,因为要用到socket,所以就要在程序运行时,先调用套接字库,在APP类成员函数初始化实例前,加载套接字库:
1 BOOL CChat_ws2App::InitInstance() 2 { 3 //load winsock2 libary 4 WORD wVersionRequested; 5 WSADATA wsData; 6 int err; 7 wVersionRequested=MAKEWORD(2,2); 8 err = WSAStartup(wVersionRequested,&wsData); 9 if(err!=0) 10 { 11 MessageBox(NULL,"error: wsa start failed.","ERROR",NULL); 12 return FALSE; 13 } 14 if(LOBYTE(wsData.wVersion)!=2 ||HIBYTE(wsData.wVersion)!=2) 15 { 16 MessageBox(NULL,"error: version incorrect.","ERROR",NULL); 17 WSACleanup(); 18 return FALSE; 19 } 20 AfxEnableControlContainer(); 21 22 // Standard initialization 23 // If you are not using these features and wish to reduce the size 24 // of your final executable, you should remove from the following 25 // the specific initialization routines you do not need. 26 27 #ifdef _AFXDLL 28 Enable3dControls(); // Call this when using MFC in a shared DLL 29 #else 30 Enable3dControlsStatic(); // Call this when linking to MFC statically 31 #endif 32 33 CChat_ws2Dlg dlg; 34 m_pMainWnd = &dlg; 35 int nResponse = dlg.DoModal(); 36 if (nResponse == IDOK) 37 { 38 // TODO: Place code here to handle when the dialog is 39 // dismissed with OK 40 } 41 else if (nResponse == IDCANCEL) 42 { 43 // TODO: Place code here to handle when the dialog is 44 // dismissed with Cancel 45 } 46 47 // Since the dialog has been closed, return FALSE so that we exit the 48 // application, rather than start the application's message pump. 49 return FALSE; 50 }
如果加载失败,直接返回false,结束程序。这里我们需要包含我们用到的socket库头文件 #include<winsock2.h> 注意,我们是用winsock2.2版本,所以别忘了将 ws2_32.lib 添加到工程里。
- 创建,并初始化套接字,这里我们同样把它们封装到dialog类里
1 class CChat_ws2Dlg : public CDialog 2 { 3 private: SOCKET m_sock; 4 BOOL InitSocket(); 5 ... 6 }
- 初始化套接字
1 // Initialize socket 2 BOOL CChat_ws2Dlg::InitSocket() 3 { 4 m_sock = WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,0); 5 if(m_sock==INVALID_SOCKET) 6 { 7 MessageBox("error: failed to create socket."); 8 return FALSE; 9 } 10 sockaddr_in addr; 11 addr.sin_addr.S_un.S_addr=htonl(INADDR_ANY); 12 addr.sin_family=AF_INET; 13 addr.sin_port=htons(6000); 14 if(SOCKET_ERROR==bind(m_sock,(sockaddr*)&addr,sizeof(sockaddr))) 15 { 16 MessageBox("error: failed binding."); 17 return FALSE; 18 } 19 //WSAAsyncSelect->require windows socket dll to send 20 //a message to winHandle.(it will also config socket as Non Blonking Mode.) 21 if(SOCKET_ERROR == WSAAsyncSelect( 22 m_sock, //Notified socket 23 m_hWnd, //window handle 24 UM_SOCK, //(User define)The message to be sent is indicated by the UM_SOCK parameter 25 FD_READ //Read Event 26 )) 27 { 28 MessageBox("WSAAsyncSelect failed."); 29 return FALSE; 30 } 31 return TRUE; 32 }
在初始化过程中,我们同时通过windows sockets的异步选择函数选择了相应的消息机制网络选择;
- 接着我们定义一下自定义的消息 UM_SOCK ,在dialog库文件定义如下:
#define UM_SOCK WM_USER+1
将初始化函数在对话框初始化时执行:
1 ///////////////////////////////////////////////////////////////////////////// 2 // CChat_ws2Dlg message handlers 3 4 BOOL CChat_ws2Dlg::OnInitDialog() 5 { 6 ..... 7 // TODO: Add extra initialization here 8 InitSocket(); 9 ..... 10 }
- 接着为消息机制定义消息事件映像进程函数(接收函数)
1 BEGIN_MESSAGE_MAP(CChat_ws2Dlg, CDialog) 2 //{{AFX_MSG_MAP(CChat_ws2Dlg) 3 ON_WM_SYSCOMMAND() 4 ON_WM_PAINT() 5 ON_WM_QUERYDRAGICON() 6 ON_BN_CLICKED(IDC_BUTTON1, OnBtnSend) 7 //}}AFX_MSG_MAP 8 ON_MESSAGE(UM_SOCK,OnSock)//Message Map 9 END_MESSAGE_MAP()
其中 Onsock 为消息事件映像进程函数。因为在注册的事件发生后,操作系统向调用进程发送相应的消息时,还会将该事件相应的信息一起传递给调用进程,这些信息是通过消息的两个参数传递的。所以我们定义的 UM_SOCK 消息响应函数时应带有 wParam 和 lParam 这两个参数。
- 申明消息事件映像响应函数
1 ///////////////////////////////////////////////////////////////////////////// 2 // CChat_ws2Dlg dialog 3 4 class CChat_ws2Dlg : public CDialog 5 { 6 ... 7 // Implementation 8 protected: 9 HICON m_hIcon; 10 11 // Generated message map functions 12 //{{AFX_MSG(CChat_ws2Dlg) 13 virtual BOOL OnInitDialog(); 14 afx_msg void OnSysCommand(UINT nID, LPARAM lParam); 15 afx_msg void OnPaint(); 16 afx_msg HCURSOR OnQueryDragIcon(); 17 afx_msg void OnBtnSend(); 18 //}}AFX_MSG 19 afx_msg void OnSock(WPARAM,LPARAM);//UM_SOCK message response function 20 DECLARE_MESSAGE_MAP() 21 };
实现响应函数:
1 /// Implementation of message response function 2 void CChat_ws2Dlg::OnSock(WPARAM wParam,LPARAM lParam) 3 { 4 switch(LOWORD(lParam)) 5 { 6 case FD_READ: 7 { 8 WSABUF wsabuf; 9 wsabuf.buf=new char[200]; 10 wsabuf.len=200; 11 DWORD dwRead; 12 DWORD dwFlag=0; 13 sockaddr_in addrFrom; 14 int len=sizeof(sockaddr); 15 CString str; 16 CString strTemp; 17 if(SOCKET_ERROR == WSARecvFrom(m_sock,&wsabuf,1,&dwRead,&dwFlag, 18 (sockaddr*)&addrFrom,&len,NULL,NULL)) 19 { 20 MessageBox("error: failed receive data."); 21 delete[] wsabuf.buf; 22 return; 23 } 24 str.Format("%s: %s",inet_ntoa(addrFrom.sin_addr),wsabuf.buf); 25 str+=" "; 26 GetDlgItemText(IDC_EDIT1,strTemp); 27 str+=strTemp; 28 SetDlgItemText(IDC_EDIT1,str); 29 delete[] wsabuf.buf; 30 break; 31 } 32 default: 33 { 34 break; 35 } 36 } 37 }
- 紧接着,我们实现发送函数---按键消息事件实现发送数据:
1 void CChat_ws2Dlg::OnBtnSend() 2 { 3 // TODO: Add your control notification handler code here 4 DWORD dwIP; 5 CString strSend; 6 WSABUF wsabuf; 7 DWORD dwSend; 8 int len; 9 sockaddr_in addrTo; 10 //get the server ip address from IDC_IPADDRESS1 11 ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP); 12 addrTo.sin_addr.S_un.S_addr=htonl(dwIP); 13 addrTo.sin_family=AF_INET; 14 addrTo.sin_port=htons(6000); 15 //get data which need to be sent 16 GetDlgItemText(IDC_EDIT2,strSend); 17 len=strSend.GetLength(); 18 //write in to wsabuf 19 wsabuf.buf=strSend.GetBuffer(len); 20 wsabuf.len=len+1; 21 //send data to socket 22 if(SOCKET_ERROR == WSASendTo(m_sock,&wsabuf,1,&dwSend,0, 23 (sockaddr*)&addrTo,sizeof(sockaddr),NULL,NULL)) 24 { 25 MessageBox("error: transmit failed."); 26 return; 27 } 28 }
- 最后,我们需要在退出程序时,释放所占用的资源,别在app类和dialog类中创建析构函数,并在析构函数中释放socket所占用的资源:
1 CChat_ws2App::~CChat_ws2App() 2 { 3 // TODO: add construction code here, 4 // Place all significant initialization in InitInstance 5 WSACleanup(); 6 } 7 8 CChat_ws2Dlg::~CChat_ws2Dlg() 9 { 10 // TODO: add construction code here, 11 // Place all significant initialization in InitInstance 12 if(m_sock) 13 { 14 closesocket(m_sock); 15 } 16 }
编译,运行:
End.
至此,线程同步异步套接字编程,我们就算讲解完了,希望对大家有帮助。
谢谢.