iocp_1

本文详细阐述了一种基于I/O复用技术的并发网络服务器实现,包括初始化套接字、处理客户端连接、读写操作以及错误处理等关键步骤。通过使用WSAStartup、WSACleanup、AcceptEx、WSASend、WSARecv等API,实现了单线程下的高效并发处理,确保了服务器能够响应大量客户端请求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

// iocpServer.cpp : Defines the entry point for the console application.
//

/***************************************
服务器实现功能:回显,接收客户端信息并返回给客户端
单线程
****************************************/


#include "stdafx.h"
#include <WinSock2.h>
#include <MSWSock.h>
#pragma comment(lib,"Ws2_32.lib")

#define BUFFER_SIZE 1024

void InitSocket(BYTE minorVer = 2, BYTE majorVer = 2)
{
	//初始化 WS2_32.dll
	WSADATA wsaData;
	WORD sockVertion = MAKEWORD(minorVer, majorVer);
	if (::WSAStartup(sockVertion, &wsaData) != 0)
	{
		exit(0);
	}
}

void ClearSocket()
{
	::WSACleanup();
}

typedef struct _SOCKET_OBJ //记录与套接字的相关信息
{
	SOCKET s; //套接字句柄
	int nOutstandingOps; //记录此套接字上的重叠I/O数量
	LPFN_ACCEPTEX lpfnAcceptEx;//扩展函数AcceptEx 指针(仅对监听套接字而言);
} SOCKET_OBJ, *PSOCKET_OBJ;

PSOCKET_OBJ GetSocketObj(SOCKET s)//申请套接字对象
{
	PSOCKET_OBJ pSocket = (PSOCKET_OBJ)::GlobalAlloc(GPTR, sizeof(SOCKET_OBJ));
	if (pSocket != NULL)
	{
		pSocket->s = s;
	}
	return pSocket;
}

void FreeSocketObj(PSOCKET_OBJ pSocket)//释放套接字对象
{
	if (pSocket->s != INVALID_SOCKET)
	{
		::closesocket(pSocket->s);
	}
	::GlobalFree(pSocket);
}

typedef struct _BUFFER_OBJ //缓冲区对象
{
	OVERLAPPED ol;//重叠结构
	char * buff;//send/recv/AcceptEx 所使用的缓冲区
	int nLen;   //buff 的长度
	PSOCKET_OBJ pSocket; //此 I/O所属的套接字对象
	int nOperetion; //提交的操作类型
#define OP_ACCEPT 1
#define OP_READ   2
#define OP_WRITE  3
	SOCKET sAccept; //用来保存AcceptEx 接受的客户套接字(仅对监听套接字而言)
	_BUFFER_OBJ *pNext;
} BUFFER_OBJ, *PBUFFER_OBJ;

HANDLE g_events[WSA_MAXIMUM_WAIT_EVENTS]; // I/O 事件句柄数组
int g_nBufferCount;						//上数组中有效句柄数量
PBUFFER_OBJ g_pBufferHead, g_pBufferTail; //记录缓冲区对象组成的表的地址

PBUFFER_OBJ GetBufferObj(PSOCKET_OBJ pSocket, ULONG nLen)
{
	if (g_nBufferCount > WSA_MAXIMUM_WAIT_EVENTS - 1)
		return NULL;
	PBUFFER_OBJ pBuffer = (PBUFFER_OBJ)::GlobalAlloc(GPTR,sizeof(BUFFER_OBJ));

	if(pBuffer != NULL)
	{
		pBuffer->buff = (char*)::GlobalAlloc(GPTR, nLen);
		pBuffer->ol.hEvent = ::WSACreateEvent(); //参数有歧义
		pBuffer->pSocket = pSocket;
		pBuffer->sAccept = INVALID_SOCKET;

		//将新的BUFFER_OBJ 添加到列表中
		if(g_pBufferHead == NULL)
		{
			g_pBufferHead = g_pBufferTail = pBuffer;
		}
		else
		{
			g_pBufferTail->pNext = pBuffer;
			g_pBufferTail = pBuffer;
		}
		g_events[++ g_nBufferCount] = pBuffer->ol.hEvent;
	}
	return pBuffer;
}

void FreeBufferObj(PBUFFER_OBJ pBuffer)
{
	//从列表中移除BUFFER_OBJ 对象
	PBUFFER_OBJ pTest = g_pBufferHead;
	BOOL bFind = FALSE;
	if (pTest == pBuffer)
	{
		g_pBufferHead = g_pBufferTail = NULL;
		bFind = TRUE;
	}
	else
	{
		while (pTest !=NULL && pTest->pNext != pBuffer)
		{
			pTest = pTest->pNext;
		}
		if (pTest != NULL)
		{
			pTest->pNext = pBuffer->pNext;
			if (pTest->pNext == NULL)
			{
				g_pBufferTail = pTest;
			}
			bFind = TRUE;
		}
	}
	//释放它占用的内存空间
	if(bFind)
	{
		g_nBufferCount --;
		::CloseHandle(pBuffer->ol.hEvent);
		::GlobalFree(pBuffer->buff);
		::GlobalFree(pBuffer);
	}
}

PBUFFER_OBJ FindBufferObj(HANDLE hEvent)//从缓冲区查找BUFFER_OBJ对象
{
	PBUFFER_OBJ pBuffer = g_pBufferHead;
	while (pBuffer != NULL)
	{
		if (pBuffer->ol.hEvent == hEvent)
		{
			break;
		}
		pBuffer = pBuffer->pNext;
	}
	return pBuffer;
}

void RebuidArray()//更新事件句柄数组g_events中的内容
{
	PBUFFER_OBJ pBuffer = g_pBufferHead;
	int i = 1;
	while(pBuffer != NULL)
	{
		g_events[++i] = pBuffer->ol.hEvent;
		pBuffer = pBuffer->pNext;
	}
}

//提交重叠I/O
BOOL PostAccept(PBUFFER_OBJ pBuffer)
{
	PSOCKET_OBJ pSocket = pBuffer->pSocket;
	if (pSocket->lpfnAcceptEx != NULL)
	{
		//设置I/O类型,增加套接字上的重叠I/O计数
		pBuffer->nOperetion = OP_ACCEPT;
		pSocket->nOutstandingOps ++;

		//投递此重叠I/O
		DWORD dwBytes;
		pBuffer->sAccept =
			::WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
		BOOL b = pSocket->lpfnAcceptEx(pSocket->s,
			pBuffer->sAccept,
			pBuffer->buff,
			BUFFER_SIZE - ((sizeof(sockaddr_in) + 16) *2),
			sizeof(sockaddr_in) + 16,
			sizeof(sockaddr_in) + 16,
			&dwBytes,
			&pBuffer->ol);
		if (!b)
		{
			if (::WSAGetLastError() != WSA_IO_PENDING)
			{
				return FALSE;
			}
		}
		return TRUE;
	}
	return FALSE;
}

BOOL PostRecv(PBUFFER_OBJ pBuffer)
{
	//设置I/O类型,增加套接字上的重叠I/O计数
	pBuffer->nOperetion = OP_READ;
	pBuffer->pSocket->nOutstandingOps ++;
	//投递此重叠I/O
	DWORD dwBytes;
	DWORD dwFlags = 0;
	WSABUF buf;
	buf.buf = pBuffer->buff;
	buf.len = pBuffer->nLen;
	if (::WSARecv(pBuffer->pSocket->s, &buf, 1, &dwBytes, &dwFlags, &pBuffer->ol, NULL) != NO_ERROR)
	{
		if (::WSAGetLastError() != WSA_IO_PENDING)
		{
			return FALSE;
		}
	}
	return TRUE;
}

BOOL PostSend(PBUFFER_OBJ pBuffer)
{
	//设置I/O类型, 增加套接字上的重叠I/O计数
	pBuffer->nOperetion = OP_WRITE;
	pBuffer->pSocket->nOutstandingOps ++;
	//投递此重叠I/O
	DWORD dwBytes;
	DWORD dwFlags = 0;
	WSABUF buf;
	buf.buf = pBuffer->buff;
	buf.len = pBuffer->nLen;
	if (::WSASend(pBuffer->pSocket->s, &buf, 1, &dwBytes, dwFlags, &pBuffer->ol, NULL) != NO_ERROR)
	{
		if (::WSAGetLastError() != WSA_IO_PENDING)
		{
			return FALSE;
		}
	}
	return TRUE;
}

BOOL HandleIO(PBUFFER_OBJ pBuffer)
{
	PSOCKET_OBJ pSocket = pBuffer->pSocket;
	//从BUFFER_OBJ 对象中提取SOCKET_OBJ 对象指针,为的是方便引用
	pSocket->nOutstandingOps --;
	//获取重叠操作结果
	DWORD dwTrans;
	DWORD dwFlags;
	BOOL bRet = ::WSAGetOverlappedResult(pSocket->s, &pBuffer->ol, &dwTrans, FALSE,&dwFlags);
	if (!bRet)
	{
		//在此套接字上有错误发生,因此关闭套接字,移除缓冲区对象
		//如果没有其他抛出的I/O请求了,释放缓冲区对象,否则,等待此套接字上的其他I/O完成
		if(pSocket->s != INVALID_SOCKET)
		{
			::closesocket(pSocket->s);
			pSocket->s = INVALID_SOCKET;
		}

		if (pSocket->nOutstandingOps == 0)
		{
			FreeSocketObj(pSocket);
		}
		FreeBufferObj(pBuffer);
		return FALSE;
	}
	//没有错误发生,处理已完成的I/O
	switch(pBuffer->nOperetion)
	{
	case OP_ACCEPT: //接收到一个新的连接,并接收对方发来的第一个封包
		{
			//为新用户创建一个 SOCKET_OBJ 对象
			PSOCKET_OBJ pClient = GetSocketObj(pBuffer->sAccept);
			//为发送数据创建一个BUFFER_OBJ对象,这个对象会在套接字出错或关闭时释放
			PBUFFER_OBJ pSend = GetBufferObj(pClient,BUFFER_SIZE);
			if (pSend == NULL)
			{
				printf("Too Much Connections!\n");
				FreeSocketObj(pClient);
				return FALSE;
			}
			RebuidArray();
			//数据复制到发送缓冲区
			pSend->nLen = dwTrans;
			memcpy(pSend->buff, pBuffer->buff,dwTrans);
			//投递此发送I/O(将数据回显给客户)
			if(!PostSend(pSend))
			{
				//如果出错,释放上面申请的两个对象
				FreeSocketObj(pSocket);
				FreeBufferObj(pSend);
				return FALSE;
			}
			//继续投递接收I/O
			PostAccept(pBuffer);
			break;
		}
	case OP_READ: //接收数据完成
		{
			if (dwTrans > 0)
			{
				//创建一个缓冲区,已发送数据。这里就是用原来的缓冲区
				PBUFFER_OBJ pSend = pBuffer;
				pSend->nLen = dwTrans;
				//投递发送I/O(将数据回显给客户)
				PostSend(pSend);
			}
			else //套接字关闭
			{
				//必须关闭套接字,以便在此套接字上投递其他的I/O也返回
				if(pSocket->s != INVALID_SOCKET)
				{
					::closesocket(pSocket->s);
					pSocket->s = INVALID_SOCKET;
				}

				if (pSocket->nOutstandingOps == 0)
				{
					FreeSocketObj(pSocket);
				}

				FreeBufferObj(pBuffer);
				return FALSE;
			}
			break;
		}
	case OP_WRITE: //发送数据完成
		{
			if(dwTrans > 0)
			{
				//继续使用这个缓冲区投递接收数据的请求
				pBuffer->nLen = BUFFER_SIZE;
				PostRecv(pBuffer);
			}
			else //套接字关闭
			{
				//同样要先关闭套接字
				if(pSocket->s != INVALID_SOCKET)
				{
					::closesocket(pSocket->s);
					pSocket->s = INVALID_SOCKET;
				}
				if (pSocket->nOutstandingOps == 0)
				{
					FreeSocketObj(pSocket);
				}
				FreeBufferObj(pBuffer);
				return FALSE;
			}
			break;
		}
	}
	return TRUE;
}

/*main 函数的作用
1.创建监听套接字,投递监听I/O
2.循环处理I/O事件
*/
int _tmain(int argc, _TCHAR* argv[])
{
	InitSocket();
	//创建监听套接字,绑定到本地端口,进入监听模式
	int nPort = 4567;
	SOCKET sListen = 
		::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
	SOCKADDR_IN si;
	si.sin_family = AF_INET;
	si.sin_port = ::ntohs(nPort);
	si.sin_addr.S_un.S_addr = INADDR_ANY;
	::bind(sListen, (sockaddr*)&si, sizeof(si));
	::listen(sListen, 200);
	//为监听套接字创建一个SOCKET_OBJ对象
	PSOCKET_OBJ pListen = GetSocketObj(sListen);
	//加载扩展函数 AcceptEx;
	GUID GuidAcceptEx = WSAID_ACCEPTEX;
	DWORD dwBytes;
	WSAIoctl(pListen->s,
		SIO_GET_EXTENSION_FUNCTION_POINTER,
		&GuidAcceptEx,
		sizeof(GuidAcceptEx),
		&pListen->lpfnAcceptEx,
		sizeof(pListen->lpfnAcceptEx),
		&dwBytes,
		NULL,
		NULL);
	//创建用来重新建立g_events数组的事件对象
	g_events[0] = ::WSACreateEvent();
	//在此可以投递多个I/O请求
	for(int i = 0; i< 5; ++i)
	{
		PostAccept(GetBufferObj(pListen, BUFFER_SIZE));
	}

	while(TRUE)
	{
		int nIndex =
			::WSAWaitForMultipleEvents(g_nBufferCount + 1, g_events, FALSE, WSA_INFINITE, FALSE);
		if (nIndex == WSA_WAIT_FAILED)
		{
			printf("WSAWaitForMultipleEvents() failed\n");
			break;
		}
		nIndex = nIndex - WSA_WAIT_EVENT_0;
		for (int i =0; i<= nIndex; ++i)
		{
			int nRet = ::WSAWaitForMultipleEvents(1, &g_events[i], TRUE, 0, FALSE);
			if (nRet == WSA_WAIT_TIMEOUT)
			{
				continue;
			}
			else
			{
				::WSAResetEvent(g_events[i]);
				//重建g_events数组
				if(i == 0)
				{
					RebuidArray();
					continue;
				}
				//处理这个I/O
				PBUFFER_OBJ pBuffer = FindBufferObj(g_events[i]);
				if (pBuffer != NULL)
				{
					if (!HandleIO(pBuffer))
					{
						RebuidArray();
					}
				}
			}
		}
	}

	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值