iocp之简单实现

2023-10-26

一、步骤

这里写图片描述

二、代码

1、头文件

#include <WinSock2.h>
#include <Windows.h>

#ifdef _WINDOWS
#pragma comment(lib,"Ws2_32.lib")   //Socket编程需用的动态链接库
#pragma comment(lib,"Kernel32.lib") //IOCP需要用到的动态链接库
#endif

//LOG_VIEW->log(str, ...)	功能:输出日志到界面

2、加载套接字库

bool IocpServer::loadSockDll()
{
    WSAData wsa;
    int ret = WSAStartup(MAKEWORD(2, 2), &wsa);
    if (0 != ret)
    {
        LOG_VIEW->log("加载套接字库失败,err:%d", ret);
        return false;
    }

    if (LOBYTE(wsa.wVersion) != 2 || HIBYTE(wsa.wVersion) != 2)
    {
        WSACleanup();
        LOG_VIEW->log("没有找到2.2版本套接字库");
        return false;
    }

    return true;
}

3、创建IOCP+创建IOCP工作线程

bool IocpServer::initIocp()
{
    LOG_VIEW->log("创建IOCP端口...");
    m_iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
    if (NULL == m_iocp)
    {
        LOG_VIEW->log("创建IOCP端口失败");
        return false;
    }

    LOG_VIEW->log("创建IOCP处理线程...");
    SYSTEM_INFO info;
    GetSystemInfo(&info);
    for (int i = 0; i < info.dwNumberOfProcessors*2; ++i)
    {
        LOG_VIEW->log("创建线程:%d", i+1);
        thread* th = new thread(std::bind(&IocpServer::workerProc, this, i+1));
        m_workerThread.push_back(th);
    }

    return true;
}
  1. CreateIoCompletionPort参数说明:
    param:有效的文件句柄 或 INVALID_HANDLE_VALUE(一般用这个)
    param:NULL(新建IOCP时)
    param:0。
    param:操作系统允许同时处理I/O完成端口数据包的最大线程数,如果此参数为零,则最大线程数等于cpu数量。

  2. 创建的工作线程数量为cpu数量*2,考虑到线程查询修改数据库时有等锁的情况,多余的线程可以替补。

4、创建socket、绑定、监听+创建接收客户端连接请求线程

bool IocpServer::initSocket(const string& ip, unsigned short port)
{
    LOG_VIEW->log("创建套接字");
    m_socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (INVALID_SOCKET == m_socket)
    {
        LOG_VIEW->log("创建socket失败");
        return false;
    }

    LOG_VIEW->log("绑定");
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.S_un.S_addr = inet_addr(ip.c_str());
    if(0 != ::bind(m_socket, (sockaddr*)&addr, sizeof(addr)))
    {
        LOG_VIEW->log("绑定socket失败");
        return false;
    }

    LOG_VIEW->log("监听");
    if (0 != listen(m_socket, 100))
    {
        LOG_VIEW->log("监听socket失败");
        return false;
    }

    LOG_VIEW->log("创建accept线程");
    m_accpetThread = new thread(std::bind(&IocpServer::acceptProc, this));

    return true;
}

WSASocket说明:socket的扩展版,最后一个参数WSA_FLAG_OVERLAPPED表示创建具有重叠属性的socket。只有具有重叠属性才够使用WSASend、WSARecv、WSAIoctl等函数。

5、接收客户端连接请求线程

void IocpServer::acceptProc()
{
    sockaddr_in addr;
    int addrLen = sizeof(addr);

    LOG_VIEW->log("accept线程工作中...");
    while (!m_stopThread)
    {
        memset(&addr, 0, sizeof(addr));
        SOCKET clientSocket = accept(m_socket, (sockaddr*)&addr, &addrLen);
        if (INVALID_SOCKET == clientSocket)
        {
            LOG_VIEW->log("接受连接失败");
            break;
        }

		//将IOCP端口与客户端SOCKET绑定
        CreateIoCompletionPort((HANDLE)clientSocket, m_iocp, (ULONG_PTR)clientSocket, 0);

        //通知新增连接,收到通知的模块处理为客户端创建会话对象
        string ip = inet_ntoa(addr.sin_addr);
        unsigned short port = htons(addr.sin_port);
        if (!m_notify->addConnect(clientSocket, ip, port))
        {
            LOG_VIEW->log("添加会话连接失败");
            continue;
        }

		//为客户端投递接收请求
        sIocpContent* ct = new sIocpContent(BUFF_LEN, eIocpType::IocpRecv);
        if (!deliveryRecvRequest(clientSocket, ct))
        {
            LOG_VIEW->log("投递IOCP接收请求失败");
            delete ct;
            break;
        }
    }

    LOG_VIEW->log("accept线程自然退出");
}

CreateIoCompletionPort参数说明:
param:有效的文件句柄 或 INVALID_HANDLE_VALUE,这里用客户端SOCKET
param:现有I/O完成端口的句柄(与socket绑定时)
param:传送给处理函数的参数,即传给GetQueuedCompletionStatus第三个参数
param:操作系统允许同时处理I/O完成端口数据包的最大线程数,如果此参数为零,则最大线程数等于cpu数量。

6、IOCP工作线程

void IocpServer::workerProc(int tag)
{
    DWORD revLen = 0;
    SOCKET pKey = 0;
    LPOVERLAPPED pOl = NULL;

    LOG_VIEW->log("iocp线程[%d]工作中...", tag);
    while (!m_stopThread)
    {
        bool ret = GetQueuedCompletionStatus(m_iocp, &revLen, (PULONG_PTR)&pKey, &pOl, INFINITE);
        if (!ret)
        {
		    //错误处理
        }
		else
		{
			sIocpContent* content = (sIocpContent*)pOl;
			switch (content->type) {
	        case eIocpType::IocpSend:
	            //发送数据成功通知
	            //处理发送数据占的内存
	            //这部分内存使用内存池,可以重复利用
	            break;
	        case eIocpType::IocpRecv:
	            //收到客户端发来数据的通知
	            //将数据发送到这个客户端的会话对象,会话对象负责解包+处理
	            //为客户端投递接收请求,就可以继续收到客户端发来的数据了
	            memset(&content->ol, 0, sizeof(content->ol));
	            deliveryRecvRequest(pKey, content);
	            break;
	        default:
	            break;
	        }
		} 
    }

    LOG_VIEW->log("iocp线程[%d]自然退出", tag);
}

GetQueuedCompletionStatus参数说明
param:之前创建的IOCP端口
param:收到的数据长度
param:IOCP端口与SOCKET绑定时,CreateIoCompletionPort的第三个参数
param:投递请求时,CreateIoCompletionPort的第六个参数,是个指针
param:等待通知的时间,INFINITE即一直等

7、为客户端投递接收请求

bool IocpServer::deliveryRecvRequest(unsigned sock, sIocpContent *content)
{
    DWORD revLen = 0, lpFlag = 0;
    int ret = WSARecv(sock, &(content->data), 1, &revLen, &lpFlag, &(content->ol), NULL);
	if (ret == 0)
    {
		//收到客户端发来的数据
		//将数据发送到这个客户端的会话对象,会话对象负责解包+处理
		//为客户端投递接收请求,就可以继续收到客户端发来的数据了
    }
    else if (ret == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING)
    {
	    //错误处理
	    return false;
    }

    return true;
}

WSARecv参数说明
param:客户端套接字
param:指向WSABUF结构数组的指针
param:参数2数组的长度
param:接收操作立即完成时,此调用接收的字节数的指针
param:标志的指针,一般初始为0
param:指向WSAOVERLAPPED结构的指针,被底层加工后传给GetQueuedCompletionStatus的第四个参数
param:指向完成接收操作时调用的完成例程的指针,传NULL
返回值:若cpu空闲,可能马上就收到数据,返回0。若发生错误,返回SOCKET_ERROR。但若错误码是WSA_IO_PENDING表示,CPU现在忙,这个接收任务放一边,底层完成了再告诉你。

8、释放资源(愤怒地写在一起)

	//线程自然退出标志
	m_stopThread = true;
	
	//释放SOCKET
	if (INVALID_SOCKET != m_socket)
    {
        LOG_VIEW->log("释放套接字");
        closesocket(m_socket);
        m_socket = INVALID_SOCKET;
    }

    if (m_accpetThread)
    {
        LOG_VIEW->log("等待accept线程结束");
        if (m_accpetThread->joinable())
        {
            m_accpetThread->join();
        }

        delete m_accpetThread;
        m_accpetThread = NULL;
        LOG_VIEW->log("accept线程结束");
    }

	//释放IOCP相关
	if (m_iocp)
    {
        LOG_VIEW->log("关闭IOCP端口");
        CloseHandle(m_iocp);
        m_iocp = NULL;
    }

    for (size_t i = 0; i < m_workerThread.size(); ++i)
    {
        char buff[8] = {0};
        itoa(i+1, buff, 10);

        if (m_workerThread[i]->joinable())
        {
            LOG_VIEW->log("等待线程结束:%d", i+1);
            m_workerThread[i]->join();
        }

        LOG_VIEW->log("线程结束:", i+1);
        delete m_workerThread[i];
    }
    m_workerThread.clear();
	
	//卸载套接字库
	WSACleanup();

9、sIocpContent结构体

#include <Winsock2.h>

#define BUFF_LEN    (1024)

enum eIocpType
{
    IocpNone = 0,
    IocpRecv,
    IocpSend,
};

struct sIocpContent
{
    OVERLAPPED	ol;
    WSABUF		data;
    eIocpType	type;

    sIocpContent(int len, eIocpType tp)
    {
        memset(&ol, 0, sizeof(ol));
        data.buf = new char[len]();
        data.len = len;
        type = tp;
    }

    sIocpContent(char* buff, int len)
    {
        memset(&ol, 0, sizeof(ol));
        data.buf = buff;
        data.len = len;
        type = eIocpType::IocpSend;
    }

    ~sIocpContent()
    {
        delete[] data.buf;
    }
};
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

iocp之简单实现 的相关文章

  • java项目利用launch4j生成可执行exe文件

    一 项目结构说明 参见文章 java项目打成可运行jar包 http mp blog csdn net postedit 79194671 二 操作流程 1 右键项目 gt export gt Runnable JAR File gt Ne
  • Nginx【反向代理负载均衡动静分离】--下

    Nginx 反向代理负载均衡动静分离 下 Nginx 工作机制 参数设置 master worker 机制 示意图 图解 一个master 管理多个worker 一说master worker 机制 争抢机制示意图 图解 一个master
  • 获取时间/时间戳,并比大小

    获取当前时间戳的几种方法 1 System currentTimeMillis 2 Calendar getInstance getTimeInMillis 3 new Date getTime 注 上面的获取时间戳值都是毫秒级的 返回的都
  • 微信公众号发送模板信息报错——invalid credential, access_token is invalid or not latest hints:

    这个大部分原因是access token不正确导致的 这个access token是微信开放文档 公众号 开始开发 获取Access Token下的获取access token获取的 而不是下面的微信网页开发 网页授权中获取access t
  • 等概率抽样——水塘抽样

    等概率抽样 水塘抽样 给出一个数据流 这个数据流的长度很大或者未知 并且对该数据流中数据只能访问一次 且不能使用额外的空间 请写出一个随机选择算法 使得数据流中所有数据被选中的概率相等 从头开始遍历数据 当遍历到第n个数据时 从0到n 1中
  • 使用Wireshark与Burp Suite分析HTTPS协议

    目录 目的 内容 器材 设备 元器件 步骤 1 虚拟机服务器搭建网站 本机访问用Burp Suite抓包获取头信息 2 Wireshark和Burp Suite处理HTTPS的过程与技术实现过程原理 1 Wireshark 2 Burp S
  • Spring Cloud五大组件

    Spring Cloud五大组件 Spring Cloud是分布式微服务架构的一站式解决方案 在Spring Boot基础上能够轻松搭建微服务系统的架构 现有Spring Cloud有两代实现 一代 Spring Cloud Netflix

随机推荐

  • SpringBoot(一)使用itelliJ社区版创建SpringBoot项目

    工欲善其事 必先利其器 要开发SpringBoot项目 首先就是选择一款合适的开发工具 目前市面上有很多优秀的开发工具 例如 IntelliJ IDEA Visual Studio Code 和 Eclipse 等 那么我们该如何选择呢 最
  • 17 Redis缓存更新策略(缓存和数据库更新选择)

    1 先更新数据库 在更新缓存 会有异常 更新完Mysql 发生异常 redis还没有更新 导致redis的是旧数据 下次读取缓存读的是脏数据 2 先删除缓存 在更新数据库 1 缓存已经删了 mysql还在更新中 由于Mysql还在更新中 会
  • java类注释_Java注释,java类注释详解

    一个程序的可读性 关键点就在于注释 下面要给大家讲到的就是Java注释方面的知识 主要会介绍java类注释 类注释一般必须放在所有的 import 语句之后 类定义之前 主要声明该类可以做什么 以及创建者 创建日期 版本和包名等一些信息 下
  • VS2022编译调试FFmpeg

    Git 工具下载 安装直接选择好安装路径 直接下一步到安装完成 配置好工程目录 msvc为编译生成的目标文件的目录 source为源代码目录 进入source目录 通过Git下载FFmpeg git clone https github c
  • 设计模式(三)——装饰模式

    往期地址 设计模式 一 简单工厂模式 设计模式 二 策略模式 本期主题 讲解装饰模式 装饰模式 1 什么是装饰模式 2 实例 2 1 场景 2 2 代码 1 什么是装饰模式 装饰模式是一种结构型设计模式 可以把对象的特殊行为进行封装 从而为
  • ue4单选框设置

    奇怪的是 ue4只有多选框 原来合并了 Style FCoreStyle Get RadioButton 不多说了 放代码 先枚举几个 enum ERadioChoice Radio0 Radio1 Radio2 SHorizontalBo
  • steps_per_epoch=2000,epochs=100之间的区别

    第一种解释 batchsize 中文翻译为批大小 批尺寸 在深度学习中 一般采用SGD训练 即每次训练在训练集中取batchsize个样本训练 iteration 中文翻译为迭代 1个iteration等于使用batchsize个样本训练一
  • 小程序

    文章目录 新建项目并梳理项目结构 配置导航栏效果 配置 tabBar 效果 实现轮播图效果 获取轮播图数据 渲染轮播图页面效果 对轮播图进行美化 实现九宫格效果 获取九宫格数据 渲染九宫格布局 美化九宫格布局 实现图片布局 上拉触底案例 定
  • Lua判断一个对象是否为空,包含userdata

    Lua判断一个对象是否为空 客户端在开发游戏的时候 经常会遇到一个问题 C 传递过来的GameObject已经被销毁了 但是lua这边判断却不是nil 而是一个userdata 那怎么判断一个userdata是否为空呢 可以使用如下代码判断
  • SSH密码暴力破解

    SSH密码暴力破解 1 Hydra 海德拉 hydra 是世界顶级密码暴力密码破解工具 支持几乎所有协议的在线密码破解 功能强大 其密码能否被破解关键取决于破解字典是否足够强大 在网络安全渗透过程中是一款必备的测试工具 1 1指定用户破解
  • 数据结构自考

    设带头结点的单向循环链表的头指针为变量head 则空循环链表的判定条件是 head gt next head 一个具有n个顶点的无向完全图的边数为 n n 1 2 设某有向图有n个顶点 则改有向图对应的邻接表中的表头节点个数为n 若查找每个
  • 使用freemarker生成静态html文件

    一 引入依赖
  • 如何在家里赚钱,分享六个适合普通人长期可做的副业

    当今社会 压力越来越大 工作 家庭 生活等等 方方面面都需要钱 仅靠一份工作赚钱 已经很难满足我们的需求 所以越来越多的人尝试做副业 通过副业增加收入 让生活过得更幸福 常见的副业都是业余学一项技能 通过这项技能来赚钱 这是最保险的副业方式
  • 获取磁盘信息并扫描是否连接移动磁盘之JS版

    昨天弄了一天 扫描U盘是否连接到电脑 的功能 折腾了一天出了个java版和js版 这里记录下JS版本 先说说思路 从浏览器的地址栏打开网页之后 通过onload事件调用ActiveXObject判断U盘是否连接到电脑 若是连接了获取信息显示
  • 数论 费马小定理+快速幂取模

    D Sum Time Limit 1000MS Memory Limit 131072KB 64bit IO Format I64d I64u Submit Status Description Input 2 Output 2 Hint
  • 商用服务器性能,安全与性能兼顾的商用服务器推荐

    数据作为企业生存之本 如果没有好的商用服务器为其安全性保驾护航 企业就会变成无本之木 而在众多商用服务器推荐中 能将稳定性能 快速处理 高安全性等优点集一身的当属全球销量独占鳌头的戴尔14代PowerEdge服务器 作为现代化数据中心的基石
  • 关于“使用rand()产生的随机数每次得到的结果相同”的问题

    之前使用rand 产生随机数出现了一个怪问题 一直没能理解 初次出现的随机数数列是1111 5556 3543 6434 3245 再次运行 产生的结果仍然是1111 5556 3543 6434 3245 再运行 仍然是1111 5556
  • apt命令

    在基于 Debian 的 Linux 发行版中 有各种工具可以与 APT 进行交互 以方便用户安装 删除和管理的软件包 apt get 便是其中一款广受欢迎的命令行工具 另外一款较为流行的是 Aptitude 这一命令行与 GUI 兼顾的小
  • 利用python进行图像视觉基础练习

    练习内容为 1 对于图像进行均值滤波和中值滤波 2 对于图像的第二个波段 进行灰度图自适应均衡化 输出为单波段影像 3 对于图像的第三个波段进行阈值处理和自适应阈值处理 输出为单波段影像 4 对于图像先进行二值化 再进行两种形态学处理 开运
  • iocp之简单实现

    一 步骤 二 代码 1 头文件 include