4

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

 1 year ago
source link: https://www.cnblogs.com/ww-yy/p/16881374.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client




编写前说明

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

事件选择模型概述

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,直到服务器退出。
    流程图
    2160415-20221111202319918-188243333.jpg

服务端

2160415-20221111210138498-1659045347.png

客户端

2160415-20221111210116679-539870451.png


2160415-20221111210122142-1690614491.png
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("开始监听\r\n");
	
	// 创建事件对象,并关联到新的套节字
	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建立连接\r\n", ::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] = '\0';
							str.Format("%s发来了一条消息:%s\r\n", ::inet_ntoa(addrRemote.sin_addr), szText);
							p->showText.SetSel(-1);
							p->showText.ReplaceSel(str);
							//szText[0] = '\0';

							// 向客户端发送数据
							char *sendText =  getallprime(1000);
							if (::send(p->sockArray[i], sendText, strlen(sendText), 0) > 0)
							{
								p->showText.SetSel(-1);
								p->showText.ReplaceSel("已发送结果\r\n");
							}
						}
					}
				}
				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("关闭连接\r\n");
				}
			}
		}
	}

}
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建立连接\r\n", 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] = '\0';
			str.Format("服务端:%s\r\n", 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("客户端:%s\r\n", szText);
			showText.SetSel(-1);
			showText.ReplaceSel(str);
		}
	}
	
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK