完成端口学习笔记(二):完成端口实现机制的仿真

        上一篇关于“完成端口”的文章中,我们讲解了在同一个控制台程序中完成本地文件的拷贝,利用的是ReadFile和WriteFile两个API函数。本篇将讲解一下怎样利用完成端口来完成网络文件的拷贝,即通过网络将客户端的文件传输到服务端。只要用过Windows套接字编程的人都可以很容易的实现这个转换过程。本博文中首先直接给出利用完成端口来实现文件网络传输的核心示例代码,然后通过解决上述过程“内存泄露”问题来详细学习完成端口中用到的几个核心数据结构;紧接着给出完成端口实现网络传输的整体流程,并按照此流程对“完成端口”的实现进行仿真;最后是对“缓冲区”的一点点见解和看法。

一、完成端口完成网络文件传输

二、单句柄数据和单IO数据

三、完成端口实现网络传输的流程示意图

四、自己仿真完成端口机制

五、缓冲区

一、完成端口完成网络文件传输

/*-----------IOCP.h--------------*/

#define PORT 2234
#define DATA_BUFSIZE 1024

/*
*-----IO操作类型
*/

enum OPERATE_TYPE{START,TRANSFER,DROP};
/*
*-----单句柄数据
*/
typedef struct _THREAD_PARAM
{

	HANDLE hCompletePort;
	int ThreadNo;


}PERTHREADPARAM,*PPERTHREADPARAM;
/*
*-----单套接字句柄数据
*/
typedef struct _SOCKET_PARAM
{
	SOCKET s;
	sockaddr_in addr;
}PERSOCKETPARAM,*PPERSOCKETPARAM;
/*
*-----单IO数据
*/
typedef struct _IO_PARAM
{
	OVERLAPPED o;
	WSABUF Buffer;
	char szMessage[DATA_BUFSIZE];
	DWORD NumberofBytesRecvd;
	DWORD Flag;
	OPERATE_TYPE OperateType;
	char FileName[DATA_BUFSIZE];//2013-12-21 异步文件传输时文件名
	DWORD ReceivedLength;//2013-12-21 文件已经接收的长度
	DWORD FileLength;//2013-12-21 文件总长度
}PERIOPARAM,*PPERIOPARAM;

DWORD WINAPI WorkFunction(LPVOID p);

/*--------------IOCP-Server.cpp----------*/
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <WinSock2.h>
#include "IOCP.h"
#include <vector>
#include <tchar.h>
#include <winerror.h>


#pragma comment(lib,"ws2_32.lib")
using namespace std;

#define ZS_OUTPUT_ERROR (printf("套接字动态库初始化失败,错误代码是%d\n",WSAGetLastError()))

int main()
{
	WSAData wasData;
	if(WSAStartup(MAKEWORD(2,2),&wasData)==WSANOTINITIALISED)
	{
		//printf("套接字动态库初始化失败,错误代码是%d\n",WSAGetLastError());
		ZS_OUTPUT_ERROR;
		return 0;
	}

	/*----------------------------------------------------------
	 *cout输出重定向
	 *----------------------------------------------------------
	*/
	streambuf* coutbuf=cout.rdbuf();
	ofstream of("c:\\server.txt");
	streambuf* filebuf=of.rdbuf();
	cout.rdbuf(filebuf);

	/*----------------------------------------------------------
	 *创建完成端口
	 *----------------------------------------------------------
	*/
	HANDLE CompletionPort=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
	
	/*----------------------------------------------------------
	 *提取系统的信息,获取内核个数
	 *----------------------------------------------------------
	*/

	SYSTEM_INFO sysInfo;
	GetSystemInfo(&sysInfo);
	
	/*----------------------------------------------------------
	 *创建指定个数的工作者线程
	 *----------------------------------------------------------
	*/
	for(int i=0;i</*sysInfo.dwNumberOfProcessors*/1;i++)
	{
		//构造线程参数
		PPERTHREADPARAM pHandle=new PERTHREADPARAM;
		pHandle->hCompletePort=CompletionPort;
		pHandle->ThreadNo=i;

		DWORD dwThreadID;
		CreateThread(NULL,0,WorkFunction,(LPVOID)pHandle,0,&dwThreadID);
	
	}
	
	/*----------------------------------------------------------
	 *创建、绑定、监听套接字
	 *----------------------------------------------------------
	*/
	
	SOCKET sListen;
	sListen=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);

	SOCKADDR_IN local;
	local.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
	local.sin_family=AF_INET;
	local.sin_port=htons(PORT);
	int err=bind(sListen,(sockaddr*)&local,sizeof(SOCKADDR_IN));
	
	listen(sListen,200);
	

	DWORD RecvBytes=0;
	DWORD Flags=0;
	while(true)
	{


		SOCKADDR_IN saRemote;
		SOCKET sClient;
		int nLen=sizeof(saRemote);

		sClient=accept(sListen,(sockaddr*)&saRemote,&nLen);

		PPERSOCKETPARAM lSocketData=new PERSOCKETPARAM;
		lSocketData->s=sClient;
		lSocketData->addr=saRemote;
		CreateIoCompletionPort((HANDLE)sClient,CompletionPort,(ULONG_PTR)lSocketData,0);
		
		PPERIOPARAM lIoData=NULL;
		lIoData=new PERIOPARAM;
		cout<<"首次开辟IO句柄参数空间地址"<<(int*)lIoData<<endl;
		memset(lIoData,0,sizeof(PERIOPARAM));/*********关键步骤***************/
		lIoData->Buffer.len=DATA_BUFSIZE;
		lIoData->Buffer.buf=lIoData->szMessage;
		lIoData->OperateType=START;
		cout<<"In Main Function"<<(unsigned int*)lIoData->Buffer.buf<<endl;


		int bRet=WSARecv(lSocketData->s,&(lIoData->Buffer),1,&RecvBytes,&Flags,&(lIoData->o),NULL);
		if(bRet==SOCKET_ERROR)
		{
			if(WSAGetLastError()!=WSA_IO_PENDING)
			{
				cout<<"WSARecv在工作线程中第1种情况的返回值:"<<bRet<<"此时发生中断现象"<<endl;
				cout<<"GetLastError的结果"<<GetLastError()<<endl;

			}

		}

	}


	return 0;
}



DWORD WINAPI WorkFunction(LPVOID p)
{

	//一旦线程创建后,CompeletePort和ThreadNo两个参数就不会改变了
	HANDLE CompeltePort=((PPERTHREADPARAM)p)->hCompletePort;
	int ThreadNo=((PPERTHREADPARAM)p)->ThreadNo;

	DWORD BytesTransferred;

	LPOVERLAPPED lpOverlapped;
	PPERSOCKETPARAM PerSocket=NULL;
	PPERIOPARAM PerIoData=NULL;

	DWORD RecvBytes;
	DWORD Flags=0;
	BOOL bRet=FALSE;

	while(TRUE)
	{
		bRet=GetQueuedCompletionStatus(CompeltePort,&BytesTransferred,(PULONG_PTR)&PerSocket,&lpOverlapped,INFINITE);

		if(bRet==-1)
			return -1;
		PerIoData=(PPERIOPARAM)CONTAINING_RECORD(lpOverlapped,PERIOPARAM,o);

		if(BytesTransferred==0)
		{
			shutdown(PerSocket->s,SD_BOTH);
			closesocket(PerSocket->s);
			delete PerSocket;
			continue;
		}

		/*
		*根据单IO数据类型进行分别处理
		*
		*/
			
		cout<<PerIoData->Buffer.buf<<endl;

		//创建新的单IO数据结构,重新投递IO接收请求
		PPERIOPARAM pIoData=NULL;
		pIoData=new PERIOPARAM;
		memset(pIoData,0,sizeof(PERIOPARAM));
		pIoData->Buffer.len=DATA_BUFSIZE;
		pIoData->Buffer.buf=pIoData->szMessage;
		int bRet=WSARecv(PerSocket->s,&(pIoData->Buffer),1,&RecvBytes,&Flags,&(pIoData->o),NULL);
		if(bRet==SOCKET_ERROR)
		{
			if(WSAGetLastError()!=WSA_IO_PENDING)
			{
				cout<<"WSARecv在工作线程中第1种情况的返回值:"<<bRet<<"此时发生中断现象"<<endl;

			}

		}


		-----------修改1--------------------
		//delete PerIoData;
	
		-----------修改2--------------------
		重复利用同一处缓冲区建,减少内存泄露
		//
		//memset(PerIoData->Buffer.buf,0,DATA_BUFSIZE*sizeof(char));
		
		
		//int bRet=WSARecv(PerSocket->s,&(PerIoData->Buffer),1,&RecvBytes,&Flags,&(PerIoData->o),NULL);
		//if(bRet==SOCKET_ERROR)
		//{
		//	if(WSAGetLastError()!=WSA_IO_PENDING)
		//	{
		//		cout<<"WSARecv在工作线程中第1种情况的返回值:"<<bRet<<"此时发生中断现象"<<endl;

		//	}

		//}
	}
	return 0;
}

/*---------IOCP-Client.cpp----------*/


#include <iostream>
#include <fstream>
#include <stdio.h>
#include <WinSock2.h>


#pragma comment(lib, "ws2_32.lib")  
using namespace std;

#define THREADCOUNT 4
#define PORT 2234
DWORD WINAPI WorkFunction(LPVOID p);



int main( )
{


	/*----------------------------------------------------------
	 *cout输出重定向
	 *----------------------------------------------------------
	*/
	streambuf* coutbuf=cout.rdbuf();
	ofstream of("c:\\client.txt");
	streambuf* filebuf=of.rdbuf();
	cout.rdbuf(filebuf);
	

	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD( 2, 2 );
	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 )
	{
		return 0;
	}
	if ( LOBYTE( wsaData.wVersion ) != 2 ||
		HIBYTE( wsaData.wVersion ) != 2 )
	{
		WSACleanup( );
		return 0;
	};


	//建立客户端socket
ZSZS:SOCKET sockClient = socket( AF_INET ,SOCK_STREAM , 0 ) ;

	//服务器地址
	SOCKADDR_IN addrSrv ;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("219.223.176.81") ;
	addrSrv.sin_family = AF_INET ;
	addrSrv.sin_port = htons( PORT ) ;

	//连接服务器
	int ret=connect( sockClient , (SOCKADDR*)&addrSrv , sizeof(SOCKADDR)) ;
	char SendMsg[]="The mates live with an estimated 35 other river porpoises at the reserve in Shishou, Hubei province.";
	//告诉服务端进行连接测试
	int bRet=send(sockClient,SendMsg, strlen(SendMsg)+1, 0);
	if(bRet==SOCKET_ERROR)
	{
		cout<<"客户端发送连接请求失败,网络断开\n";
		closesocket(sockClient);
		goto ZSZS;
	}
	//客户端发送两次
	char SendMsg2[]="About 21 kilometers of river in the reserve are closed to the public to protect the fetus";
	int bRet2=send(sockClient,SendMsg2, strlen(SendMsg2)+1, 0);
	if(bRet2==SOCKET_ERROR)
	{
		cout<<"客户端发送连接请求失败,网络断开\n";
		closesocket(sockClient);
		goto ZSZS;
	}

	unsigned int c=0;
	while(/*getchar()!='q'*/1)
	{
		char SendMsg2[]="About 21 kilometers of river in the reserve are closed to the public to protect the fetus";
		int bRet2=send(sockClient,SendMsg2, strlen(SendMsg2)+1, 0);
		if(bRet2==SOCKET_ERROR)
		{
			cout<<"客户端发送连接请求失败,网络断开\n";
			closesocket(sockClient);
			goto ZSZS;
		}

	}

	//while(getchar()!='q');
	
	return 0;

}

       上述的代码能够顺利的完成文件的网络传输(更详细的介绍可参加CSDN大牛PiggyXY的相关博文[1])。此处为了简化程序,将客户端设计为无限循环发送单条信息至服务器。利用Process Explorer对服务端程序进行检测发现,随着程序的运行,服务端会出现严重内存泄露直至最终new函数申请堆空间失败,如下图所示:


       其中内存使用最大时刻超过了2G,CPU利用率最大点接近40%,随后利用Intel Inspector对程序中可能出现的内存泄露进行精确定位,发现问题出现在工作线程的GetQueuedCompletionStatus后的代码中,

		//创建新的单IO数据结构,重新投递IO接收请求
		PPERIOPARAM pIoData=NULL;
		pIoData=new PERIOPARAM;
		memset(pIoData,0,sizeof(PERIOPARAM));
		pIoData->Buffer.len=DATA_BUFSIZE;
		pIoData->Buffer.buf=pIoData->szMessage;
		int bRet=WSARecv(PerSocket->s,&(pIoData->Buffer),1,&RecvBytes,&Flags,&(pIoData->o),NULL);
		if(bRet==SOCKET_ERROR)
		{
			if(WSAGetLastError()!=WSA_IO_PENDING)
			{
				cout<<"WSARecv在工作线程中第种情况的返回值:"<<bRet<<"此时发生中断现象"<<endl;

			}

		}


二、单句柄数据和单IO数据

       介绍单句柄数据和单IO数据的博文很多,此处列举了几个比较通俗易懂的[2][3]

       对两个结构的差异本文就不详细展开了。简而言之一句话“两者都是起到参数传递作用,无非是传递的两端有所差异而已。单句柄数据是不同的设备(此处为套接字)与操作系统(完成端口)之间进行参数传递,因而与设备相关的参数放到单句柄结构中。而单IO数据是同一设备的多次IO操作情况与操作系统的完成端口之间,所以与IO操作相关的信息放到单IO数据结构中”。网络传输中,单IO数据是用来在套接字上进行数据接收的,需要注意的是“要确保整个接收过程中,该数据结构中的缓冲区有效且可访问”。能够由程序员自由控制生命周期的自然会想到堆(Heap),因此上述程序中直接利用new在应用程序的堆上手动开辟了一块缓存空间。堆隶属于进程空间,进程的各个线程中都可以访问到,因此与完成端口绑定的工作线程中同样可以访问到。然而利用Intel InspectorProcess Explorer检测发现正是这个部分导致了服务端的大量内存的泄露。下面我们来详细分析一下导致内存泄露的原因。

2.1 服务端在每一个WSARecv投递前都开辟了一块缓冲区间

       插入断点调试,发现每次WSARecv投递前开辟的缓冲区间的地址“工作线程”中GetQueuedCompletionStatus获取的缓冲区间地址是相同的。由于(一)中的程序在利用GetQueuedCompletionStatus获取了缓冲区数据及处理后,并未对该缓冲区进行释放,而是直接另外new了一块内存空间,因此随着客户端发送数据次数的增加,内存泄露递增,最终导致服务端宕机。添加对上一次new空间的释放代码,即delete PerIoData,(具体代码见IOCP-Server.cpp中的188行)后自然可以解决内存泄露问题。

2.2 重复利用IO数据结构中的缓冲区

       单IO数据结构中的缓冲区用来提取IO操作完成后的数据,且通过2.1的测试知道GetQueuedCompletionStatus函数可以获得发起IO操作时绑定的缓冲区指针,既然该缓冲区是在堆上分配的,其生命周期与主程序相同,我们可以等待数据处理完成后将该缓冲区清空(具体代码参见IOCP-Server.cpp的191行),继续进行下一次WSARecv的IO请求投递直到IO操作完全结束再释放该缓冲区。调试可以看出,内存泄露问题消失。这样服务端针对于绑定套接字的每次IO读取操作都是使用的同一缓冲区间,解决了“内存泄露”问题的同时,降低了内存申请和释放的次数,提高了内存利用效率。从Process Explorer的结果图中也可以看出CPU使用率有所降低。


三、完成端口实现网络传输的整体流程

       从(二)种的尝试中发现,完成端口中用到的单句柄数据和单IO数据结构并不神秘,都是为了实现参数传递的目的,与我们手动创建线程CreateThread时的参数传递类同。稍显复杂的是在完成端口内部操作系统需要合理的识别、分配和组织并行IO操作的结果,很遗憾该过程是一个黑箱,我们看不到具体的实现过程。只能通过使用完成端口推测出其内部的实现机制和流程。将(二)中的程序流总结绘制出来,如下所示:


        网络传输中TCP/IP层会将客户端发送过来的数据存储在缓冲区(确切的说是接收缓冲区)中,缓冲区可以看做是一个FIFO的队列,每一个项目存储的是对应客户端发送过来的数据,另外也会存储部分其他信息,例如端口号、客户端IP地址、数据长度等等。

1)当应用层用户通过WSARecv(WSASend)发出接收(发送)请求时,操作系统会负责数据从TCP/IP协议栈的接收缓冲区到WSARecv参数中指定的堆空间中的拷贝,即将数据由远端转移到了应用程序自身存储空间中;

2)并且在拷贝完成后会发出通知;

3)随后工作线程中的GetQueuedCompletionStatus函数检测到该通知,顺利返回对应的堆空间地址和端口信息等等(此处的堆空间地址就是单IO数据结构中的一部分,而端口信息往往存放在单句柄数据结构中,本质上都是放在应用程序的堆中);

4)接下来工作线程就可以自由处理堆空间中的数据,例如利用WriteFile写入到指定文件的指定位置,或者根据数据内容判定网络对端的目前状态(登录、在线、离线)等等。

       综上所述,可以看出Windows操作系统的完成端口完成的任务包含了两大部分:第一,将数据从TCP/IP网络协议栈空间转移到应用程序空间(堆);第二,工作线程处理应用程序堆空间的数据。

四、仿真完成端口机制

       了解了完成端口主要工作——完成两次数据转移工作(确切的说是一次拷贝、一次传参)——后,下面我们就以简单的套接字网络编程为例,尝试模拟完成端口的工作流程。

4.1 建立控制台程序分别实现网络传输的服务端和客户端,在服务端对连入的套接字添加WSARecv请求操作,异步的WSARecv会立刻返回,然后再在主程序中手动利用while(1)循环来轮询WSARecv参数中指定的程序堆缓冲区中是否有数据到来。
int main()
{
	hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
	WSAData wasData;
	if(WSAStartup(MAKEWORD(2,2),&wasData)==WSANOTINITIALISED)
	{
		//printf("套接字动态库初始化失败,错误代码是%d\n",WSAGetLastError());
		ZS_OUTPUT_ERROR;
		return 0;
	}
	/*----------------------------------------------------------
	 *cout输出重定向,方便查看调试信息
	 *----------------------------------------------------------
	*/
	streambuf* coutbuf=cout.rdbuf();
	ofstream of("c:\\server.txt");
	streambuf* filebuf=of.rdbuf();
	cout.rdbuf(filebuf);
	/*----------------------------------------------------------
	 *创建、绑定、监听套接字
	 *----------------------------------------------------------
	*/
	SOCKET sListen;
	sListen=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
	SOCKADDR_IN local;
	local.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
	local.sin_family=AF_INET;
	local.sin_port=htons(PORT);
	int err=bind(sListen,(sockaddr*)&local,sizeof(SOCKADDR_IN));
	listen(sListen,200);//listen最大的监听数目在winsock2.h中有定义,最大值为SOMAXCONN
	DWORD RecvBytes=0;
	DWORD Flags=0;
	while(true)
	{
		SOCKADDR_IN saRemote;
		SOCKET sClient;
		int nLen=sizeof(saRemote);
		sClient=accept(sListen,(sockaddr*)&saRemote,&nLen);
		PPERIOPARAM lIoData=NULL;
		lIoData=new PERIOPARAM;
		memset(lIoData,0,sizeof(PERIOPARAM));/*********关键步骤***************/
		lIoData->Buffer.len=DATA_BUFSIZE;
		lIoData->Buffer.buf=lIoData->szMessage;
		lIoData->OperateType=START;

		int bRet=WSARecv(sClient,&(lIoData->Buffer),1,&RecvBytes,&Flags,&(lIoData->o),NULL);
		if(bRet==SOCKET_ERROR)
		{
			if(WSAGetLastError()!=WSA_IO_PENDING)
			{
				cout<<"WSARecv在工作线程中第种情况的返回值:"<<bRet<<"此时发生中断现象"<<endl;
				cout<<"GetLastError的结果"<<GetLastError()<<endl;

			}

		}
		//手动提取内存中的数据
		while(1)
		{
			if(strlen(lIoData->Buffer.buf)!=0)
			{
				cout<<(unsigned int*)lIoData->szMessage<<endl;
				break;
			}
		}

		int bRet2=WSARecv(sClient,&(lIoData->Buffer),1,&RecvBytes,&Flags,&(lIoData->o),NULL);
		if(bRet2==SOCKET_ERROR)
		{
			if(WSAGetLastError()!=WSA_IO_PENDING)
			{
				cout<<"WSARecv在工作线程中第种情况的返回值:"<<bRet2<<"此时发生中断现象"<<endl;
				cout<<"GetLastError的结果"<<GetLastError()<<endl;

			}

		}
return 0;
}

       该段代码,能够在客户端发送完成数据后,顺利检测到数据的到来并完成输出和返回动作。

4.2 上述代码中由于while(1)的存在,导致主程序不能处理其他事情。为了模仿完成端口,此处创建一个“轮询线程”来负责监测堆空间数据是否到来,同时创建一个事件,用来向其他线程发送数据到来的通知。并将堆空间的指针和事件句柄以“线程参数”的形式传递到“轮询线程”内部,此“线程参数”类似于完成端口的“单句柄数据”部分
4.3 “轮询线程”监测到数据到来后,应该通知“工作线程”或者“主线程”来对数据进行处理,所以另外创建一个“工作线程”,将传入堆空间指针和事件句柄,(该部分等同于完成端口的“单IO数据”)在“工作线程”内部,利用WaitForSingleObject(XXXEvent,INFINITE)一直等待“轮询线程中的通知事件”,待事件触发后,对堆空间中的数据进行处理。

4.2和4.3的代码如下:

/*----IOCP-Simul.CPP---------------*/

#include <iostream>
#include <fstream>
#include <stdio.h>
#include <WinSock2.h>
#include <vector>
#include <tchar.h>
#include <winerror.h>

#include "IOCP.h"


#pragma comment(lib,"ws2_32.lib")
using namespace std;



#define ZS_OUTPUT_ERROR (printf("套接字动态库初始化失败,错误代码是%d\n",WSAGetLastError()))


/*
 *创建全局事件,用来通知工作线程提取缓冲区数据
 *
*/
HANDLE hEvent;

/*
 *PollingWorkFunction
 *
 *轮询是否有数据到来的工作线程函数
 *
*/
BOOL WINAPI PollingWorkFunction(LPVOID p)
{
	char* pBuffer=(char*)p;
	while(TRUE)
	{
		if(strcmp(pBuffer,"")!=0)
		{
			SetEvent(hEvent);//设置“缓冲区有数据”事件,通知其他线程提取数据
		}

	}
	return 0;
}
BOOL WINAPI WorkThreadFunction(LPVOID p)
{
	char* pBuffer=(char*)p;
	while(1)
	{
		WaitForSingleObject(hEvent,INFINITE);
		//读取缓冲区数据
		cout<<pBuffer<<endl;
		//将缓冲区清空
		memset(pBuffer,0,(strlen(pBuffer)*sizeof(char)));
		//进行处理
		/*
		*...................
		*
		*/

		//复位事件
		ResetEvent(hEvent);

	}
	return 0;
}
int main()
{
	hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
	WSAData wasData;
	if(WSAStartup(MAKEWORD(2,2),&wasData)==WSANOTINITIALISED)
	{
		//printf("套接字动态库初始化失败,错误代码是%d\n",WSAGetLastError());
		ZS_OUTPUT_ERROR;
		return 0;
	}

	/*----------------------------------------------------------
	 *cout输出重定向,方便查看调试信息
	 *----------------------------------------------------------
	*/
	streambuf* coutbuf=cout.rdbuf();
	ofstream of("c:\\server.txt");
	streambuf* filebuf=of.rdbuf();
	cout.rdbuf(filebuf);

	
	/*----------------------------------------------------------
	 *创建、绑定、监听套接字
	 *----------------------------------------------------------
	*/
	
	SOCKET sListen;
	sListen=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);

	SOCKADDR_IN local;
	local.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
	local.sin_family=AF_INET;
	local.sin_port=htons(PORT);
	int err=bind(sListen,(sockaddr*)&local,sizeof(SOCKADDR_IN));
	
	listen(sListen,200);//listen最大的监听数目在winsock2.h中有定义,最大值为SOMAXCONN
	

	DWORD RecvBytes=0;
	DWORD Flags=0;
	while(true)
	{


		SOCKADDR_IN saRemote;
		SOCKET sClient;
		int nLen=sizeof(saRemote);

		sClient=accept(sListen,(sockaddr*)&saRemote,&nLen);

		cout<<"Client "<<sClient<<" is connecting:"<<endl;

		
		PPERIOPARAM lIoData=NULL;
		lIoData=new PERIOPARAM;
	
		cout<<"首次开辟IO句柄参数空间地址"<<(int*)lIoData<<endl;

		memset(lIoData,0,sizeof(PERIOPARAM));/*********关键步骤***************/
		lIoData->Buffer.len=DATA_BUFSIZE;
		lIoData->Buffer.buf=lIoData->szMessage;
		lIoData->OperateType=START;

		int bRet=WSARecv(sClient,&(lIoData->Buffer),1,&RecvBytes,&Flags,&(lIoData->o),NULL);
		if(bRet==SOCKET_ERROR)
		{
			if(WSAGetLastError()!=WSA_IO_PENDING)
			{
				cout<<"WSARecv在工作线程中第1种情况的返回值:"<<bRet<<"此时发生中断现象"<<endl;
				cout<<"GetLastError的结果"<<GetLastError()<<endl;

			}

		}
		
		//创建工作线程,等待缓冲区数据的到来
		HANDLE hWorkFunction=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)WorkThreadFunction,lIoData->Buffer.buf,0,NULL);


		//创建轮询线程,使得主线程可以继续其他工作
		HANDLE hPollingThread=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)PollingWorkFunction,lIoData->Buffer.buf,0,NULL);
		
		//主线程继续其他工作
		while(getchar()!='q');

	}


	return 0;
}
/*------------IOCP.h--------------*/

#define PORT 2234
#define DATA_BUFSIZE 1024

/*
*-----IO操作类型
*/

enum OPERATE_TYPE{START,TRANSFER,DROP};
/*
*-----单句柄数据
*/
typedef struct _THREAD_PARAM
{

	HANDLE hCompletePort;
	int ThreadNo;


}PERTHREADPARAM,*PPERTHREADPARAM;
/*
*-----单套接字句柄数据
*/
typedef struct _SOCKET_PARAM
{
	SOCKET s;
	sockaddr_in addr;
}PERSOCKETPARAM,*PPERSOCKETPARAM;
/*
*-----单IO数据
*/
typedef struct _IO_PARAM
{
	OVERLAPPED o;
	WSABUF Buffer;
	char szMessage[DATA_BUFSIZE];
	DWORD NumberofBytesRecvd;
	DWORD Flag;
	OPERATE_TYPE OperateType;
	char FileName[DATA_BUFSIZE];//2013-12-21 异步文件传输时文件名
	DWORD ReceivedLength;//2013-12-21 文件已经接收的长度
	DWORD FileLength;//2013-12-21 文件总长度
}PERIOPARAM,*PPERIOPARAM;

DWORD WINAPI WorkFunction(LPVOID p);


五、缓冲区

       缓冲的字面意思是“缓解冲击力”,通常用来描述某个变化过程的减缓。计算机中用到缓冲的地方很多,硬件方面往往用来解决由于不同的读写速度而产生的冲突问题,例如CPU与内存之间的缓存,设备(如显卡)与CPU之间的缓存,甚至内存也可以看做是CPU与硬盘之间的缓存等等;操作系统软件方面,往往用来解决消息触发与消息处理函数之间的速度协调问题,确保每个消息都能得到妥善处理。例如中断机制中的中断信号缓冲区,内存管理中的虚拟内存区域,TCP/IP传输协议中的缓冲栈等等,软件的“缓冲”大多利用FIFO的队列来实现,这也符合真实世界中对事件的先后处理顺序。(关于操作系统中缓冲区的内容,可以推荐大家一本书[4],个人认为书中讲解的很透彻

       完成端口就是将“TCP/IP协议栈缓冲区”“消息触发与消息处理函数”两类缓冲区进行统一管理。在第四部分中虽然我们通过手动创建事件、轮询线程、工作线程来简单的仿真了一下完成端口的工作机制,但是由于程序内部无法直接获取“TCP/IP协议栈缓冲区”的操作权限,所以只能通过检测主程序的堆缓冲区来模拟一下,并未真正实现完成端口的整个流程,况且完成端口内部对于工作线程队列、IO操作完成队列、等待线程队列等都采用了不同的控制方法,实际过程还是很复杂的,由于作者能力有限,还未真正手动仿真出完成端口的整体流程。有待进一步研究(未完待续)



参考资料:

[1]http://blog.csdn.net/piggyxp/article/details/6922277

[2]http://blog.csdn.net/sodme/article/details/427405

[3]http://blog.csdn.net/yu52000guang/article/details/7061382

[4]《30天自制操作系统》



Author:zssure
E-mail: zssure@163.com
Date:    2013-01-04



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zssure

己欲立而立人,己欲达而达人

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值