windows socket网络编程–事件选择模型

编写前说明

团队成员
汪月月 组长
骆念念 组员
曹卉潼 组员
唐宇悦 组员

事件选择模型概述

Winsock提供了另一种有用的异步事件通知I/O模型——WSAEventSelect模型。这个模型与WSAAsyncSelect模型类似,允许应用程序在一个或者多个套接字上接收基于事件的网络通知。它与 WSAAsyncSelect模型类似是因为它也接收FDXXX类型的网络事件,不过并不是依靠Windows的消息驱动机制,而是经由事件对象句柄通知

API详解

WSAEVENT WSAAPI WSACreateEvent(); 

返回值
如果未发生错误, WSACreateEvent 将返回事件对象的句柄。 否则,返回值WSA_INVALID_EVENT。
作用
创建新的事件对象

int WSAAPI WSAEventSelect(   SOCKET   s,            //标识套接字的描述符。   WSAEVENT hEventObject, //标识要与指定FD_XXX网络事件集关联的事件对象的句柄。   long     lNetworkEvents//一个位掩码,指定应用程序感兴趣的FD_XXX网络事件的组合。 ); 

返回值
如果应用程序的网络事件的规范和关联的事件对象成功,则返回值为零。 否则,返回值SOCKET_ERROR。
作用
给事件绑上socket与操作码,并投递给操作系统,应用程序便可以在事件上等待了。

事件类型 含义
FD_READ 应用程序想接收是否有可读的通知
FD_WRITE 应用程序想接收是否有可写的通知
FD_OOB 应用程序想接收是否有OOB数据抵达通知
FD_ACCEPT 应用程序想接收与传入连接有关的通知
FD_CONNECT 应用程序想接收一个已完成连接的通知或者一个多点join操作的通知
FD_CLOSE 应用程序想接收与套接字关闭有关的通知
DWORD WSAAPI WSAWaitForMultipleEvents(   DWORD          cEvents,   const WSAEVENT *lphEvents,   BOOL           fWaitAll,   DWORD          dwTimeout,   BOOL           fAlertable ); 

cEvents
lphEvents 指向的数组中的事件对象句柄数。 事件对象句柄的最大数目 是WSA_MAXIMUM_WAIT_EVENTS。 必须指定一个或多个事件。

lphEvents
指向事件对象句柄数组的指针。 数组可以包含不同类型的对象的句柄。 如果 fWaitAll 参数设置为 TRUE,则它可能不包含同一句柄的多个副本。 如果在等待仍在挂起时关闭其中一个句柄,则未定义 WSAWaitForMultipleEvents 的行为。

fWaitAll
一个指定等待类型的值。 如果为 TRUE,则当 发出 lphEvents 数组中所有对象的状态时,函数将返回。 如果为 FALSE,则函数在发出任何事件对象的信号时返回。 在后一种情况下,返回值减 去WSA_WAIT_EVENT_0 指示导致函数返回其状态的事件对象的索引。 如果在调用期间发出了多个事件对象的信号,则这是信号事件对象的数组索引,其索引值为所有信号事件对象的最小索引值。

dwTimeout
超时间隔(以毫秒为单位)。 WSAWaitForMultipleEvents 如果超时间隔过期,即使 不满足 fWaitAll 参数指定的条件,也会返回。 如果 dwTimeout 参数为零, WSAWaitForMultipleEvents 将测试指定事件对象的状态并立即返回。 如果 dwTimeoutWSA_INFINITE, WSAWaitForMultipleEvents 将永远等待;也就是说,超时间隔永远不会过期。

fAlertable
一个值,该值指定线程是否处于可警报的等待状态,以便系统可以执行I/O完成例程。 如果为TRUE,则线程处于可警报的等待状态,当系统执行 I/O 完成例程时, WSAWaitForMultipleEvents 可以返回。 在这种情况下,将返回 WSA_WAIT_IO_COMPLETION ,并且等待的事件尚未发出信号。 应用程序必须再次调用 WSAWaitForMultipleEvents 函数。 如果为FALSE,则线程不会处于可警报的等待状态,并且不会执行 I/O 完成例程。

int WSAAPI WSAEnumNetworkEvents(   SOCKET             s,              //标识套接字   WSAEVENT           hEventObject,   //用于标识要重置的关联事件对象的可选句柄   LPWSANETWORKEVENTS lpNetworkEvents //指向 WSANETWORKEVENTS 结构的指针,该结构填充了发生的网络事件记录和任何关联的错误代码 ); 

返回值
如果操作成功,则返回值为零。 否则,返回值SOCKET_ERROR
作用
枚举出与事件对象相关联的套接字发生了哪些信号,结果放在WSANETWORKEVENTS结构体中

工作原理

流程大致是这样:

  1. 定义一个socket数组和event数组
  2. 每一个socket操作关联一个event对象
  3. 调用WSAWaitForMultipleEvents函数等待事件的触发
  4. 调用WSAEnumNetworkEvents函数查看是哪个一个事件,根据事件找到相应的socket,然后进行相应的处理:比如数据显示等,同时,记得要将那个event重置为无信号状态。
  5. 循环步骤3和4,直到服务器退出。
    流程图
    windows socket网络编程--事件选择模型

运行结果

服务端
windows socket网络编程--事件选择模型

客户端
windows socket网络编程--事件选择模型
windows socket网络编程--事件选择模型

代码实现

服务端

UINT  CMFCWSAEventDlg::ThreadProc(LPVOID lparam) { 	// TODO: 在此添加控件通知处理程序代码 	CMFCWSAEventDlg* p = (CMFCWSAEventDlg*)lparam; 	SocketInit socketInit; 	SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 	if (socketServer == INVALID_SOCKET) { 		AfxMessageBox(_T("套接字创建失败")); 		closesocket(socketServer); 		WSACleanup(); 	} 	sockaddr_in sock; 	sock.sin_family = AF_INET; 	sock.sin_port = htons(5678); 	sock.sin_addr.S_un.S_addr = INADDR_ANY; 	int n = sizeof(sock); 	if (bind(socketServer, (sockaddr*)&sock, sizeof(sock)) == SOCKET_ERROR) { 		AfxMessageBox(_T("监听失败")); 		closesocket(socketServer); 		WSACleanup();  	} 	if (listen(socketServer, SOMAXCONN) == SOCKET_ERROR) { 		AfxMessageBox(_T("监听失败")); 		closesocket(socketServer); 		WSACleanup(); 	} 	p->showText.SetWindowText("开始监听rn"); 	 	// 创建事件对象,并关联到新的套节字 	WSAEVENT event = ::WSACreateEvent(); 	::WSAEventSelect(socketServer, event, FD_ACCEPT | FD_CLOSE); 	// 添加到表中 	p->eventArray[p->nEventTotal] = event; 	p->sockArray[p->nEventTotal] = socketServer; 	p->nEventTotal++; 	CString str; 	sockaddr_in addrRemote; 	 	while (1){  		// 在所有事件对象上等待 		int nIndex = ::WSAWaitForMultipleEvents(p->nEventTotal, p->eventArray, FALSE, WSA_INFINITE, FALSE); 		// 对每个事件调用WSAWaitForMultipleEvents函数,以便确定它的状态 		nIndex = nIndex - WSA_WAIT_EVENT_0;  		for (int i = nIndex; i < p->nEventTotal; i++) 		{ 			nIndex = ::WSAWaitForMultipleEvents(1, &p->eventArray[i], TRUE, 1000, FALSE); 			if (nIndex == WSA_WAIT_FAILED || nIndex == WSA_WAIT_TIMEOUT) 			{ 				continue; 			} 			else 			{ 				// 获取到来的通知消息,WSAEnumNetworkEvents函数会自动重置受信事件 				WSANETWORKEVENTS event; 				 				::WSAEnumNetworkEvents(p->sockArray[i], p->eventArray[i], &event); 				if (event.lNetworkEvents & FD_ACCEPT)                // 处理FD_ACCEPT通知消息 				{ 					if (event.iErrorCode[FD_ACCEPT_BIT] == 0) 					{ 						if (p->nEventTotal > WSA_MAXIMUM_WAIT_EVENTS) 						{ 							p->showText.SetSel(-1); 							p->showText.ReplaceSel("时间太长"); 							continue; 						} 						int nAddrLen = sizeof(addrRemote); 						SOCKET sNew = ::accept(p->sockArray[i], (SOCKADDR*)&addrRemote, &nAddrLen); 						//MessageBox("已连接"); 						int nLen = p->showText.GetWindowTextLengthA(); 						//p->showText.SetWindowText() 						 						str.Format("%s建立连接rn", ::inet_ntoa(addrRemote.sin_addr)); 						p->showText.SetSel(-1); 						p->showText.ReplaceSel(str); 						WSAEVENT event = ::WSACreateEvent(); 						::WSAEventSelect(sNew, event, FD_READ | FD_CLOSE | FD_WRITE); 						// 添加到表中 						p->eventArray[p->nEventTotal] = event; 						p->sockArray[p->nEventTotal] = sNew; 						p->nEventTotal++; 					} 				} 				else if (event.lNetworkEvents & FD_READ)         // 处理FD_READ通知消息 				{ 					if (event.iErrorCode[FD_READ_BIT] == 0) 					{ 						//char szText[256]; 						char szText[1024] = { 0 }; 						//memset(szText, 0, sizeof(szText)); 						int nlen = strlen(szText); 						int nRecv = ::recv(p->sockArray[i], szText,1024, 0); 						 						//AfxMessageBox(nRecv); 						if (nRecv > 0) 						{ 							szText[nRecv] = ''; 							str.Format("%s发来了一条消息:%srn", ::inet_ntoa(addrRemote.sin_addr), szText); 							p->showText.SetSel(-1); 							p->showText.ReplaceSel(str); 							//szText[0] = '';  							// 向客户端发送数据 							char *sendText =  getallprime(1000); 							if (::send(p->sockArray[i], sendText, strlen(sendText), 0) > 0) 							{ 								p->showText.SetSel(-1); 								p->showText.ReplaceSel("已发送结果rn"); 							} 						} 					} 				} 				else if (event.lNetworkEvents & FD_CLOSE)        // 处理FD_CLOSE通知消息 				{ 					if (event.iErrorCode[FD_CLOSE_BIT] == 0) 					{ 						::closesocket(p->sockArray[i]); 						for (int j = i; j < p->nEventTotal - 1; j++) 						{ 							p->eventArray[j] = p->eventArray[j + 1]; 							p->sockArray[j] = p->sockArray[j + 1]; 						} 						p->nEventTotal--; 					} 					p->showText.SetSel(-1); 					p->showText.ReplaceSel("关闭连接rn"); 				} 			} 		} 	}  } 

客户端

void CMFCWSAEventClientDlg::OnBnClickedButton1() { 	// TODO: 在此添加控件通知处理程序代码 	AfxBeginThread(ThreadProc, this); }  UINT  CMFCWSAEventClientDlg::ThreadProc(LPVOID lparam) { 	CMFCWSAEventClientDlg* p = (CMFCWSAEventClientDlg*)lparam; 	// 创建套节字 	SocketInit socketInit; 	p->s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 	if (p->s == INVALID_SOCKET){ 		AfxMessageBox(" Failed socket()"); 		closesocket(p->s); 		WSACleanup(); 	}  	// 也可以在这里调用bind函数绑定一个本地地址 	// 否则系统将会自动安排  	BYTE nFild[4]; 	p->ipAddr.GetAddress(nFild[0], nFild[1], nFild[2], nFild[3]); 	CString serverIP, serverPort; 	serverIP.Format("%d.%d.%d.%d", nFild[0], nFild[1], nFild[2], nFild[3]); 	p->port.GetWindowTextA(serverPort); 	sockaddr_in servAddr; 	servAddr.sin_family = AF_INET; 	servAddr.sin_port = htons(atoi(serverPort)); 	servAddr.sin_addr.S_un.S_addr = inet_addr(serverIP);  	if (::connect(p->s, (sockaddr*)&servAddr, sizeof(servAddr)) == -1) 	{ 		AfxMessageBox(" Failed connect() "); 		closesocket(p->s); 		WSACleanup(); 	} 	CString str,szText; 	str.Format("与服务器%s建立连接rn", serverIP); 	p->showText.SetSel(-1); 	p->showText.ReplaceSel(str); 	while (1) { 		//发送数据 		p->OnBnClickedButton2(); 		// 接收数据 		char buff[1024]; 		int nRecv = ::recv(p->s, buff, 1024, 0); 		if (nRecv > 0){ 			buff[nRecv] = ''; 			str.Format("服务端:%srn", buff); 			p->showText.SetSel(-1); 			p->showText.ReplaceSel(str); 		} 		p->sendText.SetWindowText(""); 	} 	return 0; } void CMFCWSAEventClientDlg::OnBnClickedButton2() { 	CString str,szText; 	sendText.GetWindowTextA(szText); 	// TODO: 在此添加控件通知处理程序代码 	if (szText != "") { 		if (::send(s, szText, strlen(szText), 0) > 0){ 			str.Format("客户端:%srn", szText); 			showText.SetSel(-1); 			showText.ReplaceSel(str); 		} 	} 	 } 

发表评论

相关文章