完成端口cookbook

  在服务器开发上虚度日月久矣,每次新的开发都要重新写一遍netlayer,厌烦了这种事情想做一个类库给自己的时候,一直使用完成端口做网络层没有错,但是不去深刻理解完成端口的本质就不对了,而且在该使用这个强力工具的时候没有想起使用却是大大的错了。

  cookbook只讲step by step创建一个完成端口模块(网络)。

  意识到线程切换的巨大代价,NT小组开发了完成端口这个内核级的东西。我们平时使用比较多的是如下三个API:

WINBASEAPI

__out

HANDLE

WINAPI

CreateIoCompletionPort(

    __in     HANDLE FileHandle,

    __in_opt HANDLE ExistingCompletionPort,

    __in     ULONG_PTR CompletionKey,

    __in     DWORD NumberOfConcurrentThreads

    );



WINBASEAPI

BOOL

WINAPI

GetQueuedCompletionStatus(

    __in  HANDLE CompletionPort,

    __out LPDWORD lpNumberOfBytesTransferred,

    __out PULONG_PTR lpCompletionKey,

    __out LPOVERLAPPED *lpOverlapped,

    __in  DWORD dwMilliseconds

    );



WINBASEAPI

BOOL

WINAPI

PostQueuedCompletionStatus(

    __in     HANDLE CompletionPort,

    __in     DWORD dwNumberOfBytesTransferred,

    __in     ULONG_PTR dwCompletionKey,

    __in_opt LPOVERLAPPED lpOverlapped

    );

  时间顺序上,完成端口可以理解为,首先我们告诉操作系统要做一件事情( CreateIoCompletionPort),为了获取该事情的处理结果或者结果数据,我们使用GetQueuedCompletionStatus阻塞等待操作的完成,操作系统做完指定的任务后,通过PostQueuedCompletionStatus方法返回给我们定制的信息。

  这只是时间顺序上的逻辑模拟解释,操作系统做些事情的时候要做的东西我就不知道了。既然如此,那开发一个基于完成端口的网络模块应该有如下步骤:

  1.初始化winsock2库。

  这个老生常谈了,但是我在开发的时候还是往往忘了这个,调试异常的时候才意识到忘了初始化环境了。在开发网络程序的时候,我们总要使用这样的函数对:

int _tmain(int argc, _TCHAR* argv[])

{

	WSAData	wd;

	WSAStartup(MAKEWORD(2,2),&wd);	//初始化

	//your code

	WSACleanup();					//清理

	return 0;

}

  2.初始化监听socket相关(初始化服务器环境)

  网络编程非常常见的东西:

	m_ssock	= socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

	sockaddr_in	saddr;

	saddr.sin_addr.s_addr	 = INADDR_ANY;

	saddr.sin_family		= AF_INET;

	saddr.sin_port			= htons(2350);

	bind(m_ssock,(sockaddr*)&saddr,sizeof(saddr));

	listen(m_ssock,5);

  3.把服务器socket绑定在完成端口上

  这涉及完成端口句柄的创建以及socket句柄的绑定。当把一个完成句柄绑定在完成端口句柄上的时候,我们可以传递一个CompletionKey参数给CreateIoCompletionPort函数,以后GetQueuedCompletionStatus方法可以原样获取这个参数,也就是说,我们可以给特定句柄绑定一份数据,以后该句柄被GetQueuedCompletionStatus方法查询到的时候都可以获取这份数据,我看过不少代码把这个东西叫做perHandleData。

  基于传递perHandleData的需求,我们可以定义一个结构。

struct perHandleData 

{

	union

	{

		SOCKET	sock;

		HANDLE	handle;

	};

	int	ty;		//sock=1,handle=2

};



	//开辟完成端口和线程

	m_hiocp	= CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,0);



	perHandleData*	svrhandle	= m_handleset.getnew();

	svrhandle->sock	= m_ssock;

	svrhandle->ty	= 1;



	CreateIoCompletionPort(m_ssock,m_hiocp,ULONG_PTR(svrhandle),0);

  3.创建工作者线程

  在异步模型中,总有线程是在等待的,完成端口模型提高效率的方式是操作系统管理等待,对于我们的程序而言只是简单的调用GetQueuedCompletionStatus方法等待,在有消息的时候操作系统会告诉我们。

	for ( int i = 0;i < MAX_WORKER; ++i )

	{

		m_hworkerset[i]	= CreateThread(NULL,0,worker,(LPVOID)this,0,NULL);

	}



DWORD	WINAPI iocp::worker( LPVOID lpthis )

{

	iocp* pthis = static_cast<iocp*>(lpthis);

	BOOL	bgqcp;

	DWORD	transed;

	perIoData*	piodata;

	perHandleData*	phddata;

	while (true)

	{

		transed	= 0;

		piodata	= NULL;

		bgqcp	= GetQueuedCompletionStatus(pthis->m_hiocp,&transed,(PULONG_PTR)&phddata,(LPOVERLAPPED*)&piodata,INFINITE);

		if (!bgqcp)

		{

			continue;

		}

		//your process

	}

	return	0L;

}

  4.GetQueuedCompletionStatus得到信息并处理

  从GetQueuedCompletionStatus中,我们得到了先前交给CreateIoCompletionPort的perHandleData数据,通同时我们还可以通过out指针得到一个指向OVERLAPPED结构的指针,利用struct的内存布局,我们可以使用这个指针传递自定义信息。完成端口每次io操作完毕后我们都可以获得信息,很多代码里把这份信息称为perIoData。

struct perIoData 

{

	WSAOVERLAPPED	ov;

	IOOperation		op;

	LPVOID			data;

	perIoData()	:data(NULL){}

	~perIoData(){if(data)delete data;}

};

  5.如何把perIoData交给操作系统

  perIoData在完成端口模型中是传递数据的核心,mswsock天生和完成端口结合在了一起,所以在完成端口的网络编程中,传递参数的隐晦性是学习的难点。其实WSARecv、WSASend、AcceptEx都可以传递perIoData。也就是说,只要再进行简单的预处理,完成端口的网络模型就可以工作起来拉。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值