简单的IOCP程序(TCP)

// tcp_iocp_serve.cpp

#include <WinSock2.h>
#include <Windows.h>
#include <vector>
#include <iostream>

using namespace std;

#pragma comment(lib, "Ws2_32.lib")      // Socket编程需用的动态链接库
#pragma comment(lib, "Kernel32.lib")    // IOCP需要用到的动态链接库

/**
* 结构体名称:PER_IO_DATA
* 结构体功能:重叠I/O需要用到的结构体,临时记录IO数据
**/
const int g_nDataBuffSize = 2 * 1024;
typedef struct
{
	OVERLAPPED sOverLapped;
	WSABUF sDatabuff;
	char szBuffer[g_nDataBuffSize];
	int nBufferLen;
	int nOperationType;
}PER_IO_OPERATEION_DATA, *LPPER_IO_OPERATION_DATA, *LPPER_IO_DATA, PER_IO_DATA;

/**
* 结构体名称:PER_HANDLE_DATA
* 结构体存储:记录单个套接字的数据,包括了套接字的变量及套接字的对应的客户端的地址。
* 结构体作用:当服务器连接上客户端时,信息存储到该结构体中,知道客户端的地址以便于回访。
**/
typedef struct
{
	SOCKET socket;
	SOCKADDR_STORAGE clientaddr;
}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

// 定义全局变量
const int g_nDefaultPort = 6000;
vector < PER_HANDLE_DATA* > g_vctClientGroup;      // 记录客户端的向量组

HANDLE g_hMutex = CreateMutex(NULL, FALSE, NULL);
DWORD WINAPI ServerWorkThread(LPVOID lpCompletionPortID);
DWORD WINAPI ServerSendThread(LPVOID lpParam);

// 开始主函数
int main()
{
	// 加载socket动态链接库
	WORD wVersionRequested = MAKEWORD(2, 2); // 请求2.2版本的WinSock库
	WSADATA wsaData;    // 接收Windows Socket的结构信息
	DWORD dwErr = WSAStartup(wVersionRequested, &wsaData);

	if (0 != dwErr)
	{
		// 检查套接字库是否申请成功
		cerr << "Request Windows Socket Library Error!\n";
		system("pause");
		return -1;
	}
	if (LOBYTE(wsaData.wVersion) != 2
		|| HIBYTE(wsaData.wVersion) != 2)
	{
		// 检查是否申请了所需版本的套接字库
		WSACleanup();
		cerr << "Request Windows Socket Version 2.2 Error!\n";
		system("pause");
		return -1;
	}

	// 创建IOCP的内核对象
	/**
	* 需要用到的函数的原型:
	* HANDLE WINAPI CreateIoCompletionPort(
	*    __in   HANDLE FileHandle,     // 已经打开的文件句柄或者空句柄,一般是客户端的句柄
	*    __in   HANDLE ExistingCompletionPort, // 已经存在的IOCP句柄
	*    __in   ULONG_PTR CompletionKey,   // 完成键,包含了指定I/O完成包的指定文件
	*    __in   DWORD NumberOfConcurrentThreads // 真正并发同时执行最大线程数,一般推介是CPU核心数*2
	* );
	**/
	HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
	if (NULL == hCompletionPort)
	{
		// 创建IO内核对象失败
		cerr << "CreateIoCompletionPort failed. Error:" << GetLastError() << endl;
		system("pause");
		return -1;
	}

	// 创建IOCP线程--线程里面创建线程池

	// 确定处理器的核心数量
	SYSTEM_INFO sSysInfo;
	GetSystemInfo(&sSysInfo);

	// 基于处理器的核心数量创建线程
	for (DWORD i = 0; i < (sSysInfo.dwNumberOfProcessors * 2); ++i)
	{
		// 创建服务器工作器线程,并将完成端口传递到该线程
		HANDLE hThreadHandle = CreateThread(NULL, 0, ServerWorkThread, hCompletionPort, 0, NULL);
		if (NULL == hThreadHandle)
		{
			cerr << "Create Thread Handle failed. Error:" << GetLastError() << endl;
			system("pause");
			return -1;
		}
		CloseHandle(hThreadHandle);
	}

	// 建立流式套接字
	SOCKET srvSocket = socket(AF_INET, SOCK_STREAM, 0);

	// 绑定SOCKET到本机
	SOCKADDR_IN srvAddr;
	srvAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	srvAddr.sin_family = AF_INET;
	srvAddr.sin_port = htons(g_nDefaultPort);
	int nBindResult = bind(srvSocket, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR));
	if (SOCKET_ERROR == nBindResult)
	{
		cerr << "Bind failed. Error:" << GetLastError() << endl;
		system("pause");
		return -1;
	}

	// 将SOCKET设置为监听模式
	int nListenResult = listen(srvSocket, 10);
	if (SOCKET_ERROR == nListenResult)
	{
		cerr << "Listen failed. Error: " << GetLastError() << endl;
		system("pause");
		return -1;
	}

	// 开始处理IO数据
	cout << "本服务器已准备就绪,正在等待客户端的接入...\n";

	// 创建用于发送数据的线程
	HANDLE hSendThread = CreateThread(NULL, 0, ServerSendThread, 0, 0, NULL);

	while (true)
	{
		PER_HANDLE_DATA * pPerHandleData = NULL;
		SOCKADDR_IN saRemote;
		int nRemoteLen = 0;
		SOCKET acceptSocket;

		// 接收连接,并分配完成端,这儿可以用AcceptEx()
		nRemoteLen = sizeof(saRemote);
		acceptSocket = accept(srvSocket, (SOCKADDR*)&saRemote, &nRemoteLen);
		if (SOCKET_ERROR == acceptSocket)
		{
			// 接收客户端失败
			cerr << "Accept Socket Error: " << GetLastError() << endl;
			system("pause");
			return -1;
		}

		// 创建用来和套接字关联的单句柄数据信息结构
		pPerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));  // 在堆中为这个PerHandleData申请指定大小的内存
		pPerHandleData->socket = acceptSocket;
		memcpy(&pPerHandleData->clientaddr, &saRemote, nRemoteLen);
		g_vctClientGroup.push_back(pPerHandleData);       // 将单个客户端数据指针放到客户端组中

		// 将接受套接字和完成端口关联
		CreateIoCompletionPort((HANDLE)(pPerHandleData->socket), hCompletionPort, (DWORD)pPerHandleData, 0);


		// 开始在接受套接字上处理I/O使用重叠I/O机制
		// 在新建的套接字上投递一个或多个异步
		// WSARecv或WSASend请求,这些I/O请求完成后,工作者线程会为I/O请求提供服务
		// 单I/O操作数据(I/O重叠)
		LPPER_IO_OPERATION_DATA perIoData = NULL;
		perIoData = (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_OPERATEION_DATA));
		ZeroMemory(&(perIoData->sOverLapped), sizeof(OVERLAPPED));
		perIoData->sDatabuff.len = 1024;
		perIoData->sDatabuff.buf = perIoData->szBuffer;
		perIoData->nOperationType = 0;    // read

		DWORD dwRecvBytes;
		DWORD dwFlags = 0;
		WSARecv(pPerHandleData->socket, &(perIoData->sDatabuff), 1, &dwRecvBytes, &dwFlags, &(perIoData->sOverLapped), NULL);
	}

	system("pause");
	return 0;
}

// 开始服务工作线程函数
DWORD WINAPI ServerWorkThread(LPVOID lpParam)
{
	HANDLE hCompletionPort = (HANDLE)lpParam;
	DWORD dwBytesTransferred = 0;
	LPOVERLAPPED lpOverlapped;
	LPPER_HANDLE_DATA perHandleData = NULL;
	LPPER_IO_DATA perIoData = NULL;
	DWORD dwRecvBytes = 0;
	DWORD dwFlags = 0;
	BOOL bRet = FALSE;

	while (true)
	{
		bRet = GetQueuedCompletionStatus(hCompletionPort, &dwBytesTransferred, (PULONG_PTR)&perHandleData, (LPOVERLAPPED*)&lpOverlapped, INFINITE);
		if (bRet == 0)
		{
			cerr << "GetQueuedCompletionStatus Error: " << GetLastError() << endl;
			return -1;
		}
		perIoData = (LPPER_IO_DATA)CONTAINING_RECORD(lpOverlapped, PER_IO_DATA, sOverLapped);

		// 检查在套接字上是否有错误发生
		if (0 == dwBytesTransferred)
		{
			closesocket(perHandleData->socket);
			GlobalFree(perHandleData);
			GlobalFree(perIoData);
			continue;
		}

		// 开始数据处理,接收来自客户端的数据
		WaitForSingleObject(g_hMutex, INFINITE);
		cout << "A Client says: " << perIoData->sDatabuff.buf << endl;
		ReleaseMutex(g_hMutex);

		// 为下一个重叠调用建立单I/O操作数据
		ZeroMemory(&(perIoData->sOverLapped), sizeof(OVERLAPPED)); // 清空内存
		perIoData->sDatabuff.len = 1024;
		perIoData->sDatabuff.buf = perIoData->szBuffer;
		perIoData->nOperationType = 0;    // read
		WSARecv(perHandleData->socket, &(perIoData->sDatabuff), 1, &dwRecvBytes, &dwFlags, &(perIoData->sOverLapped), NULL);
	}

	return 0;
}


// 发送信息的线程执行函数
DWORD WINAPI ServerSendThread(LPVOID /*lpParam*/)
{
	while (true)
	{
		char szTalk[200] = {0};
		gets_s(szTalk);
		int nLen = 0;
		for (nLen = 0; szTalk[nLen] != '\0'; ++nLen)
		{
			// 找出这个字符组的长度
		}
		szTalk[nLen] = '\n';
		szTalk[++nLen] = '\0';
		cout << "I Say:" << szTalk;
		WaitForSingleObject(g_hMutex, INFINITE);
		for (int i = 0; i < (int)g_vctClientGroup.size(); ++i)
		{
			send(g_vctClientGroup[i]->socket, szTalk, 200, 0);  // 发送信息
		}
		ReleaseMutex(g_hMutex);
	}

	return 0;
}


// tcp_iocp_client.cpp

#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <winsock2.h>
#include <Windows.h>

using namespace std;

#pragma comment(lib, "Ws2_32.lib")      // Socket编程需用的动态链接库

SOCKET g_sockClient;                    // 连接成功后的套接字
HANDLE g_hBufferMutex = NULL;           // 令其能互斥成功正常通信的信号量句柄
const int g_nDefaultPort = 6000;

DWORD WINAPI SendMessageThread(LPVOID lpParam);
DWORD WINAPI ReceiveMessageThread(LPVOID lpParam);

int main()
{
	// 加载socket动态链接库(dll)
	WORD wVersionRequested;
	WSADATA wsaData;    // 这结构是用于接收Wjndows Socket的结构信息的
	wVersionRequested = MAKEWORD(2, 2);   // 请求2.2版本的WinSock库
	int nErr = WSAStartup(wVersionRequested, &wsaData);
	if (nErr != 0) 
	{
		// 返回值为零的时候是表示成功申请WSAStartup
		return -1;
	}
	if (LOBYTE(wsaData.wVersion) != 2
		|| HIBYTE(wsaData.wVersion) != 2)
	{
		// 检查版本号是否正确
		WSACleanup();
		return -1;
	}

	// 创建socket操作,建立流式套接字,返回套接字号sockClient
	g_sockClient = socket(AF_INET, SOCK_STREAM, 0);
	if (g_sockClient == INVALID_SOCKET)
	{
		printf("Error at socket():%ld\n", WSAGetLastError());
		WSACleanup();
		return -1;
	}

	// 将套接字sockClient与远程主机相连
	// int connect( SOCKET s,  const struct sockaddr* name,  int namelen);
	// 第一个参数:需要进行连接操作的套接字
	// 第二个参数:设定所需要连接的地址信息
	// 第三个参数:地址的长度
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");      // 本地回路地址是127.0.0.1;
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(g_nDefaultPort);
	while (SOCKET_ERROR == connect(g_sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
		// 如果还没连接上服务器则要求重连
		cout << "服务器连接失败,是否重新连接?(Y/N):";
		char choice;
		while (cin >> choice && (!((choice != 'Y' && choice == 'N') || (choice == 'Y' && choice != 'N'))))
		{
			cout << "输入错误,请重新输入:";
			cin.sync();
			cin.clear();
		}
		if (choice == 'Y')
		{
			continue;
		}
		else
		{
			cout << "退出系统中...";
			system("pause");
			return 0;
		}
	}
	cin.sync();
	cout << "本客户端已准备就绪,用户可直接输入文字向服务器反馈信息。\n";

	send(g_sockClient, "\nAttention: A Client has enter...\n", 200, 0);

	g_hBufferMutex = CreateSemaphore(NULL, 1, 1, NULL);


	HANDLE hSendThread = CreateThread(NULL, 0, SendMessageThread, NULL, 0, NULL);
	HANDLE hReceiveThread = CreateThread(NULL, 0, ReceiveMessageThread, NULL, 0, NULL);


	WaitForSingleObject(hSendThread, INFINITE);  // 等待线程结束
	closesocket(g_sockClient);
	CloseHandle(hSendThread);
	CloseHandle(hReceiveThread);
	CloseHandle(g_hBufferMutex);
	WSACleanup();   // 终止对套接字库的使用

	printf("End linking...\n");
	printf("\n");

	system("pause");

	return 0;
}


DWORD WINAPI SendMessageThread(LPVOID /*lpParam*/)
{
	while (true)
	{
		string strTalk;
		getline(cin, strTalk);
		WaitForSingleObject(g_hBufferMutex, INFINITE);     // P(资源未被占用)
		if ("quit" == strTalk)
		{
			strTalk.push_back('\0');
			send(g_sockClient, strTalk.c_str(), 200, 0);
			break;
		}
		else
		{
			strTalk.append("\n");
		}
		printf("\nI Say:(\"quit\"to exit):");
		cout << strTalk;
		int nSendResult = send(g_sockClient, strTalk.c_str(), /*200*/strTalk.length() + 1, 0); // 发送信息
		ReleaseSemaphore(g_hBufferMutex, 1, NULL);     // V(资源占用完毕)

		if (SOCKET_ERROR == nSendResult)
		{
			cout << "Send failed.Error:" << WSAGetLastError() << endl;
			break ;
		}
	}

	return 0;
}


DWORD WINAPI ReceiveMessageThread(LPVOID /*lpParam*/)
{
	while (true)
	{
		char szRecvBuf[300] = {0};

		int nRecvResult = recv(g_sockClient, szRecvBuf, 200, 0);

		if (SOCKET_ERROR == nRecvResult)
		{
			cout << "Recv failed.Error:" << WSAGetLastError() << endl;
			break ;
		}

		WaitForSingleObject(g_hBufferMutex, INFINITE);     // P(资源未被占用)

		printf("%s Says: %s", "Server", szRecvBuf);        // 接收信息

		ReleaseSemaphore(g_hBufferMutex, 1, NULL);         // V(资源占用完毕)
	}

	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值