C++实现socket IOCP

这几天在学习多线程和网络socket模型,结合人宅老师的C++教程和windows提供的api参考以及网上的一些资料写下这些,记录一下。

服务端代码。

#define _CRT_SECURE_NO_WARNINGS
#include <WinSock2.h>
#include <list>
#include <process.h>
#include <iostream>
#pragma comment(lib,"ws2_32.lib") 
using namespace std;

struct IoData 
{
	IoData()
	{
		ZeroMemory(this, sizeof(IoData));
	}

	OVERLAPPED OverL;
	CHAR buffer[1024];
	byte Type;
	DWORD Len;
	WSABUF wasbuffer;
};

class FClient
{
public:
	FClient(SOCKET S, SOCKADDR_IN InSin);
	virtual ~FClient() {};

	BOOL Recv();
	BOOL Send();

public:
	SOCKET ClientSocket;
	SOCKADDR_IN Sin;
	IoData Data;
};
FClient::FClient(SOCKET S, SOCKADDR_IN InSin)
	:ClientSocket(S),
	Sin(InSin)
{

}

BOOL FClient::Recv()
{
	DWORD Flag = 0;
	DWORD Len = 0;
	Data.Type = 0;
	Data.wasbuffer.buf = Data.buffer;
	Data.wasbuffer.len = 1024;
	//参考https://learn.microsoft.com/zh-cn/windows/win32/api/winsock2/nf-winsock2-wsarecv
	if (WSARecv(
		ClientSocket,
		&Data.wasbuffer,
		1,&Len,&Flag,&Data.OverL,NULL)== SOCKET_ERROR)
	{
		if (WSAGetLastError() != ERROR_IO_PENDING)
		{
			return false;
		}
	}
	return TRUE;
}

BOOL FClient::Send()
{
	DWORD Flag = 0;
	DWORD Len = 0;
	Data.Type = 1;
	Data.wasbuffer.buf = Data.buffer;
	Data.wasbuffer.len = strlen(Data.buffer);
	//参考https://learn.microsoft.com/zh-cn/windows/win32/api/winsock2/nf-winsock2-wsasend
	if (WSASend(
		ClientSocket,
		&Data.wasbuffer,
		1, &Len, &Flag, (LPOVERLAPPED) & Data.OverL, NULL) == SOCKET_ERROR)
	{
		if (WSAGetLastError() != ERROR_IO_PENDING)
		{
			return false;
		}
	}
	return TRUE;
}

list<FClient*>ClientList;
void ListRemove(FClient* Client)
{
	//遍历客户端链表,将匹配的进程号排出
	for (auto iter = ClientList.begin(); iter != ClientList.end(); iter++)
	{
		if (Client == (*iter))
		{
			ClientList.erase(iter);
			break;
		}
	}
}

HANDLE cp = NULL;		//创建完成端口
unsigned int __stdcall Run(void* content)
{
	for (;;)
	{
		DWORD IOSize = -1;
		LPOVERLAPPED lpOverlapped = NULL;
		FClient* Client = NULL;

		//队列任务模式
		bool Ret = GetQueuedCompletionStatus(
			cp, &IOSize, (LPDWORD)&Client, &lpOverlapped, INFINITE
		);

		if (Client == NULL && lpOverlapped == NULL)
		{
			break;
		}

		//排队成功了的话
		if(Ret)
		{
			//检查接受的字节数
			if (IOSize == 0)
			{
				ListRemove(Client); //0则直接移除等待队列
				continue;
			}

			//https://blog.csdn.net/zyhse/article/details/109246875
			//CONTAINING_RECORD宏的用途,构造数据
			IoData* InData = CONTAINING_RECORD(lpOverlapped, IoData, OverL);
			switch (InData->Type)
			{
			case 0:	//接受 客户端发送的信息
			{
				Client->Data.Len = IOSize;
				Client->Data.buffer[IOSize] = '\0';	
				printf(Client->Data.buffer);

				char buffer[1024] = { 0 };				//测试给客户端返回的报文
				sprintf_s(buffer, 1024, "hi I'm Server (%d)", Client->ClientSocket);
				strcpy(Client->Data.buffer, buffer);
				Client->Send();
				break;
			}
			case 1:	//发送
			{
				printf(Client->Data.buffer);
				Client->Data.Len = 0;
				if (!Client->Recv())
				{
					ListRemove(Client);
				}
				break;
			}
			}
		}
		else	//失败了获取失败信息
		{
			DWORD Err_msg = GetLastError();
			if (Err_msg == WAIT_TIMEOUT)
			{
				continue;
			}
			else if(lpOverlapped != NULL)
			{
				ListRemove(Client);
			}
			else
			{
				cout << Err_msg << endl;
				break;
			}
		}



	}
	return 0;
}



int main()
{
	SYSTEM_INFO systeminfo;
	GetSystemInfo(&systeminfo);

	int CpuNums = systeminfo.dwNumberOfProcessors;
	static const int Threadnums = CpuNums * 2;

	HANDLE hThreadHandle[128];	//线程的返回句柄

	if ((cp = CreateIoCompletionPort(
		((HANDLE)(LONG_PTR)-1),
		NULL,
		0,
		0
	)) == NULL)
	{
		return GetLastError();
	}

	for (int i = 0; i < Threadnums; i++)
	{
		hThreadHandle[i] = (HANDLE)_beginthreadex(
			NULL,// 安全属性, 为NULL时表示默认安全性
			0,// 线程的堆栈大小, 一般默认为0
			Run, // 所要启动的线程函数
			cp, // 线程函数的参数, 是一个void*类型, 传递多个参数时用结构体
			0,// 新线程的初始状态,0表示立即执行,CREATE_SUSPENDED表示创建之后挂起
			NULL);   // 用来接收线程ID
	}

	//启动windows套接字
	WSADATA WsaData;
	int err = WSAStartup(MAKEWORD(2, 2), &WsaData);
	if (err != 0)
	{
		return -1;
	}
	//创建Socket,监听Socket
	SOCKET Listen = INVALID_SOCKET;
	if ((Listen = WSASocket(
		AF_INET,//IPV4
		SOCK_STREAM, /*
		TCP一种套接字类型,
		它通过 OOB 数据传输机制提供排序的可靠双向基于连接的字节流。
		此套接字类型使用 Internet 地址系列(AF_INET 或AF_INET6)
		的传输控制协议(TCP)   SOCK_DGRAM是udp */
		0, //协议
		NULL,//指向 WSAPROTOCOL_INFO 结构的指针,
		//该结构定义要创建的套接字的特征。 如果此参数不为 NULL,
		//则套接字将绑定到与指示 的 WSAPROTOCOL_INFO 结构关联的提供程序。
		0,
		WSA_FLAG_OVERLAPPED//
	)) == INVALID_SOCKET)
	{
		WSACleanup();
		return -1;
	}
	sockaddr_in Sin;
	Sin.sin_family = AF_INET;
	Sin.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	Sin.sin_port = htons(8888);			//端口号

	if (bind(Listen, (SOCKADDR*)&Sin, sizeof(Sin)) == SOCKET_ERROR)
	{
		closesocket(Listen);
		WSACleanup();
		return -1;
	}

	if (listen(Listen, SOMAXCONN))
	{
		closesocket(Listen);
		WSACleanup();
		return -1;
	}

	//iocp投递
	SOCKET ClientAccpet = INVALID_SOCKET;
	SOCKADDR_IN ClientAccpetAddr;
	int ClientAccpetAddrLen = sizeof(ClientAccpetAddr);

	for (;;) 
	{
		//阻塞进程
		ClientAccpet = WSAAccept(Listen, (SOCKADDR*)&ClientAccpetAddr,
			&ClientAccpetAddrLen, NULL, 0);

		if (ClientAccpet == SOCKET_ERROR)
		{
			break;
		}

		FClient* InClient = new FClient(ClientAccpet, ClientAccpetAddr);
		ClientList.push_back(InClient);

		//绑定完成端口
		if (CreateIoCompletionPort(
			(HANDLE)ClientAccpet,
			cp,
			(DWORD)InClient,
			0
		) == NULL)
		{
			break;
		}

		if (!InClient->Recv())
		{
			ListRemove(InClient);
		}
	}
	
	//销毁线程池
	for (int i = 0; i < Threadnums; i++)
	{
		PostQueuedCompletionStatus(cp, 0, NULL, NULL);
		CloseHandle(hThreadHandle[i]);
	}
	WaitForMultipleObjects(Threadnums, hThreadHandle, TRUE, INFINITE);

	return 0;
}

 客户端

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib") 

int main()
{
	//启动windows套接字
	WSADATA WsaData;
	int Ret = 0;
	if ((Ret = WSAStartup(MAKEWORD(2, 1), &WsaData)) != 0)
	{
		return -1;
	}

	for (;;)
	{
		//创建套接字
		SOCKET ClientSocket = socket(
			AF_INET,
			SOCK_STREAM,
			IPPROTO_TCP		//tcp协议 IPPROTO_IP IP协议
		);

		sockaddr_in Sin;
		Sin.sin_family = AF_INET;
		Sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
		Sin.sin_port = htons(8888);			//端口号

		//检测连接
		if (
			connect(
			
				ClientSocket,
			
				(SOCKADDR*)&Sin,
			
				sizeof(Sin)
			) == SOCKET_ERROR)
		{
			//错误则关闭套接字
			closesocket(ClientSocket);
			break;
		}

		//发报文
		char buffer[1024] = { 0 };
		sprintf_s(buffer, 1024, "Hello I'm client %d \n", ClientSocket);

		send(ClientSocket, buffer, strlen(buffer), 0);

		memset(buffer, 0, 1024);
		recv(ClientSocket, buffer, sizeof(buffer), 0);//阻塞

		printf(buffer);

		closesocket(ClientSocket);
	}
	WSACleanup();
	return 0;

}

 调试可以看到服务端启动后各个线程通过调用GetQueuedCompletionStatus()进入队列任务等待

 服务端能成功接收客户端发来的报文。并反馈报文给客户端。

 最终等32个线程跑完后,可以在控制台看到打印结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值