简单IOCP例子
使用IOCP模型编程的优点
① 帮助维持重复使用的内存池。(与重叠I/O技术有关)
② 去除删除线程创建/终结负担。
③ 利于管理,分配线程,控制并发,最小化的线程上下文切换。
④ 优化线程调度,提高CPU和内存缓冲的命中率。
服务器:
// IOCP_TCPIP_Socket_Server.cpp
#include <WinSock2.h>
#include <Windows.h>
#include <vector>
#include <iostream>
using namespace std;
#pragma comment(lib, "Ws2_32.lib") // Socket编程需用的动态链接库
#pragma comment(lib, "Kernel32.lib") // IOCP需要用到的动态链接库
/**
* 结构体名称:PER_IO_DATA
* 结构体功能:重叠I/O需要用到的结构体,临时记录IO数据
**/
const int DataBuffSize = 2 * 1024;
typedef struct
{
OVERLAPPED overlapped;
WSABUF databuff;
char buffer[DataBuffSize];
int BufferLen;
int operationType;
}PER_IO_OPERATEION_DATA, *LPPER_IO_OPERATION_DATA, *LPPER_IO_DATA, PER_IO_DATA;
/**
* 结构体名称:PER_HANDLE_DATA
* 结构体存储:记录单个套接字的数据,包括了套接字的变量及套接字的对应的客户端的地址。
* 结构体作用:当服务器连接上客户端时,信息存储到该结构体中,知道客户端的地址以便于回访。
**/
typedef struct
{
SOCKET socket;
SOCKADDR_STORAGE ClientAddr;
}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
// 定义全局变量
const int DefaultPort = 5000;
vector < PER_HANDLE_DATA* > clientGroup; // 记录客户端的向量组
vector<LPPER_IO_OPERATION_DATA> IOOperationDataGroup;
HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);
DWORD WINAPI ServerWorkThread(LPVOID CompletionPortID);
DWORD WINAPI ServerSendThread(LPVOID IpParam);
// 开始主函数
int main()
{
// 加载socket动态链接库
WORD wVersionRequested = MAKEWORD(2, 2); // 请求2.2版本的WinSock库
WSADATA wsaData; // 接收Windows Socket的结构信息
DWORD err = WSAStartup(wVersionRequested, &wsaData);
if (0 != err) { // 检查套接字库是否申请成功
cerr << "Request Windows Socket Library Error!\n";
system("pause");
return -1;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {// 检查是否申请了所需版本的套接字库
WSACleanup();
cerr << "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 (NULL == completionPort) { // 创建IO内核对象失败
cerr << "CreateIoCompletionPort failed. Error:" << GetLastError() << endl;
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);//第一NULL代表默认安全选项,第一个0,代表线程占用资源大小,第二个0,代表线程创建后立即执行
if (NULL == ThreadHandle) {
cerr << "Create Thread Handle failed. Error:" << GetLastError() << endl;
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 (SOCKET_ERROR == bindResult) {
cerr << "Bind failed. Error:" << GetLastError() << endl;
system("pause");
return -1;
}
// 将SOCKET设置为监听模式
int listenResult = listen(srvSocket, 10);
if (SOCKET_ERROR == listenResult) {
cerr << "Listen failed. Error: " << GetLastError() << endl;
system("pause");
return -1;
}
// 开始处理IO数据
cout << "本服务器已准备就绪,正在等待客户端的接入...\n";
//// 创建用于发送数据的线程
//HANDLE sendThread = CreateThread(NULL, 0, ServerSendThread, 0, 0, NULL);//第二个0,代表回掉函数参数为0
while (true) {
PER_HANDLE_DATA * PerHandleData = NULL;
SOCKADDR_IN saRemote;
int RemoteLen;
SOCKET acceptSocket;
// 接收连接,并分配完成端,这儿可以用AcceptEx()
RemoteLen = sizeof(saRemote);
acceptSocket = accept(srvSocket, (SOCKADDR*)&saRemote, &RemoteLen);
if (SOCKET_ERROR == acceptSocket) { // 接收客户端失败
cerr << "Accept Socket Error: " << GetLastError() << endl;
system("pause");
return -1;
}
// 创建用来和套接字关联的单句柄数据信息结构
PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA)); // 在堆中为这个PerHandleData申请指定大小的内存
PerHandleData->socket = acceptSocket;
memcpy(&PerHandleData->ClientAddr, &saRemote, RemoteLen);
clientGroup.push_back(PerHandleData); // 将单个客户端数据指针放到客户端组中
// 将接受套接字和完成端口关联
CreateIoCompletionPort((HANDLE)(PerHandleData->socket), completionPort, (DWORD)PerHandleData, 0);
// 开始在接受套接字上处理I/O使用重叠I/O机制
// 在新建的套接字上投递一个或多个异步
// WSARecv或WSASend请求,这些I/O请求完成后,工作者线程会为I/O请求提供服务
// 单I/O操作数据(I/O重叠)
LPPER_IO_OPERATION_DATA PerIoData = NULL;
PerIoData = (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_OPERATEION_DATA));
ZeroMemory(&(PerIoData->overlapped), sizeof(OVERLAPPED));
PerIoData->databuff.len = 1024;
PerIoData->databuff.buf = PerIoData->buffer;
PerIoData->operationType = 0; // read
IOOperationDataGroup.push_back(PerIoData);
DWORD RecvBytes;
DWORD Flags = 0; //WSARecv中的1,代表缓冲区lpBuffers只包含一个WSABUF,Flags代表接收普通数据
WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL);//PerIoData->overlapped就是CONTAINING_RECORD的第一个变量
}
for (auto it = IOOperationDataGroup.begin(); it != IOOperationDataGroup.end(); it++)
{
if (*it != NULL)
GlobalFree(*it);
}
system("pause");
return 0;
}
// 开始服务工作线程函数
DWORD WINAPI ServerWorkThread(LPVOID IpParam)
{
HANDLE CompletionPort = (HANDLE)IpParam;
DWORD BytesTransferred;
LPOVERLAPPED IpOverlapped;
LPPER_HANDLE_DATA PerHandleData = NULL;
LPPER_IO_DATA PerIoData = NULL;
DWORD RecvBytes;
DWORD Flags = 0;
BOOL bRet = false;
while (true) {
bRet = GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (PULONG_PTR)&PerHandleData, (LPOVERLAPPED*)&IpOverlapped, INFINITE);//此处可以将IpOverlapped换为PerIoData,然后将下面CONTAINING_RECORD注释掉
if (bRet == 0) {
cerr << "GetQueuedCompletionStatus Error: " << GetLastError() << endl;
return -1;
}
PerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(IpOverlapped, PER_IO_DATA, overlapped);
//这个宏的作用是:根据一个结构体实例中的成员的地址,取到整个结构体实例的地址
//PER_IO_DATA的成员overlapped的地址为&IpOverlapped,结果就可以获得PER_IO_DATA的地址
// 检查在套接字上是否有错误发生
if (0 == BytesTransferred) {
closesocket(PerHandleData->socket);
GlobalFree(PerHandleData);
GlobalFree(PerIoData);
continue;
}
// 开始数据处理,接收来自客户端的数据
WaitForSingleObject(hMutex, INFINITE);
cout << "A Client says: " << PerIoData->databuff.buf << endl;
ReleaseMutex(hMutex);
// 为下一个重叠调用建立单I/O操作数据
ZeroMemory(&(PerIoData->overlapped), sizeof(OVERLAPPED)); // 清空内存
PerIoData->databuff.len = 1024;
PerIoData->databuff.buf = PerIoData->buffer;//buf是个指针,这一过程会清空buffer的内容
PerIoData->operationType = 0; // read
WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL);
}
return 0;
}
// 发送信息的线程执行函数
DWORD WINAPI ServerSendThread(LPVOID IpParam)
{
while (1) {
if (clientGroup.empty())
{
Sleep(5000);
continue;
}
char talk[200];
cin.get(talk,200);
int len;
for (len = 0; talk[len] != '\0'; ++len) {
// 找出这个字符组的长度
}
talk[len] = '\n';
talk[++len] = '\0';
printf("I Say:");
cout << talk;
WaitForSingleObject(hMutex, INFINITE);
for (int i = 0; i < clientGroup.size(); ++i) {
send(clientGroup[i]->socket, talk, 200, 0); // 发送信息
}
ReleaseMutex(hMutex);
}
return 0;
}
服务器2:将信息原样返回去
#include <iostream>
using namespace std;
#include <cstdio>
#include <WINSOCK2.H>
#include <windows.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib") // Socket编程需用的动态链接库
#pragma comment(lib, "Kernel32.lib") // IOCP需要用到的动态链接库
#define PORT 5150
#define DATA_BUFSIZE 8192
DWORD WINAPI ServerWorkerThread(LPVOID ComlpetionPortID);
typedef struct
{
OVERLAPPED OVerlapped;
WSABUF DATABuf;
CHAR Buffer[DATA_BUFSIZE];
DWORD BytesSend, BytesRecv;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;
typedef struct
{
SOCKET Socket;
}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
DWORD WINAPI ServerWorkerThread(LPVOID ComlpetionPortID);
int main(int argc, char* argv[])
{
SOCKADDR_IN InternetAddr;
SOCKET Listen, Accept;
HANDLE CompetionPort;
SYSTEM_INFO SystenInfo;
LPPER_HANDLE_DATA PerHandleData;
LPPER_IO_OPERATION_DATA PerIOData;
int i;
DWORD RecvBytes;
DWORD Flags;
DWORD ThreadID;
WSADATA wsadata;
DWORD Ret;
if (Ret = WSAStartup(0x2020, &wsadata) != 0)
{
printf("WSAStartup failed with error %d/n", Ret);
return 0;
}
//打开一个空的完成端口
if ((CompetionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) == NULL)
{
printf("CreateIoCompletionPort failed with error %d/n", GetLastError());
return 0;
}
GetSystemInfo(&SystenInfo);
// 开启cpu个数的2倍个的线程
for (i = 0; i < SystenInfo.dwNumberOfProcessors * 2; i++)
{
HANDLE ThreadHandle;
//创建服务器工作线程,并且向线程传送完成端口
if ((ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, CompetionPort, 0, &ThreadID)) == NULL)
{
printf("CreateThread failed with error %d/n", GetLastError());
return 0;
}
CloseHandle(ThreadHandle);
}
//打开一个服务器socket
if ((Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf("WSASocket()failed with error %d/n", WSAGetLastError());
return 0;
}
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(PORT);
if (bind(Listen, (LPSOCKADDR)&InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)
{
printf("bind failed with error %d/n", WSAGetLastError());
return 0;
}
if (listen(Listen, 5) == SOCKET_ERROR)
{
printf("listen failed with error %d/n", WSAGetLastError());
return 0;
}
//接收连接并且分发给完成端口
while (TRUE)
{
if ((Accept = WSAAccept(Listen, NULL, NULL, NULL, 0)) == SOCKET_ERROR)
{
printf("WSAAccept failed with error %d/n", WSAGetLastError());
return 0;
}
//创建与套接字相关的套接字信息结构
if ((PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA))) == NULL)
{
printf("GlobalAlloc failed with error %d/n", GetLastError());
return 0;
}
// Associate the accepted socket with the original completion port.
printf("Socket number %d connected/n", Accept);
PerHandleData->Socket = Accept;//结构中存入接收的套接字
//与我们的创建的那个完成端口关联起来,将关键项也与指定的一个完成端口关联
if ((CreateIoCompletionPort((HANDLE)Accept, CompetionPort, (DWORD)PerHandleData, 0)) == NULL)
{
printf("CreateIoCompletionPort failed with error%d/n", GetLastError());
return 0;
}
// 创建同下面的WSARecv调用相关的IO套接字信息结构体
if ((PerIOData = (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_OPERATION_DATA))) == NULL)
{
printf("GlobalAloc failed with error %d/n", GetLastError());
return 0;
}
ZeroMemory(&(PerIOData->OVerlapped), sizeof(OVERLAPPED));
PerIOData->BytesRecv = 0;
PerIOData->BytesSend = 0;
PerIOData->DATABuf.len = DATA_BUFSIZE;
PerIOData->DATABuf.buf = PerIOData->Buffer;
Flags = 0;
if (WSARecv(Accept, &(PerIOData->DATABuf), 1, &RecvBytes, &Flags, &(PerIOData->OVerlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
printf("WSARecv()failed with error %d/n", WSAGetLastError());
return 0;
}
}
}
return 0;
}
//工作线程
DWORD WINAPI ServerWorkerThread(LPVOID ComlpetionPortID)
{
HANDLE ComplectionPort = (HANDLE)ComlpetionPortID;
DWORD BytesTransferred;
LPOVERLAPPED Overlapped;
LPPER_HANDLE_DATA PerHandleData;
LPPER_IO_OPERATION_DATA PerIOData;
DWORD SendBytes, RecvBytes;
DWORD Flags;
while (TRUE)
{
if (GetQueuedCompletionStatus(ComplectionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIOData, INFINITE) == 0)
{
printf("GetQueuedCompletionStatus failed with error%d/n", GetLastError());
return 0;
}
//首先检查套接字上是否发生错误,如果发生了则关闭套接字并且清除同套节字相关的SOCKET_INFORATION 结构体
if (BytesTransferred == 0)
{
printf("Closing Socket %d/n", PerHandleData->Socket);
if (closesocket(PerHandleData->Socket) == SOCKET_ERROR)
{
printf("closesocket failed with error %d/n", WSAGetLastError());
return 0;
}
GlobalFree(PerHandleData);
GlobalFree(PerIOData);
continue;
}
//检查BytesRecv域是否等于0,如果是,说明WSARecv调用刚刚完成,可以用从己完成的WSARecv调用返回的BytesTransferred值更新BytesRecv域
if (PerIOData->BytesRecv == 0)
{
PerIOData->BytesRecv = BytesTransferred;
PerIOData->BytesSend = 0;
}
else
{
PerIOData->BytesRecv += BytesTransferred;
}
//
if (PerIOData->BytesRecv > PerIOData->BytesSend)//收到数据比发送的多了,就回发出去
{
//发布另一个WSASend()请求,因为WSASendi 不能确保发送了请的所有字节,继续WSASend调用直至发送完所有收到的字节
ZeroMemory(&(PerIOData->OVerlapped), sizeof(OVERLAPPED));
PerIOData->DATABuf.buf = PerIOData->Buffer + PerIOData->BytesSend;
PerIOData->DATABuf.len = PerIOData->BytesRecv - PerIOData->BytesSend;
if (WSASend(PerHandleData->Socket, &(PerIOData->DATABuf), 1, &SendBytes, 0, &(PerIOData->OVerlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
printf("WSASend()fialed with error %d/n", WSAGetLastError());
return 0;
}
}
}
else
{
PerIOData->BytesRecv = 0;
//Now that is no more bytes to send post another WSARecv()request
//现在己经发送完成
Flags = 0;
ZeroMemory(&(PerIOData->OVerlapped), sizeof(OVERLAPPED));
PerIOData->DATABuf.buf = PerIOData->Buffer;
PerIOData->DATABuf.len = DATA_BUFSIZE;
if (WSARecv(PerHandleData->Socket, &(PerIOData->DATABuf), 1, &RecvBytes, &Flags, &(PerIOData->OVerlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
printf("WSARecv()failed with error %d/n", WSAGetLastError());
return 0;
}
}
}
}
}
简单IOCP例子的更多相关文章
- 简单的例子了解自定义ViewGroup(一)
在Android中,控件可以分为ViewGroup控件与View控件.自定义View控件,我之前的文章已经说过.这次我们主要说一下自定义ViewGroup控件.ViewGroup是作为父控件可以包含多 ...
- CSharpGL(1)从最简单的例子开始使用CSharpGL
CSharpGL(1)从最简单的例子开始使用CSharpGL 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码中包含10多个独立的Demo ...
- 用一个简单的例子来理解python高阶函数
============================ 用一个简单的例子来理解python高阶函数 ============================ 最近在用mailx发送邮件, 写法大致如 ...
- Spring-Context之一:一个简单的例子
很久之前就想系统的学习和掌握Spring框架,但是拖了很久都没有行动.现在趁着在外出差杂事不多,就花时间来由浅入深的研究下Spring框架.Spring框架这几年来已经发展成为一个巨无霸产品.从最初的 ...
- C#调用存储过程简单完整例子
CREATE PROC P_TEST@Name VARCHAR(20),@Rowcount INT OUTPUTASBEGIN SELECT * FROM T_Customer WHERE NAME= ...
- 关于apriori算法的一个简单的例子
apriori算法是关联规则挖掘中很基础也很经典的一个算法,我认为很多教程出现大堆的公式不是很适合一个初学者理解.因此,本文列举一个简单的例子来演示下apriori算法的整个步骤. 下面这个表格是代表 ...
- 为什么C语言在2013年仍然很重要:一个简单的例子
附注:在最初的文章里,我没说明进行模2^64的计算——我当然明白那些不是“正确的”斐波那契数列,其实我不是想分析大数,我只是想探寻编译器产生的代码和计算机体系结构而已. 最近,我一直在开发Dynvm— ...
- Singleton模式(Singleton创建类型)c#简单的例子
单(Singleton创建模式)c#简单的例子 当需要生成一个实例,可单发模式 样品可以在短短的球员中产生,玩家和测试.单线程例子,如以下: namespace singletonpattern { ...
- 修饰模式(Decorator结构化)C#简单的例子
修饰模式(Decorator结构化)C#简单的例子 播放器的基本功能是移动.执行等.BaseAbility 新增功能:1.伤害技能harmAbility:2.阻碍技能BaulkAbility:3.辅助 ...
随机推荐
- Linux:Aircrack-ng
Aircrack-ng 工具主要有 airmon-ng 处理网卡工作模式 airodump-ng 抓包 aircrack-ng 破解 aireplay-ng 发包,干扰 另外还要用到以下 linux ...
- 从JDK源码角度看Byte
Java的Byte类主要的作用就是对基本类型byte进行封装,提供了一些处理byte类型的方法,比如byte到String类型的转换方法或String类型到byte类型的转换方法,当然也包含与其他类型 ...
- RxJava 1.x 笔记:创建型操作符
本篇文章是阅读 官方文档 的笔记. 作者:shixinzhang(百度搜索 "shixinzhang CSDN" 即可找到我) RxJava 也用了有段时间,那么多操作符总不想去记 ...
- TeamTalk源码分析(十一) —— pc客户端源码分析
--写在前面的话 在要不要写这篇文章的纠结中挣扎了好久,就我个人而已,我接触windows编程,已经六七个年头了,尤其是在我读研的三年内,基本心思都是花在学习和研究windows程序上 ...
- SQL基础五(作业代码)
create database stuinfo create table student ( mid ) not null primary key, mname ) not null ) create ...
- iOS通讯录相关知识-浅析
本文来自于:贞娃儿的博客 http://blog.sina.com.cn/zhenwawaer 在开发一些应用中,我们如果需要iPhone设备中的通讯录信息.或者,需要开发通讯录相关的一些功能.那 ...
- IntelliJ-IDEA中mybatis三剑客
一.mybatis-generator的使用 作用:根据数据库自动生成pojo.dao和xml文件. 1.引入mybatis-generator pom.xml中引入配置:
- Loj 2028 随机序列
Loj 2028 随机序列 连续的乘号会将序列分成若干个块,块与块之间用加减号连接: \[ (a_1*a_2*...a_i)\pm(a_{i+1}*a_{i+2}*...a_j)\pm... \] 除 ...
- BZOJ3926 Zjoi2015 诸神眷顾的幻想乡【广义后缀自动机】
Description 幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生日,无数幽香的粉丝到了幽香家门前的太阳花田上来为幽香庆祝生日. 粉丝们非常热情,自发组织表演了一系列节目给幽香看. ...
- BZOJ4150 AMPPZ2014 The Staging 【线段树】*
BZOJ4150 AMPPZ2014 The Staging Description 在舞台上有n个枪手,第i个枪手瞄准了第p[i]个枪手,将于第u[i]秒开枪.一个枪手如果成功开枪, 那么被瞄准的枪 ...