C++实现进程端口网络数据接收系统设计示例程序

一、问题描述

最近做了一道简单的系统设计题,大概描述如下:
1.一个进程可以绑定多个端口,用于监听接收网络中的数据,但是一个端口只能被一个进程占用
2.1 <= pid <= 65535, 1 <= port <= 100000, 1 <= topNum <= 5, 0 <= packetLen < 1000
类接口函数声明如下,要求实现其中每个函数,满足程序要求。

class NetWorkRecvSystem
{
public:NetWorkRecvSystem();// 将某个端口和进程绑定bool BindPort(int pid, int port);// 解除端口port的绑定,如果port未被当前系统中的进程占用,则返回falsebool UnBindPort(int port);// 在端口port上接收到字节数为packetLen长度的网络数据// 如果当前端口已被解绑或未被进程占用,则直接返回0// 否则该端口对应的进程的接收数据总长度累加上当前的dataLen,返回最后的总长度int RecvNetData(int port, int dataLen);// 统计总接收数据长度排名前topNum的进程列表// 按照如下规则进行排序输出:// 1.先按照进程的总数据接收长度从大到小降序排序// 2.如果两个进程的数据接收总长度相等,则按照进程pid从小到大升序// 最后返回前topNum个进程的列表// 注意:数据长度为0的进程不输出,如果所有进程都没有接收到数据,则返回空列表{}std::vector<int> statTopNum(int topNum);
};

举例1:
输入:
NetWorkRecvSystem sys; // 创建一个系统变量
sys.BindPort(12345, 80);
sys.BindPort(67890, 3306);
sys.BindPort(12345, 8080);
sys.statTopNum(2); // 由于当前进程只做端口绑定,还未接收到数据,所以返回空列表 []
sys.RecvNetData(80, 100); // 端口80上接收到100字节的网络数据,此时进程12345的总数据接收长度为100
sys.RecvNetData(3306, 300); // 端口3306上接收到300字节的网络数据,此时进程67890的总数据接收长度为300
sys.statTopNum(1); // 由于此时进程67890的总长度为300,大于进程12345的总数据接收长度100,所以返回[67890]
sys.RecvNetData(80,200); // 123456 -> 300, 67890 -> 300
sys.BindPort(34567, 3306); // false
sys.BindPort(34567, 21);
sys.RecvNetData(21,400); // 34567 -> 400,此时123456 -> 300, 67890 -> 300
sys.statTopNum(5); // [34567, 123456, 67890]
sys.UnBindPort(21);
sys.statTopNum(1); // [34567]

系统设计

做系统设计这类题目,首选要读懂题意,其次再选择合适的数据结构用于保存数据,我首先想到用一个std::map<int, ProcessItem>的接口来保存每个进程的网络端口和数据包接收信息,其中ProcessItem结构如下:

struct ProcessItem
{int processId = -1;		// 进程的pid,唯一标识std::set<int> ports;	// 进程所占用的端口集合,一个进程可占用多个不同的端口int packetLen = 0;		// 进程所有端口接收到的总报文字节数
};

后面实际写代码过层中发现std::map是个红黑树结构,不太好排序,而且会有些数据冗余;只用std::vector<ProcessItem> procItemVec;数组就能满足要求,而且结合C++ STL algorithmstd::vector排序很方便。

还有一个要注意的点,对std::vector循环遍历时,如果要erase删除某个元素,要注意迭代器失效的问题,这个可以参考我之前的一篇博客:C++ vector迭代器失效

C++代码实现:

NetWorkSystem.h头文件

#include <vector>
#include <set>using std::vector;
using std::set;struct ProcessItem
{int processId = -1;		// 进程的pid,唯一标识std::set<int> ports;	// 进程所占用的端口集合,一个进程可占用多个不同的端口int packetLen = 0;		// 进程所有端口接收到的总报文字节数
};class NetWorkSystem
{
public:NetWorkSystem();~NetWorkSystem();// 将某个端口和进程绑定bool BindPort(int pid, int port);// 解除端口port的绑定,如果port未被当前系统中的进程占用,则返回falsebool UnBindPort(int port);// 在端口port上接收到字节数为packetLen长度的网络数据// 如果当前端口已被解绑或未被进程占用,则直接返回0// 否则该端口对应的进程的接收数据总长度累加上当前的dataLen,返回最后的总长度int RecvNetPacketData(int port, int packetLen);// 统计总接收数据长度排名前topNum的进程列表// 按照如下规则进行排序输出:// 1.先按照进程的总数据接收长度从大到小降序排序// 2.如果两个进程的数据接收总长度相等,则按照进程pid从小到大升序// 最后返回前topNum个进程的列表// 注意:数据长度为0的进程不输出,如果所有进程都没有接收到数据,则返回空列表{}std::vector<int> statTopNum(int topNum);private:std::vector<ProcessItem> procItemVec;	// 数据,用来保存进程和端口映射的数组
};

NetWorkSystem.cpp实现文件:

#include "NetWorkSystem.h"
#include <algorithm>NetWorkSystem::NetWorkSystem()
{
}NetWorkSystem::~NetWorkSystem()
{
}bool NetWorkSystem::BindPort(int pid, int port)
{if (pid <= 0 || port <= 0) {return false;}// 如果端口port已被其他进程占用,则不处理,直接返回falsefor (auto procIter : procItemVec) {if (procIter.ports.count(port) != 0) {return false;}}auto iter = std::find_if(procItemVec.begin(), procItemVec.end(), [pid](const ProcessItem item) {return pid == item.processId;});// 如果之前有进程,则将其插入到对应进程的ports集合中(集合可以去重)if (iter != procItemVec.end()) {iter->ports.insert(port);} else {// 之前没有该进程,则新建一项,初始化进程信息,并放入到数组中ProcessItem procItem;procItem.processId = pid;std::set<int> portSet = { port };procItem.ports = portSet;procItem.packetLen = 0;procItemVec.push_back(procItem);}return true;
}bool NetWorkSystem::UnBindPort(int port)
{if (port <= 0) {return false;}// 如果端口port被其他进程占用,则从对应进程的端口集合中解绑,直接返回truefor (auto procIter : procItemVec) {auto portIter = procIter.ports.find(port);// 找到对应的端口portif (portIter != procIter.ports.end()) {// 将该端口中对应进程的端口集合中移除procIter.ports.erase(port);return true;}}// 如果没找到该端口,则返回falsereturn false;
}int NetWorkSystem::RecvNetPacketData(int port, int packetLen)
{if (port <= 0 || packetLen <= 0) {return 0;}for (auto procIter = procItemVec.begin(); procIter != procItemVec.end(); procIter++) {// 找到对应的端口if (procIter->ports.count(port) != 0) {procIter->packetLen += packetLen;return procIter->packetLen;}}return 0;
}// 统计接收网络数据包总长度前topNum的进程列表
std::vector<int> NetWorkSystem::statTopNum(int topNum)
{std::vector<int> pidList;// 1. 先缓存进程信息列表(对缓存数据进行处理,防止原始数据procItemVec被弄脏)auto procItemVecTemp = procItemVec;// 2. 移除那些网络数据包为0的进程项for (auto iter = procItemVecTemp.begin(); iter != procItemVecTemp.end();) {if (iter->packetLen == 0) {iter = procItemVecTemp.erase(iter);	// 注意:vector在循环时做erase操作很容易导致迭代器失效问题} else {iter++;}}// 3. 如果procItemVecTemp长度为0,即所有进程都没有接收到数据包,则返回空列表if (procItemVecTemp.size() == 0) {return std::vector<int>();}// 4. 对第3步处理后的进程信息数据按照规则进行排序// 规则1: 先根据进程的packetLen长度从大到小降序// 规则2: 如果两个进程项的packetLen相等,则按照进程processId从小到大升序std::sort(procItemVecTemp.begin(), procItemVecTemp.end(), [](const ProcessItem item1, const ProcessItem item2) {if (item1.packetLen == item2.packetLen) {return item1.processId < item2.processId;}return item1.packetLen > item2.packetLen;});// 5. 只输出procItemVecTemp中排名topNum的进程pid列表int processCnt = topNum;for (auto procIter = procItemVecTemp.begin(); procIter != procItemVecTemp.end() && processCnt > 0; procIter++) {pidList.push_back(procIter->processId);if (processCnt-- <= 0) {break;}}return pidList;
}

main.cpp

#include <iostream>
#include "NetWorkSystem.h"void PrintVector(std::vector<int> nums)
{std::cout << "[";for (auto iter = nums.begin(); iter != nums.end(); iter++) {if (iter != nums.end() - 1) {std::cout << *iter << ","} else {std::cout << *iter;}}std::cout << "]" << std::endl;
}void NetWorkSystem_test_001()
{std::vector<int> pidListResult = {};NetWorkSystem sys;	// 创建一个系统变量sys.BindPort(12345, 80);sys.BindPort(67890, 3306);sys.BindPort(12345, 8080);std::cout << "--------------- 111 start ----------------------------" << std::endl;pidListResult = sys.statTopNum(2);		// 由于当前进程只做端口绑定,还未接收到数据,所以返回空列表 []PrintVector(pidListResult);std::cout << "--------------- 111 end ----------------------------" << std::endl;sys.RecvNetPacketData(80, 100);	// 端口80上接收到100字节的网络数据,此时进程12345的总数据接收长度为100sys.RecvNetPacketData(3306, 300); // 端口3306上接收到300字节的网络数据,此时进程67890的总数据接收长度为300std::cout << "--------------- 222 start ----------------------------" << std::endl;pidListResult = sys.statTopNum(1);	// 由于此时进程67890的总长度为300,大于进程12345的总数据接收长度100,所以返回[67890]PrintVector(pidListResult);std::cout << "--------------- 222 end ----------------------------" << std::endl;sys.RecvNetPacketData(80, 200); // 123456 -> 300, 67890 -> 300sys.BindPort(34567, 3306); // falsesys.BindPort(34567, 21);sys.RecvNetPacketData(21, 400); // 34567 -> 400,此时123456 -> 300, 67890 -> 300std::cout << "--------------- 333 start ----------------------------" << std::endl;pidListResult = sys.statTopNum(5);  // [34567, 123456, 67890]PrintVector(pidListResult);std::cout << "--------------- 333 end ----------------------------" << std::endl;sys.UnBindPort(21);std::cout << "--------------- 444 start ----------------------------" << std::endl;pidListResult = sys.statTopNum(1); // [34567]PrintVector(pidListResult);std::cout << "--------------- 444 end ----------------------------" << std::endl;
}int main(int argc, char* argv[])
{NetWorkSystem_test_001();
}

代码运行结果如下图所示:
代码运行结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/208958.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ros2/ros安装ros-dep||rosdep init错误

第一个错误的做法&#xff1a; sudo apt-get install python3-pip sudo pip3 install 6-rosdep sudo 6-rosdep 如果使用上述代码将会摧毁整个系统&#xff0c;不重装系统反正我是搞不定啊&#xff0c;因为我不知道那个写软件的人到底做了什么。因为这个我安装的版本是humble&…

AlexNet 阅读笔记

“ImageNet Classification with Deep Convolutional Neural Networks” (Krizhevsky 等, 2012, p. 1) 使用深度卷积神经网络进行 ImageNet 分类 3公式&#xff0c;26个引用&#xff0c;4张图片&#xff0c;2个简单表格 Abstract 我们训练了一个大型深度卷积神经网络&#…

Leetcode刷题详解——环绕字符串中唯一的子字符串

1. 题目链接&#xff1a;467. 环绕字符串中唯一的子字符串 2. 题目描述&#xff1a; 定义字符串 base 为一个 "abcdefghijklmnopqrstuvwxyz" 无限环绕的字符串&#xff0c;所以 base 看起来是这样的&#xff1a; "...zabcdefghijklmnopqrstuvwxyzabcdefghijklm…

卷积之后通道数为什么变了

通道数增多与卷积之后得到的图像特征数量有关 卷积层的作用本来就是把输入中的特征分离出来变成新的 feature map&#xff0c;每一个输出通道就是一个卷积操作提取出来的一种特征。在此过程中ReLU激活起到过滤的作用&#xff0c;把负相关的特征点去掉&#xff0c;把正相关的留…

C++:vector增删查改模拟实现

C:vector增删查改模拟实现 前言一、迭代器1.1 非const迭代器&#xff1a;begin()、end()1.2 const迭代器&#xff1a;begin()、end() 二、构造函数、拷贝构造函数、赋值重载、析构函数模拟实现2.1 构造函数2.1.1 无参构造2.1.2 迭代器区间构造2.1.3 n个值构造 2.2 拷贝构造2.3 …

vue路由导航守卫(全局守卫、路由独享守卫、组件内守卫)

目录 一、什么是Vue路由导航守卫&#xff1f; 二、全局守卫 1、beforeEach 下面是一个beforeEach的示例代码&#xff1a; 2、beforeResolve 下面是一个beforeResolve的示例代码&#xff1a; 3、afterEach 下面是一个afterEach的示例代码&#xff1a; 三、路由独享守卫…

Shell - 学习笔记 - 1.14 - 如何编写自己的Shell配置文件(配置脚本)?

第1章 Shell基础(开胃菜) 14 - 如何编写自己的Shell配置文件(配置脚本)? 学习了《Shell配置文件的加载》一节,读者应该知道 Shell 在登录和非登录时都会加载哪些配置文件了。对于普通用户来说,也许 ~/.bashrc 才是最重要的文件,因为不管是否登录都会加载该文件。 我们…

【数据处理】NumPy数组的合并操作,如何将numpy数组进行合并?

&#xff0c;NumPy中的合并操作是指将两个或多个数组合并成一个数组的操作。这种操作可以通过不同的函数来实现。 一、横向合并&#xff08;水平合并&#xff09; 横向合并是指将两个具有相同行数的数组按列方向合并成一个数组的操作。在NumPy中&#xff0c;可以使用hstack()…

044:vue中引用json数据的方法

第044个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

多相Buck的工作原理

什么是多相Buck电源&#xff1f; 多相电源控制器是一种通过同时控制多个电源相位的设备&#xff0c;以提供稳定的电力供应。相位是指电源中的电流和电压波形。多相控制器的设计旨在最大程度地减小电力转换系统的纹波&#xff0c;并提高整体能效。它通常包含一系列的功率级联&a…

我的创作纪念日1024天纪念

机缘 经历的1024天&#xff0c;突然有一种惊奇&#xff0c;日子一天天过&#xff0c;有种恍惚的感觉 收获 从最开始的随笔&#xff0c;慢慢向着笔记总结转变&#xff0c;不经意间积累了好多 憧憬 虽不知最终会怎样发展&#xff0c;但坚持与向前是一定的&#xff0c;未来一…

结构化布线系统

满足下列需求&#xff1a; 1.标准化&#xff1a;国际、国家标准。 2.实用性&#xff1a;针对实际应用的需要和特点来建设系统。 3.先进性&#xff1a;采用国际最新技术。5-10年内技术不落后。 4.开放性&#xff1a;整个系统的开放性。 5.结构化、层次化&#xff1a;易于管理和维…

Matplotlib数据可视化

绘图基础语法 &#xff11; 创建画布并且创建子图 首先创建一个空白的画布&#xff0c;并且可以将画布分为几个部分&#xff0c;这样就可以在同一附图上绘制多个图像。 plt.figure 创建一个空白画布&#xff0c;可以指定画布大小、像素 figure.add_subplot 创建并且选中子…

docker镜像、容器管理与迁移

镜像管理 搜索镜像&#xff1a; 这种方法只能用于官方镜像库 搜索基于 centos 操作系统的镜像 # docker search centos 按星级搜索镜像&#xff1a; 查找 star 数至少为 100 的镜像&#xff0c;默认不加 s 选项找出所有相关 ubuntu 镜像&#xff1a; …

【web安全】文件读取与下载漏洞

前言 菜某整理仅供学习&#xff0c;有误请赐教。 概念 个人理解&#xff1a;就是我们下载一个文件会传入一个参数&#xff0c;但是我们可以修改参数&#xff0c;让他下载其他的文件。因为是下载文件&#xff0c;所以我们可以看到文件里面的源码&#xff0c;内容。 文件读取…

Python嗅探和解析网络数据包

网络工具解释 Scapy是Python2和Python3都支持的库。 它用于与网络上的数据包进行交互。 它具有多种功能&#xff0c;通过这些功能我们可以轻松伪造和操纵数据包。 通过 scapy 模块&#xff0c;我们可以创建不同的网络工具&#xff0c;如 ARP Spoofer、网络扫描仪、数据包转储器…

swiftUi——颜色

在SwiftUI中&#xff0c;您可以使用Color结构来表示颜色。Color可以直接使用预定义的颜色&#xff0c;例如.red、.blue、.green等&#xff0c;也可以使用自定义的RGB值、十六进制颜色代码或者系统提供的颜色。 1. 预定义颜色 Text("预定义颜色").foregroundColor(.…

Swing程序设计(9)复选框,下拉框

文章目录 前言一、复选框二、下拉框总结 前言 该篇文章简单介绍了Java中Swing组件里的复选框组件、列表框组件、下拉框组件&#xff0c;这些在系统中都是常用的组件。 一、复选框 复选框&#xff08;JCheckBox&#xff09;在Swing组件中的使用也非常广泛&#xff0c;一个方形方…

Albumentations(Augmentation Transformations)

Albumentations&#xff08;Augmentation Transformations&#xff09; Albumentations&#xff08;Augmentation Transformations&#xff09;是一个用于图像数据增强&#xff08;数据增广&#xff09;的Python包。它提供了丰富的图像增强技术&#xff0c;用于训练机器学习模…

hadoop安装与配置-shell脚本一键安装配置(集群版)

文章目录 前言一、安装准备1. 搭建集群 二、使用shell脚本一键安装1. 复制脚本2. 增加执行权限3. 分发脚本4. 执行脚本5. 加载用户环境变量 三、启动与停止1. 启动/停止hadoop集群(1) 复制hadoop集群启动脚本(2) 增加执行权限(3) 启动hadoop集群(4) 停止hadoop集群(5) 重启hado…