转自:http://www.cmnsoft.com/wordpress/?p=248 感谢原作者。我在此整理一下:

完成端口(IOCP)是WINDOWS平台上特有的一种技术。要使用IOCP技术,就要用到微软的WSA(windows socket api)。

进行网络编程的套接口(socket)有UNIX套接口、伯克利套接口、WSA。其中使用最多的是伯克利套接口,因为他可在UNIX、WINDOWS、OS/2等计算机上使用。WSA套接口比伯克利套接口多了WSA三个字母。

伯克利套接口:socket()、recv()、send()等。

WSA套接口:WSASocket()、WSARecv()、WSASend()等。

WSA2.0还增加了很多新的功能函数。IOCP要在WSA2.0以上才能实现,所以我们要检查我们的系统是否支持WSA2.0(现在大部份都支持)。

IOCP的过程是,建立一个线程池(通常是有几个处理核心就创建几个线程。然后等待I/O操作提出,每一个链接进来的SOCKET分配一个线程去处理,处理完释放线程)。因为他是等待系统I/O操作的提出,而不是主动去轮询端口,所以他比传统方法更有效率。另外I/O的速度比CPU慢,而在IOCP里,数据是否传送到了由I/O提出,CPU只管处理数据问题,而不用轮询I/O端口,所以CPU的使用更高效。

还有一点,IOCP一般用TCP链接服务器,当然他也可以做UDP。为什么用IOCP做UDP少呢。我觉得理由很简单,因为UDP比较适合局域网内使用,IOCP是处理大规模链接用的,比如上千人的连网游戏,而且需要同时管理几百、上千个套接字时才使用。而在局域网里很少出现这种情况。所以,我觉得ICOP还是适合TCP链接。

IOCP经常用到的函数:

//创建一个完成端口

HANDLE CreateIoCompletionProt(
HANDLE FileHandle, //socket接口
HANDLE ExistingCompletionPort, //指定一个现有的IOCP
DWORD CompletionKey, //指定要与某个特定套接字关联在一起的数据。
DWORD NumberOfConcurrentThreads //一个IOCP上能同时执行几个线程,通常是0,
让系统根据核心数情况来定。
) //获取完成端口队列的状态。
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort, //一个等待的完成端口。
LPDWORD lpNumberOfBytesTransferred, // I/O操作后实际接收或发送的字节数。
LPDWORD lpCompletionkey, //CreateioCompletionProt中的completionkey。
LPOVERLAPPED* lpOVerlapped, //接收完成的I/o操作的重叠结果。
DWORD dwMilliseconds //调用者喜欢等待一个数据包在IOCP上出现的时间
//设为INFINTE,则无休止等待。
) //向完成端口线程发出指示“立即结束并退出”
BOOL PostQueuedCompletionStatus(
HANDLE CompletionPort, //想退出的完成端口
DWORD dwNumbetOfBytesTransferred,
DWORD dwCompletionKey,
LPOVERLAPPED lpOverlapped
)
后面三个参数的解释跟GetQueuedCompletionStatus是一样的。 //接收活动SOCKET
SOCKET WSAAPI WSAAccept (
SOCKET s, //监听一个套接口。
struct sockaddr FAR * addr, //通讯地址族
int FAR * addrlen, //通讯地址族长度
LPCONDITIONPROC lpfnCondition, //用户提供的条件函数的进程实例地址。
//该函数根据参数传入的调用者信息作出接受或拒绝的决定
DWORD dwCallbackData //作为条件函数参数返回给应用程序的回调数据。
) //向套接字发送数据
int WSAAPI WSASend (
SOCKET s, //发送目的套接字
LPWSABUF lpBuffers, //发送数据缓冲区,这是个指针数组。存多组缓冲区
DWORD dwBufferCount, // lpBuffers包含缓冲区数
LPDWORD lpNumberOfBytesSent, //已发送字节数
int iFlags, //标志位
LPWSAOVERLAPPED lpOverlapped, //WSAOVERLAPPED结构的指针
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
//发送操作完成后调用的完成例程的指针
)
//返回已发送字节数 //向套接字接送数据
int WSARecv(
SOCKET s, //接收数据的套接字
LPWSABUF lpBuffers, // 接收缓冲区
DWORD dwBufferCount, // lpBuffers中WSABUF结构的数量
LPDWORD lpNumberOfBytesRecvd, // 如果接收操作立即完成,
//这里会返回函数调用所接收到的字节数
LPDWORD lpFlags, // 通常设置为0
LPWSAOVERLAPPED lpOverlapped, // “绑定”的重叠结构
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
// 完成例程中将会用到的参数,我们这里设置为 NULL
)
//返回WSA_IO_PENDING表示成功

  

IOCP服务端的设计大概按下面几个步骤来完成。

第一步:初始化WSA2.0并创建一个套接字。

第二步:创建一个完成端口。

第三步:根据系统CPU个数创建完成处理线程。

第四步:监听套接字申请接入,并将申请进来的套接字加入完成端口队列。(由那个线程负责执行是完成端口自已完成的,不用管。

而处理线程要做的事只有两件:

第1件:端口是否有数据传输产生。
第2件:如果有检查是发送还是接收,并做相应处理。

为了方便主进程跟线程间传递数据,我们通常会建立一个数据结构来传递数据。
比如我们建立一个IOCP_STR的结构体。

struct IOCP_STR

{
OVERLAPPED overlapped; //这个是必须的。把结构构造为OVERLAPPED指针。
WSABUF DataBuf; //数据指针,指向下面的buf
char buf[BUF_SIZE]; //数据区大小
DWORD sendbytes; //要发送多少个字节
DWORD recvbytes; //要接收多少个字节
}

  我们写一个IOCP服务器,他的作用是把客户端的发送过来的信息回送过去。这个程序分为四个部份

第一部分:定义传输数据的结构体。

#define BUF_SIZE 2048
//传输数据信息
struct IOCP_DATA
{
OVERLAPPED overlapped;
WSABUF dataBuf;
char buf[BUF_SIZE];
DWORD recvbytes;
DWORD sendbytes;
};
//socket信息
struct IOCP_SOCKET
{
SOCKET m_socket;
};

  第二部分:服务线程处理

//服务器处理线程
DWORD WINAPI ServerThread(LPVOID CompletionPort)
{
IOCP_SOCKET* m_Socket = NULL;
IOCP_DATA* m_IocpData = NULL;
DWORD m_BytesTransferred=0;
DWORD Flags = 0;
DWORD m_TransBytes = 0;
HANDLE m_CompletionPort = CompletionPort;
while(1)
{
if(GetQueuedCompletionStatus(m_CompletionPort,&m_BytesTransferred,
(LPDWORD)&m_Socket,(LPOVERLAPPED*)&m_IocpData,INFINITE)==0)
{
printf("getQueued error!\n");
} if(m_BytesTransferred == 0)
{
continue;
} if(m_IocpData->recvbytes == 0)
{
//准备接收数据
m_IocpData->recvbytes = m_BytesTransferred;
m_IocpData->sendbytes = 0;
}
else
{
//准备发送数据
m_IocpData->sendbytes += m_BytesTransferred;
} //如果有数据没有发送,则发送出去
if(m_IocpData->recvbytes > m_IocpData->sendbytes)
{
printf("send bytes...\n");
ZeroMemory(&(m_IocpData->overlapped),sizeof(OVERLAPPED));
//计算要发送字节数和起始地址
m_IocpData->dataBuf.buf = m_IocpData->buf + m_IocpData->sendbytes;
m_IocpData->dataBuf.len = m_IocpData->recvbytes - m_IocpData->sendbytes;
if(WSASend(m_Socket->m_socket,&(m_IocpData->dataBuf),1,&m_TransBytes,0,
&(m_IocpData->overlapped),NULL)==SOCKET_ERROR)
{
printf("send bytes error!\n");
return 0;
}
}
else
{
//如果数据已经发送完,则等待接收新据
printf("recv bytes...\n");
m_IocpData->recvbytes = 0;
ZeroMemory(&(m_IocpData->overlapped),sizeof(OVERLAPPED));
m_IocpData->dataBuf.len = BUF_SIZE;
m_IocpData->dataBuf.buf = m_IocpData->buf;
if(WSARecv(m_Socket->m_socket,&(m_IocpData->dataBuf),1,&m_TransBytes,
&Flags,&(m_IocpData->overlapped),NULL)==SOCKET_ERROR)
{
if(WSAGetLastError()!= ERROR_IO_PENDING)
printf("thread recv error\n");
}
}
}
return 0;
}

  第三部分:IOCP类

class NIOCP
{
public:
SOCKET m_ServerSocket;
HANDLE m_CompletionPort;
int num_Cores;
private:
// 检查是否支持2.0
bool TestVersion()
{
WSADATA wsaData;
WORD version = MAKEWORD(2,0);
//初始化WinSock DLL库
int ret = WSAStartup(version,&wsaData);
if(ret!=0)
return false;
return true;
} public:
void CleanClient()
{
closesocket(m_ServerSocket);
WSACleanup();
} int GetNumCore()
{
SYSTEM_INFO SystemInfo;
GetSystemInfo(&SystemInfo);
num_Cores = SystemInfo.dwNumberOfProcessors;
return num_Cores;
} bool InitIOCP()
{
IOCP_SOCKET* m_HandleSocket;
IOCP_DATA* m_IocpData;
if(TestVersion()==false)
{
printf("require WSA 2.0\n");
} //创建完成端口
m_CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
if(m_CompletionPort==NULL)
{
printf("create completionport error\n");
}
DWORD threadID=0;
//创建服务端处理线程
int n=0;
for(n=0;n<num_Cores;n++)
{
HANDLE threadHandle;
threadHandle = CreateThread(NULL,0,ServerThread,
m_CompletionPort,0,&threadID); if(threadHandle==NULL)
{
printf("create thread error!\n");
} CloseHandle(threadHandle);
}
//创建服务端socket
m_ServerSocket = WSASocket(AF_INET,SOCK_STREAM,0,
NULL,0,WSA_FLAG_OVERLAPPED); if(m_ServerSocket == INVALID_SOCKET)
{
printf("Init Socket error!\n");
} //初始化socket的网络地址
sockaddr_in m_addr;
m_addr.sin_family = AF_INET;
m_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
m_addr.sin_port = htons(10001);
bind(m_ServerSocket,(PSOCKADDR)&m_addr,sizeof(m_addr));
listen(m_ServerSocket,10);
SOCKET m_TempSocket; while(true)
{
struct sockaddr_in client_address;
int address_len = sizeof(client_address);
//捕获申请链接的socket
m_TempSocket = WSAAccept(m_ServerSocket,
(struct sockaddr*)(&client_address),&address_len,NULL,0); printf("client IP:%s connected!\n",inet_ntoa(client_address.sin_addr));
//给数据分配空间,并加入完成端口队列
m_HandleSocket = (IOCP_SOCKET*)GlobalAlloc(GPTR,sizeof(IOCP_SOCKET));
m_HandleSocket->m_socket = m_TempSocket;
if(CreateIoCompletionPort((HANDLE)m_TempSocket,m_CompletionPort,
(DWORD)m_HandleSocket,0)==NULL)
{
printf("create iocp error!\n");
return false;
} m_IocpData = (IOCP_DATA*)GlobalAlloc(GPTR,sizeof(IOCP_DATA));
ZeroMemory(&(m_IocpData->overlapped),sizeof(OVERLAPPED));
m_IocpData->sendbytes = 0;
m_IocpData->recvbytes = 0;
m_IocpData->dataBuf.len = BUF_SIZE;
m_IocpData->dataBuf.buf = m_IocpData->buf;
DWORD Flag = 0;
DWORD TransBytes = 0; if(WSARecv(m_TempSocket,&(m_IocpData->dataBuf),1,&TransBytes,
&Flag,&(m_IocpData->overlapped),NULL)==SOCKET_ERROR)
{
if(WSAGetLastError()!= ERROR_IO_PENDING)
printf("recv error\n");
}
}
return true;
}
};

  第四部分:主程序调用

int main()
{
NIOCP* S_IOCP=new NIOCP;
printf("CPU CORES:%d\n",S_IOCP->GetNumCore()); if(false==S_IOCP->InitIOCP())
{
printf("Init IOCP error!\n");
} getch();
return 0;
}

  

以上四部份代码都写到一个CPP里面,当然你也可以分开几个CPP来写。运行结果是一样的。

最后我们启动第一节中TCP的客户端程序来检验一下服务器程序。

IOCP服务程序

客户端我们用之前TCP的客户程序

可以看到,当客户发送链接申请给服务器,并传送数据给服务器后,服务线程将信息回发给客户,然后就继续等待接收新的数据传入。

【转】IOCP创建的更多相关文章

  1. Server Develop (八) IOCP模型

    IOCP模型 IOCP全称I/O Completion Port,中文译为I/O完成端口.IOCP是一个异步I/O的Windows API,它可以高效地将I/O事件通知给应用程序,类似于Linux中的 ...

  2. 真实的C++单例模式举例

    把构造函数声明为protected的理由很简单,但把构造函数声明为private的原因却很少知道.   从语法上讲,任何函数如果被声明为private,这个函数就不能从外部调用,构造函数也是函数,相反 ...

  3. 局域网聊天软件(winsocket)

    LANChat工作整理 2013/8/22 程序实现功能: 局域网聊天软件,启动即可找到在线设备,并能够进行简单的文字聊天. 其实下面这个框图已经说明了程序的绝大部分功能原理. 核心类的程序框图 我觉 ...

  4. 深入浅出NodeJS——异步I/O

    底层操作系统,异步通过信号量.消息等方式有着广泛的应用. PHP语言从头到尾都是以同步堵塞方式执行,利于程序猿顺序编写业务逻辑. 异步I/O.事件驱动.单线程构成Node的基调. why异步I/O ( ...

  5. JavaScript:event loop详解

    之前已经有两篇随笔提到了event loop,一篇是事件机制,一篇是tasks和microtasks,但是里面的event loop都是文字描述,很难说细,逻辑也只是简单的提了一遍.其实之前也是通过阮 ...

  6. Node.js异步IO原理剖析

    为什么要异步I/O? 从用户体验角度讲,异步IO可以消除UI阻塞,快速响应资源 JavaScript是单线程的,它与UI渲染共用一个线程.所以在JavaScript执行的时候,UI渲染将处于停顿的状态 ...

  7. c++ 网络编程(八) LINUX-epoll/windows-IOCP下 socket opoll函数用法 优于select方法的epoll 以及windows下IOCP 解决多进程服务端创建进程资源浪费问题

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/9622548.html 锲子:关于并发服务器中的I/O复用实现方式,前面在网络编程系列四还是五来 ...

  8. 高性能TcpServer(C#) - 2.创建高性能Socket服务器SocketAsyncEventArgs的实现(IOCP)

    高性能TcpServer(C#) - 1.网络通信协议 高性能TcpServer(C#) - 2.创建高性能Socket服务器SocketAsyncEventArgs的实现(IOCP) 高性能TcpS ...

  9. IOCP Internals

    Buffer Type Buffer I/O 针对Buffer I/O的请求,系统会为其分配一个非换页内存作为缓存区,其大小等同于I/O请求的缓存区大小.对于写操作,I/O管理器在创建IRP时,将请求 ...

随机推荐

  1. WebView&HTML5-----使用WebView播放HTML5视频文件

    一.问题描述 HTML5提供了很多新的特性比如,视频播放.web本地存储.地理定位.应用缓存.Canvas等,但这些特性需要浏览器的支持,在Android应用中我们可以使用WebView构建Web应用 ...

  2. 大数据开发实战:Stream SQL实时开发三

    4.聚合操作 4.1.group by 操作 group by操作是实际业务场景(如实时报表.实时大屏等)中使用最为频繁的操作.通常实时聚合的主要源头数据流不会包含丰富的上下文信息,而是经常需要实时关 ...

  3. 机器学习中的损失函数 (着重比较:hinge loss vs softmax loss)

    https://blog.csdn.net/u010976453/article/details/78488279 1. 损失函数 损失函数(Loss function)是用来估量你模型的预测值 f( ...

  4. C语言变长数组data[0]【总结】

    1.前言 今天在看代码中遇到一个结构中包含char data[0],第一次见到时感觉很奇怪,数组的长度怎么可以为零呢?于是上网搜索一下这样的用法的目的,发现在linux内核中,结构体中经常用到data ...

  5. Kafka:ZK+Kafka+Spark Streaming集群环境搭建(二)安装hadoop2.9.0

    如何搭建配置centos虚拟机请参考<Kafka:ZK+Kafka+Spark Streaming集群环境搭建(一)VMW安装四台CentOS,并实现本机与它们能交互,虚拟机内部实现可以上网.& ...

  6. Spring(十九):Spring AOP(三):切面的优先级、重复使用切入点表达式

    背景: 1)指定切面优先级示例:有的时候需要对一个方法指定多个切面,而这多个切面有时又需要按照不同顺序执行,因此,切面执行优先级别指定功能就变得很实用. 2)重复使用切入点表达式:上一篇文章中,定义前 ...

  7. Android7.0新特性,及Android N适配

    新特性部分 Android 7.0 Nougat 提供新功能以提升性能.生产效率和安全性,主要新增了下面的新特性和优化: 一.新的Notification Android N 添加了很多新的notif ...

  8. IT行业简报 2014-2-8

    1.微信在“我的银行卡”页面接入嘀嘀打车,三天内微信打车突破10万单,日均订单为70万,其中微信支付订单超过48万单2.三大运营商手机支付用户仅366.3万,与腾讯单月发展手机支付用户500万户相比, ...

  9. Mybatis源码分析之Cache一级缓存原理(四)

    之前的文章我已经基本讲解到了SqlSessionFactory.SqlSession.Excutor以及Mpper执行SQL过程,下面我来了解下myabtis的缓存, 它的缓存分为一级缓存和二级缓存, ...

  10. spring boot与spring mvc的区别是什么?

    Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等.但他们的基础都是Spring 的 ioc和 aop ioc 提供了依赖注入的容器 aop ,解决了面向横切面 ...