IOCP反射服务器
时间:2022-05-03
本文章向大家介绍IOCP反射服务器,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
这两天学习了一下IOCP网络模型。
主要参考了这两片文章:http://blog.csdn.net/neicole/article/details/7549497/和http://blog.csdn.net/piggyxp/article/details/6922277
IOCP是我见过的最复杂的网络模型了,在Windows里肯定就是boss了,而且一开始我感觉IOCP甚至比epoll还要复杂(其实epoll也不复杂,全部都不复杂,只是不懂的人觉得复杂~)。当仔细研究一下之后,觉得也就
也像我很纠结的公事 此际回头看
原来并没有事
真想不到当初我们也讨厌吃苦瓜
今天竟吃得出那睿智愈来愈记挂
私以为,掌握IOCP的关键应该是异步的概念和回调。
Talk is cheap, show you the code !
Server.cpp
#include <WinSock2.h>
#include <Windows.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")//Socket编程需用的动态链接库
#define DEFAULTPORT 6000
#define DEFAULTSIZE 1024
/**
* 结构体名称:PER_IO_DATA
* 结构体功能:重叠I/O需要用到的结构体,临时记录IO数据
**/
typedef struct
{
OVERLAPPED overlapped;
char buffer[DEFAULTSIZE];
int BufferLen;
bool readflag;
}PER_IO_DATA, *LPPER_IO_DATA;
/**
* 结构体名称:PER_HANDLE_DATA
* 结构体存储:记录单个套接字的数据,包括了套接字的变量及套接字的对应的客户端的地址。
* 结构体作用:当服务器连接上客户端时,信息存储到该结构体中,知道客户端的地址以便于回访。
**/
typedef struct
{
SOCKET socket;
SOCKADDR_IN ClientAddr;
}PER_SOCKET_DATA, *LPPER_SOCKET_DATA;
DWORD WINAPI ServerWorkThread(LPVOID CompletionPortID)
{
HANDLE CompletionPort = (HANDLE)CompletionPortID;
DWORD BytesTransferred;
LPPER_SOCKET_DATA PerSocketData = NULL;
LPPER_IO_DATA PerIoData = NULL;
DWORD RecvBytes;
DWORD Flags = 0;
BOOL bRet = false;
while(true)
{
bRet = GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (PULONG_PTR)&PerSocketData, (LPOVERLAPPED*)&PerIoData, INFINITE);
if(bRet == FALSE)
{
printf("GetQueuedCompletionStatus Error:%dn", GetLastError());
if(PerSocketData != NULL)
{
if(PerSocketData->socket != NULL)
closesocket(PerSocketData->socket);
free(PerSocketData);
}
if(PerIoData != NULL)
free(PerIoData);
continue;
}
//PerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(IpOverlapped, PER_IO_DATA, overlapped);
//http://blog.csdn.net/mylovepan/article/details/8204126
//http://bbs.csdn.net/topics/390719212
//1.关闭窗口或者停止调试windows会释放句柄的,socket会被关闭并向对方发送断开连接
//2.对方关闭socket后,WSARecv操作的GetQueuedCompletionStatus返回值是真,不过收到的字节数BytesTransferred是0,这里判断。
//GetQueuedCompletionStatus返回TRUE并且读取到的数据长度为0时,关闭套接字
if(BytesTransferred == 0)
{
printf("BytesTransferred==0:%dn", GetLastError());
if(PerSocketData != NULL)
{
if(PerSocketData->socket != NULL)
closesocket(PerSocketData->socket);
free(PerSocketData);
}
if(PerIoData != NULL)
free(PerIoData);
continue;
}
int iRes = 0;
Flags = 0;
if(PerIoData->readflag == true)
{
// 开始数据处理,接收来自客户端的数据
printf("%s says: %sn", inet_ntoa(PerSocketData->ClientAddr.sin_addr), PerIoData->buffer);
//send(PerSocketData->socket, PerIoData->buffer, strlen(PerIoData->buffer)+1, 0);
memset(&PerIoData->overlapped, 0, sizeof(PerIoData->overlapped));
PerIoData->readflag = false;
WSABUF wsabuf;
wsabuf.buf = PerIoData->buffer;
wsabuf.len = sizeof(PerIoData->buffer);
iRes = WSASend(PerSocketData->socket, &wsabuf, 1, &RecvBytes, Flags, &PerIoData->overlapped, NULL);
}
else
{
//为下一个重叠调用建立单I/O操作数据
memset(PerIoData, 0, sizeof(PER_IO_DATA)); // 清空内存
PerIoData->readflag = true;
WSABUF wsabuf;
wsabuf.buf = PerIoData->buffer;
wsabuf.len = sizeof(PerIoData->buffer);
iRes = WSARecv(PerSocketData->socket, &wsabuf, 1, &RecvBytes, &Flags, &PerIoData->overlapped, NULL);
}
}
return 0;
}
int main(int argc, char* argv[])
{
//加载socket动态链接库
WORD wVersionRequested = MAKEWORD(2, 2); //请求2.2版本的WinSock库
WSADATA wsaData; //接收Windows Socket的结构信息
DWORD err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)//检查套接字库是否申请成功
{
printf("Request Windows Socket Library Error!n");
system("pause");
return -1;
}
if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)//检查是否申请了所需版本的套接字库
{
WSACleanup();
printf("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 (completionPort == NULL)//创建IO内核对象失败
{
printf("CreateIoCompletionPort failed. Error:%dn", GetLastError());
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);
if(ThreadHandle == NULL){
printf("Create Thread Handle failed. Error:%dn", GetLastError());
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(bindResult == SOCKET_ERROR)
{
printf("Bind failed. Error:", GetLastError());
system("pause");
return -1;
}
// 将SOCKET设置为监听模式
int listenResult = listen(srvSocket, 10);
if(listenResult == SOCKET_ERROR)
{
printf("Listen failed. Error:%dn", GetLastError());
system("pause");
return -1;
}
//开始处理IO数据
printf("服务器已准备就绪,正在等待客户端的接入...n");
while(true)
{
SOCKET acceptSocket;
//接收连接,并分配完成端,这儿可以用AcceptEx()
SOCKADDR_IN saRemote;
int RemoteLen = sizeof(saRemote);
acceptSocket = accept(srvSocket, (SOCKADDR*)&saRemote, &RemoteLen);
if(acceptSocket == SOCKET_ERROR)// 接收客户端失败
{
printf("Accept Socket Error:%dn", GetLastError());
system("pause");
return -1;
}
//创建用来和套接字关联的单句柄数据信息结构
LPPER_SOCKET_DATA PerSocketData = (LPPER_SOCKET_DATA)malloc(sizeof(PER_SOCKET_DATA)); //在堆中为这个PerHandleData申请指定大小的内存
PerSocketData->socket = acceptSocket;
memcpy(&PerSocketData->ClientAddr, &saRemote, RemoteLen);
// 将接受套接字和完成端口关联
CreateIoCompletionPort((HANDLE)(PerSocketData->socket), completionPort, (DWORD)PerSocketData, 0);
// 开始在接受套接字上处理I/O使用重叠I/O机制
// 在新建的套接字上投递一个或多个异步
// WSARecv或WSASend请求,这些I/O请求完成后,工作者线程会为I/O请求提供服务
// 单I/O操作数据(I/O重叠)
LPPER_IO_DATA PerIoData = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
memset(PerIoData, 0, sizeof(PER_IO_DATA));
PerIoData->readflag = true; // read
DWORD RecvBytes;
DWORD Flags = 0;
WSABUF wsabuf;
wsabuf.buf = PerIoData->buffer;
wsabuf.len = sizeof(PerIoData->buffer);
WSARecv(PerSocketData->socket, &wsabuf, 1, &RecvBytes, &Flags, &PerIoData->overlapped, NULL);
}
system("pause");
return 0;
}
服务器的代码不到200行,其实也就是几个API函数的调用,但是效率却明显比其他(Windows)模型要高!
Client.cpp
#include <winsock2.h>
#include <Windows.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib") // Socket编程需用的动态链接库
#define DEFAULTPORT 6000
#define DEFAULTSIZE 1024
int main(int argc, char* argv[])
{
//加载socket动态链接库
WORD wVersionRequested = MAKEWORD(2, 2); //请求2.2版本的WinSock库
WSADATA wsaData; //接收Windows Socket的结构信息
DWORD err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)//检查套接字库是否申请成功
{
printf("Request Windows Socket Library Error!n");
system("pause");
return -1;
}
if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)//检查是否申请了所需版本的套接字库
{
WSACleanup();
printf("Request Windows Socket Version 2.2 Error!n");
system("pause");
return -1;
}
// 创建socket操作,建立流式套接字,返回套接字号sockClient
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
if(sockClient == INVALID_SOCKET)
{
printf("Error at socket():%ldn", WSAGetLastError());
WSACleanup();
return -1;
}
// 将套接字sockClient与远程主机相连
// int connect( SOCKET s, const struct sockaddr* name, int namelen);
// 第一个参数:需要进行连接操作的套接字
// 第二个参数:设定所需要连接的地址信息
// 第三个参数:地址的长度
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 本地回路地址是127.0.0.1;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(DEFAULTPORT);
while(SOCKET_ERROR == connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
{
// 如果还没连接上服务器则要求重连
printf("服务器连接失败,是否重新连接?(Y/N):");
char choice;
while((choice = getchar()) && (!((choice != 'Y' && choice == 'N') || (choice == 'Y' && choice != 'N'))))
{
printf("输入错误,请重新输入:");
}
if (choice == 'Y'){
continue;
}
else
{
printf("退出系统中...n");
system("pause");
return 0;
}
}
printf("客户端已准备就绪,直接输入文字向服务器反馈信息。n");
char buf[DEFAULTSIZE];
int iRes = 0;
while(true)
{
gets(buf);
if(buf[0] == 'q')
{
break;
}
else
{
printf("I Say:("quit"to exit):%sn",buf);
iRes = send(sockClient, buf, strlen(buf)+1, 0); // 发送信息
memset(buf, 0, DEFAULTSIZE);
iRes = recv(sockClient, buf, DEFAULTSIZE, 0);
printf("Server Say:%sn",buf);
}
}
closesocket(sockClient);
WSACleanup(); // 终止对套接字库的使用
printf("End linking...n");
system("pause");
return 0;
}
- 机器学习之——LINE及LargeVis可视化算法
- 开发人员看测试之细说JBehave
- 智能合约中存在的3种最常见的误解
- O'ReillyAI系列:将学习速率可视化来优化神经网络
- 再下一城,腾讯黑科技介入新零售
- 微信团队广发内部体验邀请,小程序将大火!
- 胖虎科技获1亿元融资 域名“我爱胖虎”创意十足!
- 高挺:区块链在金融领域的三个应用方向
- 条码支付迎来分级限额制 支付宝、微信纷纷响应
- 关键基础设施威胁预警,HDD声波攻击可致蓝屏
- 集成学习之随机森林通俗理解
- 深度学习CNN眼中的图片是什么样的
- 第七节 关联映射之多对多
- 词向量fasttext,CNN is All,强化学习,自回归生成模型,可视化神经网络损失函数
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Redis 的过期策略是如何实现的?
- 记一次线上问题及反思
- 用 Redis 散列实现短网址生成器|文末福利
- 原创|面试官:Java对象一定分配在堆上吗?
- 频繁FGC的真凶原来是它
- 类加载器知识点吐血整理
- ThreadPoolExecutor 线程池"源码分析"
- 一起刷 leetcode 之螺旋矩阵(头条和美团真题)
- 如何快速判断一个用户是否访问过我们的 APP?
- replication-manager之switchover剖析
- 组复制安装部署 | 全方位认识 MySQL 8.0 Group Replication
- 提升低端设备的 Web 性能
- TypeScript 4.0 RC发布,带来诸多更新
- istio mcp实现探究
- K8S 生态周报| Helm v2 进入维护期倒计时