现在,快递业务是越来越多,越来越常见了。发快递,有发送端和接收端,
接收快递这端大概是这么一个流程:有一个仓库,接收来自各个公司邮寄的快递,
然后这个快递的派送员,一直等着仓库里有快递,只有有一件快递,则就会有一个
仓库快递员去把这件快递送到客户手里。
上面的流程,大概就是完成端口模型的流程了。让我细细道来。
有一个老板,相当于程序里的主线程;
老板盖了一间厂房,相当于程序里创建完成端口CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,4),
最后一个参数是4,为什么是4呢,因为仓库只有4辆送货电动车,所以同时只有四个人派送快递到客户手里。
现在,得有快递公司把快递寄到我这里,才有活干啊。就得与快递公司签约,快递公司就相当于程序里的文件句柄(
也可以是套接字,油槽之类的东东),只要和这些快递公司相关的快递都会送到我这个仓库里
(只要和绑定的文件相关的操作完成情况都会送到完成端口),签约的过程就相当于,把文件句柄和完成端口绑定过程,CreateIoCompletionPort(hFile,hComPort,0,0))(第二次调用)第三个参数相对于快递公司的信息,
你如 0 代表顺丰,1代表圆通,当你收到快递后,你看到0,你就知道,顺丰送过来的。
好了,现在有快递过来了,你得派人去派发这些快递啊。老板需要招几个员工,那招几个呢?好吧,公司只有
4辆送货电动车,招一个人,三辆车闲着,招十个人,六个人闲着,还得发工资。那就四个吧,一人一辆车。
算了,还是招五个人吧,万一有一个人生病了呢。招五个人就相当于我们创建五个工作线程,
4辆车,就相当于我们电脑有四个CPU。
好了,现在要做的就是,让这五个人当中四个有电动车的人,去仓库等着,有一个快递到仓库,就去派发这件快递,
就相当于,程序里面线程里的的GetQueuedCompletionStatus(pServerInfo->hCompletionPort,
&dwNumberOfBytesTransferred, (PULONG_PTR)&ulCompletionKey,(LPOVERLAPPED*)&lpOverlapped,INFINITE));
第一参数,完成端口,相对于我们的仓库,第三个参数,相对于,快递公司的信息,0,是顺丰,第四个参数是
这个快递的信息,对应于程序里,就是,OverLapped结构体,里面保存着,传输多少字节,错误码之类的信息。
这个结构体,是在ReadFile,WriteFile函数最后一个参数传递的。
再说说,异步这个事。程序里就是,ReadFile,WriteFIle函数调用后,不用等待完成了,再往下执行,而是,这些读写
的任务交给了操作系统,你的线程该干嘛就干嘛去;这就好比,仓库老板和快递公司签了一件快递的合同,他不用等
快递到了,再去忙其他的事情,而是让其他员工去仓库等着这些快递的到来就好了。程序里就是让工作线程等着ReadFile,
WriteFile完成的通知,然后去处理就好了。
好了,大概要讲的就这些,下面附VS2010下面能跑的代码。只有一百行左右,非常简单。
#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
struct OverlappedInfo:public OVERLAPPED
{
OverlappedInfo()
{
memset(this,0,sizeof(OVERLAPPED));
memset(szMsg,0,64);
memset(szBuffer,0,64);
}
char szMsg[64];
char szBuffer[64];
};
struct ServerInfo
{
int iThreadIndex;
HANDLE hCompletionPort;
};
//IO请求完成后,工作线程用来处理数据
DWORD CALLBACK pfnThreadFun(LPVOID param)
{
DWORD dwNumberOfBytesTransferred=0;
ULONG ulCompletionKey=0;
LPOVERLAPPED *lpOverlapped=NULL;
ServerInfo* pServerInfo = (ServerInfo*)param;
while(TRUE)
{
//若没有IO请求完成,会卡死在这里,类似WaitForOjbect系列函数等待内核对象一样
if(!GetQueuedCompletionStatus(pServerInfo->hCompletionPort,&dwNumberOfBytesTransferred,
(PULONG_PTR)&ulCompletionKey,(LPOVERLAPPED*)&lpOverlapped,INFINITE))
{
printf("error...\n");
}
else
{
//有数据来了,处理这些数据;其实这里的OverLapped结构地址,就是主线程那边创建的OverLappe结构的地址
printf("Thread:%d; ulCompletionKey:%ld; ServerMsg:%s ReadData:%s\n",
pServerInfo->iThreadIndex,ulCompletionKey,((OverlappedInfo*)lpOverlapped)->szMsg,
((OverlappedInfo*)lpOverlapped)->szBuffer);
}
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hFile[10];
HANDLE hComPort;
HANDLE hThread[5];
for (int i = 0;i<10;i++)
{
TCHAR szFile[MAX_PATH] = {0};
wsprintf(szFile,_T("./test_%d.txt"),i);
//以CREATE_ALWAYS方式,创建十个文本文件,倒数第二个参数为FILE_FLAG_OVERLAPPED
hFile[i] = ::CreateFile(szFile,GENERIC_WRITE|GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,CREATE_ALWAYS,FILE_FLAG_OVERLAPPED,NULL);
if(hFile[i]==INVALID_HANDLE_VALUE)
{
printf("文件打开失败..\n");
}
}
//创建唯一一个完成端口
hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,4);
for (int i = 0;i<10;i++)
{
//将十个文件句柄与唯一一个完成端口绑定
if(hComPort!=CreateIoCompletionPort(hFile[i],hComPort,i,0))
{
printf("CreateIoCompletionPort(hFile,hComPort,0,0) failed\n");
}
}
ServerInfo serverInfo[5];
for (int i = 0;i<5;i++)
{
serverInfo[i].hCompletionPort = hComPort;
serverInfo[i].iThreadIndex = i;
//创建工作线程
hThread[i] = CreateThread(NULL,0,pfnThreadFun,&serverInfo[i],0,NULL);
}
DWORD dwWritten = 0;
for (int i = 0;i<10;i++)
{
OverlappedInfo oWrite ;
sprintf(oWrite.szMsg,"write to test_%d.txt",i);
char szData[64]={0};
sprintf(szData,"file data from text_%d.txt",i);
//IO异步请求
WriteFile(hFile[i],szData,strlen(szData)+1,&dwWritten,&oWrite);
}
while(TRUE)
{
for (int i = 0;i<10;i++)
{
OverlappedInfo oRead;
sprintf(oRead.szMsg,"Read from file test_%d.txt:",i);
//IO异步请求,第二参数为从文件读取到的数据,因为oRead这个OverLapped 结构体
//会传到GetQueuedCompletionStatus的倒数第二个参数,所以那里可以获取读到的数据
ReadFile(hFile[i],oRead.szBuffer,64,NULL,&oRead);
Sleep(1000);
}
}
return 0;
}