IOCP反射服务器

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

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

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

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

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

Talk is cheap, show you the code !

Server.cpp

#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

#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;
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏xingoo, 一个梦想做发明家的程序员

开启服务和停止服务

Start函数用于开启服务 1 初始化状态变量 2 创建监听套接字 3 加载使用扩展API函数 4 创建完成端口对象 5 建立监听套接字和完成端口对象间的关联 ...

2628
来自专栏芋道源码1024

Nginx基础——Rewrite规则

rewrite是nginx一个特别重要的指令,该指令可以使用正则表达式改写URI。可以指定一个或多个rewrite指令,按顺序匹配。

641
来自专栏高性能服务器开发

windows完成端口(二)

系列目录 windows完成端口(一) windows完成端口(二) windows完成端口(三) windows完成端口(四) windows完成端口(五) ...

35511
来自专栏Golang语言社区

服务器端Go程序对长短链接的处理及运行参数的保存

对长、短连接的处理策略(模拟心跳) 作为一个可能会和很多Client进行通讯交互的Server,首先要保证的就是整个Server运行状态的稳定性,因此在和Cli...

4117
来自专栏架构说

tcp如何维护长连接

上次提到tcp数据流无边界特点 还有一个特点那就是 TCP有长连接和短连接之分 目录结构: ? tcp连接的终止 — 01 — socke正常关闭 流程:...

3789
来自专栏SeanCheney的专栏

Scrapy使用随机IP代理插件Scrapy-Proxies

使用Scrapy_Proxies随机IP代理插件 https://github.com/aivarsk/scrapy-proxies ---- 安装: pip...

3528
来自专栏Golang语言社区

Go语言简单的TCP编程

前期准备 ---- 需要import "net"包 IP类型,其中一个重要的方法是IP.ParseIP(ipaddr string)来判断是否是合法的IP地址 ...

2664
来自专栏蓝天

socket的五大误区

第一个隐患很明显,但它是开发新手最容易犯的一个错误。如果您忽略函数的返回状态,当它们失败或部分成功的时候,您也许会迷失。反过来,这可能传播错误,使定位问题的源头...

682
来自专栏coder修行路

socket编程进阶

1、   动态导入模块 第一种方法(python解释器自己内部用的): ? 上图是我程序的目录结构 下面代码是动态导入模块3.py的源码: 1 #AUTHOR:...

3068
来自专栏知识分享

五,ESP8266 TCP服务器多连接(基于Lua脚本语言)

一些时间去准备朋友的元器件了... 接着写,,争取今天写完所有的文章,,因为答应了朋友下周5之前要做好朋友的东西 对于TCP大家在玩AT指令的时候有没有发现客户...

4767

扫码关注云+社区