前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >IOCP反射服务器

IOCP反射服务器

作者头像
_gongluck
发布2018-03-08 15:34:07
8020
发布2018-03-08 15:34:07
举报

这两天学习了一下IOCP网络模型。

主要参考了这两片文章:http://blog.csdn.net/neicole/article/details/7549497/和http://blog.csdn.net/piggyxp/article/details/6922277

IOCP是我见过的最复杂的网络模型了,在Windows里肯定就是boss了,而且一开始我感觉IOCP甚至比epoll还要复杂(其实epoll也不复杂,全部都不复杂,只是不懂的人觉得复杂~)。当仔细研究一下之后,觉得也就

代码语言:javascript
复制
也像我很纠结的公事 此际回头看    
原来并没有事
真想不到当初我们也讨厌吃苦瓜
今天竟吃得出那睿智愈来愈记挂

私以为,掌握IOCP的关键应该是异步的概念和回调。

Talk is cheap, show you the code !

Server.cpp

代码语言:javascript
复制
#include <WinSock2.h>
#include <Windows.h>
#include <stdio.h>

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

#define DEFAULTPORT 6000
#define DEFAULTSIZE 1024

/** 
 * 结构体名称:PER_IO_DATA 
 * 结构体功能:重叠I/O需要用到的结构体,临时记录IO数据
 **/  
typedef struct  
{  
    OVERLAPPED overlapped;
    char buffer[DEFAULTSIZE];  
    int BufferLen;  
    bool readflag;  
}PER_IO_DATA, *LPPER_IO_DATA;  
  
/** 
 * 结构体名称:PER_HANDLE_DATA 
 * 结构体存储:记录单个套接字的数据,包括了套接字的变量及套接字的对应的客户端的地址。 
 * 结构体作用:当服务器连接上客户端时,信息存储到该结构体中,知道客户端的地址以便于回访。 
 **/  
typedef struct  
{  
    SOCKET socket;  
    SOCKADDR_IN ClientAddr;  
}PER_SOCKET_DATA, *LPPER_SOCKET_DATA; 

DWORD WINAPI ServerWorkThread(LPVOID CompletionPortID)
{
	HANDLE CompletionPort = (HANDLE)CompletionPortID;  
	DWORD BytesTransferred; 
	LPPER_SOCKET_DATA PerSocketData = NULL;  
	LPPER_IO_DATA PerIoData = NULL;  
	DWORD RecvBytes;  
	DWORD Flags = 0;  
	BOOL bRet = false;
	while(true)
	{   
		bRet = GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (PULONG_PTR)&PerSocketData, (LPOVERLAPPED*)&PerIoData, INFINITE);  
		if(bRet == FALSE)
		{  
			printf("GetQueuedCompletionStatus Error:%d\n", GetLastError());
			if(PerSocketData != NULL)
			{
				if(PerSocketData->socket != NULL)
					closesocket(PerSocketData->socket);
				free(PerSocketData);
			}
			if(PerIoData != NULL)
				free(PerIoData);
			continue;
		}  
		//PerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(IpOverlapped, PER_IO_DATA, overlapped);  

		//http://blog.csdn.net/mylovepan/article/details/8204126
		//http://bbs.csdn.net/topics/390719212
		//1.关闭窗口或者停止调试windows会释放句柄的,socket会被关闭并向对方发送断开连接
		//2.对方关闭socket后,WSARecv操作的GetQueuedCompletionStatus返回值是真,不过收到的字节数BytesTransferred是0,这里判断。
		//GetQueuedCompletionStatus返回TRUE并且读取到的数据长度为0时,关闭套接字
		if(BytesTransferred == 0)
		{  
			printf("BytesTransferred==0:%d\n", GetLastError());
			if(PerSocketData != NULL)
			{
				if(PerSocketData->socket != NULL)
					closesocket(PerSocketData->socket);
				free(PerSocketData);
			}
			if(PerIoData != NULL)
				free(PerIoData);
			continue;
		}  

		int iRes = 0;
		Flags = 0;
		if(PerIoData->readflag == true)
		{
		// 开始数据处理,接收来自客户端的数据
		printf("%s says: %s\n", inet_ntoa(PerSocketData->ClientAddr.sin_addr), PerIoData->buffer);
		//send(PerSocketData->socket, PerIoData->buffer, strlen(PerIoData->buffer)+1, 0);
		memset(&PerIoData->overlapped, 0, sizeof(PerIoData->overlapped));
		PerIoData->readflag = false;
		WSABUF wsabuf;
		wsabuf.buf = PerIoData->buffer;
		wsabuf.len = sizeof(PerIoData->buffer);
		iRes = WSASend(PerSocketData->socket, &wsabuf, 1, &RecvBytes, Flags, &PerIoData->overlapped, NULL);
		}
		else
		{
		//为下一个重叠调用建立单I/O操作数据
		memset(PerIoData, 0, sizeof(PER_IO_DATA)); // 清空内存
		PerIoData->readflag = true;
		WSABUF wsabuf;
		wsabuf.buf = PerIoData->buffer;
		wsabuf.len = sizeof(PerIoData->buffer);
		iRes = WSARecv(PerSocketData->socket, &wsabuf, 1, &RecvBytes, &Flags, &PerIoData->overlapped, NULL);
		}
	}

	return 0;
}

int main(int argc, char* argv[])
{
	//加载socket动态链接库
	WORD wVersionRequested = MAKEWORD(2, 2); //请求2.2版本的WinSock库  
	WSADATA wsaData;    //接收Windows Socket的结构信息
	DWORD err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)//检查套接字库是否申请成功
	{  
		printf("Request Windows Socket Library Error!\n");  
		system("pause");
		return -1;
	}  
	if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)//检查是否申请了所需版本的套接字库
	{
		WSACleanup();  
		printf("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 completionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0);  
    if (completionPort == NULL)//创建IO内核对象失败  
	{    
        printf("CreateIoCompletionPort failed. Error:%d\n", GetLastError());  
        system("pause");  
        return -1;  
    }

	// 创建IOCP线程--线程里面创建线程池  
	// 确定处理器的核心数量  
	SYSTEM_INFO mySysInfo;  
	GetSystemInfo(&mySysInfo);  
	// 基于处理器的核心数量创建线程  
	for(DWORD i = 0; i < (mySysInfo.dwNumberOfProcessors * 2); ++i)
	{  
		//创建服务器工作器线程,并将完成端口传递到该线程  
		HANDLE ThreadHandle = CreateThread(NULL, 0, ServerWorkThread, completionPort, 0, NULL);  
		if(ThreadHandle == NULL){  
			printf("Create Thread Handle failed. Error:%d\n", GetLastError());  
			system("pause");  
			return -1;  
		}  
		CloseHandle(ThreadHandle);  
	}

	// 建立流式套接字  
	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(DEFAULTPORT);
	int bindResult = bind(srvSocket, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR));  
	if(bindResult == SOCKET_ERROR)
	{  
		printf("Bind failed. Error:", GetLastError());  
		system("pause");  
		return -1;  
	}  
	// 将SOCKET设置为监听模式  
	int listenResult = listen(srvSocket, 10);  
	if(listenResult == SOCKET_ERROR)
	{  
		printf("Listen failed. Error:%d\n", GetLastError());  
		system("pause");  
		return -1;  
	}

	//开始处理IO数据  
	printf("服务器已准备就绪,正在等待客户端的接入...\n");
	while(true)
	{
		SOCKET acceptSocket;  
		//接收连接,并分配完成端,这儿可以用AcceptEx()  
		SOCKADDR_IN saRemote;  
		int RemoteLen = sizeof(saRemote);  
		acceptSocket = accept(srvSocket, (SOCKADDR*)&saRemote, &RemoteLen);  
		if(acceptSocket == SOCKET_ERROR)// 接收客户端失败  
		{   
			printf("Accept Socket Error:%d\n", GetLastError());  
			system("pause");  
			return -1;  
		}
		//创建用来和套接字关联的单句柄数据信息结构  
		LPPER_SOCKET_DATA PerSocketData = (LPPER_SOCKET_DATA)malloc(sizeof(PER_SOCKET_DATA));  //在堆中为这个PerHandleData申请指定大小的内存  
		PerSocketData->socket = acceptSocket;
		memcpy(&PerSocketData->ClientAddr, &saRemote, RemoteLen);
		// 将接受套接字和完成端口关联  
		CreateIoCompletionPort((HANDLE)(PerSocketData->socket), completionPort, (DWORD)PerSocketData, 0); 

		// 开始在接受套接字上处理I/O使用重叠I/O机制  
		// 在新建的套接字上投递一个或多个异步  
		// WSARecv或WSASend请求,这些I/O请求完成后,工作者线程会为I/O请求提供服务
		// 单I/O操作数据(I/O重叠)
		LPPER_IO_DATA PerIoData = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));  
		memset(PerIoData, 0, sizeof(PER_IO_DATA));  
		PerIoData->readflag = true;    // read  

		DWORD RecvBytes;  
		DWORD Flags = 0;
		WSABUF wsabuf;
		wsabuf.buf = PerIoData->buffer;
		wsabuf.len = sizeof(PerIoData->buffer);
		WSARecv(PerSocketData->socket, &wsabuf, 1, &RecvBytes, &Flags, &PerIoData->overlapped, NULL);
	}

	system("pause");
	return 0;
}

服务器的代码不到200行,其实也就是几个API函数的调用,但是效率却明显比其他(Windows)模型要高!

Client.cpp

代码语言:javascript
复制
#include <winsock2.h>  
#include <Windows.h>
#include <stdio.h>

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

#define DEFAULTPORT 6000
#define DEFAULTSIZE 1024 

int main(int argc, char* argv[])  
{  
	//加载socket动态链接库
	WORD wVersionRequested = MAKEWORD(2, 2); //请求2.2版本的WinSock库  
	WSADATA wsaData;    //接收Windows Socket的结构信息
	DWORD err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)//检查套接字库是否申请成功
	{  
		printf("Request Windows Socket Library Error!\n");  
		system("pause");
		return -1;
	}  
	if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)//检查是否申请了所需版本的套接字库
	{
		WSACleanup();  
		printf("Request Windows Socket Version 2.2 Error!\n");  
		system("pause");  
		return -1;  
	}

	// 创建socket操作,建立流式套接字,返回套接字号sockClient  
	SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);  
	if(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(DEFAULTPORT);  
	while(SOCKET_ERROR == connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{  
		// 如果还没连接上服务器则要求重连  
		printf("服务器连接失败,是否重新连接?(Y/N):");  
		char choice;  
		while((choice = getchar()) && (!((choice != 'Y' && choice == 'N') || (choice == 'Y' && choice != 'N'))))
		{  
			printf("输入错误,请重新输入:");
		}  
		if (choice == 'Y'){  
			continue;  
		}  
		else
		{  
			printf("退出系统中...\n");  
			system("pause");  
			return 0;  
		}  
	}   
	printf("客户端已准备就绪,直接输入文字向服务器反馈信息。\n");  

	char buf[DEFAULTSIZE];
	int iRes = 0;
	while(true)
	{  
		gets(buf);
		if(buf[0] == 'q')
		{  
			break;  
		}  
		else
		{  
			printf("I Say:(\"quit\"to exit):%s\n",buf);  
			iRes = send(sockClient, buf, strlen(buf)+1, 0); // 发送信息
			memset(buf, 0, DEFAULTSIZE);
			iRes = recv(sockClient, buf, DEFAULTSIZE, 0);
			printf("Server Say:%s\n",buf);
		}  
	}  

	closesocket(sockClient);  
	WSACleanup();   // 终止对套接字库的使用  
	printf("End linking...\n");   
	system("pause");  
	return 0;
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017年12月04日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档