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

news2024/11/25 20:42:14

一、问题描述

最近做了一道简单的系统设计题,大概描述如下:
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未被当前系统中的进程占用,则返回false
	bool 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未被当前系统中的进程占用,则返回false
	bool 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已被其他进程占用,则不处理,直接返回false
	for (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被其他进程占用,则从对应进程的端口集合中解绑,直接返回true
	for (auto procIter : procItemVec) {
		auto portIter = procIter.ports.find(port);
		// 找到对应的端口port
		if (portIter != procIter.ports.end()) {
			// 将该端口中对应进程的端口集合中移除
			procIter.ports.erase(port);
			return true;
		}
	}
	// 如果没找到该端口,则返回false
	return 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的总数据接收长度为100
	sys.RecvNetPacketData(3306, 300); // 端口3306上接收到300字节的网络数据,此时进程67890的总数据接收长度为300
	std::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 -> 300
	sys.BindPort(34567, 3306); // false
	sys.BindPort(34567, 21);
	sys.RecvNetPacketData(21, 400); // 34567 -> 400,此时123456 -> 300, 67890 -> 300

	std::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.coloradmin.cn/o/1296684.html

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

相关文章

二维码智慧门牌管理系统升级解决方案:数字鉴权

文章目录 前言一、数字鉴权的核心机制二、数字鉴权的意义和应用 前言 随着科技的飞速发展&#xff0c;我们的生活逐渐进入数字化时代。在这个数字化的过程中&#xff0c;数据的安全性和门牌信息的保障变得至关重要。今天&#xff0c;我们要介绍的是二维码智慧门牌管理系统升级…

JavaWeb(十一)

一、会话跟踪技术的概述 1.1、会话的概念 用户打开浏览器&#xff0c;访问web服务器的资源&#xff0c;会话建立&#xff0c;直到有一方断开连接&#xff0c;会话结束。在一次会话中可以包含多次请求和响应。 从浏览器发出请求到服务端响应数据给前端之后&#xff0c;一次会话…

工业主板和消费主板的区别

消费类主板是用于家庭和个人计算机的批量生产的通用主板。另一方面&#xff0c;工业主板则用于工厂、制造设备、医疗设备、公共基础设施以及其他重视可靠性的场所。 工业主板的特点 工业主板有以下四个主要特点。 长期稳定供应 高可靠性 耐环境性 可定制 工业应用需要主…

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

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

在vscode下将ipynb文件转成markdown(.md文件)的方法

在vscode下将ipynb文件转成markdown&#xff08;.md文件&#xff09;的方法 写在最前面安装nbconvert工具vscode界面 or cmd终端基本命令将ipynb文件转换成md文件 总结 写在最前面 VSCode作为一款强大的代码编辑器&#xff0c;提供了广泛的功能。它支持多种文件格式的编辑和查…

387.字符串中的第一个唯一字符 —> `size()`

解答&#xff1a; int firstUniqChar(string s) {int size s.size();// char count[26] { 0 };// error.1int count[26] { 0 };// for (int i 0; i < s.size() - 1; i) // error.2for (int i 0; i < size; i){count[s[i] - a] 1;}for (int i 0; i < size; i){…

promethesu告警规则配置,alertmanager通过webhook通知

文章目录 前言一、promethesu告警二、告警配置编写rule文件prometheus配置prometheus产生告警 三、告警通知prometheus 配置 alertmanageralertmanager 配置 webhook通知编写接口接收 webhook 总结 前言 如果没有学习过prometheus的基础和监控的同学&#xff0c;可以先过一遍这…

C/C++端口复用SO_REUSEADDR(setsockopt参数),test ok

端口复用最常用的用途应该是防止服务器重启时之前绑定的端口还未释放或者程序突然退出而系统没有释放端口。这种情况下如果设定了端口复用&#xff0c;则新启动的服务器进程可以直接绑定端口。如果没有设定端口复用&#xff0c;绑定会失败&#xff0c;提示ADDR已经在使用中——…

StoneDB-8.0-V2.2.0 企业版正式发布!性能优化,稳定性提升,持续公测中!

​ 11月&#xff0c;StoneDB 新版本如期而至&#xff0c;这一个月来我们的研发同学加班加点&#xff0c;持续迭代&#xff1a;在 2.2.0 版本中&#xff0c;我们针对用户提出的需求和做出了重量级更新&#xff0c;修复了一些已知和用户反馈的 Bug&#xff0c;同时对部分代码进行…

docker学习(四、修改容器创建新的镜像推送到云上)

镜像是只读的&#xff0c;容器是可编辑的。Docker镜像是分层的&#xff0c;支持通过扩展镜像&#xff0c;创建新的镜像。 学到这里感觉docker跟git很想~~ 通过docker commit将修改的容器做成新的镜像 # 将容器做成新的镜像 docker commit -m"提交备注" -a"作…

结构化布线系统

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

Nginx+Promtail+Loki+Grafana 升级ELK强大工具

最近客户有个新需求,就是想查看网站的访问情况,由于网站没有做google的统计和百度的统计,所以访问情况,只能通过日志查看,通过脚本的形式给客户导出也不太实际,给客户写个简单的页面,咱也做不到 成熟的日志解决方案,那就是ELK,还有现在比较火的Loki,(当然还有很多其…

WPF使用WebBrowser报脚本错误问题处理

前言 WPF使用WebBrowser报脚本错误问题处理,我们都知道WPF自带的WebBrowser都用的IE内核,但是在特殊的条件下我们还需要用到它,比如展示纯html简单的页面。再展示主流页面的时候比如用到Jquery高级库或者VUE等当前主流站点时经常就会报JS脚本错误,在Winform里面我们一句代…

进程、线程、线程池状态

线程几种状态和状态转换 进程主要写明三种基本状态&#xff1a; 线程池的几种状态&#xff1a;

这是最后的战役了

不变因子 初等因子 行列式因子 smith标准型 酉矩阵 H-阵等等 A H A A^H A AHA 就是 H-阵 正定H阵的性质 若 A A A 为正定的H-阵. 存在可逆矩阵 Q Q Q&#xff0c; 使得 A Q H Q AQ^H Q AQHQ.存在 P P P, 使得 P H A P I P^HAPI PHAPI.A的特征值大于0. Q − 1 A Q Q^{…

生成fip.bin在Milkv-duo上跑rtthread的相关尝试,及其问题分析

前言 &#xff08;1&#xff09;PLCT实验室实习生长期招聘&#xff1a;招聘信息链接 &#xff08;2&#xff09;本来是想在Milkv-duo上跑rtthread的&#xff0c;做了很多努力&#xff0c;一直没有结果。虽然不知道最终能不能成功做出来&#xff0c;还是把自己的相关努力分享出来…

RocketMQ安装和使用

RocketMQ快速入门 下载RocketMQ 下载地址 环境要求 Linux64位系统 JDK1.8(64位) 安装RocketMQ 解压 unzip rocketmq-all-4.4.0-bin-release.zip启动RocketMQ 启动NameServer # 1.启动NameServer nohup sh bin/mqnamesrv & # 2.查看启动日志 tail -f ~/logs/rocke…

用C语言实现队列的顺序结构

用C语言实现队列的初始化、队列的判空操作、入队操作、出队运算、取队头元素运算、顺序打印队列。 #include<stdio.h> #define QueueSize 100 typedef char ElemType; typedef struct//队列结构体 {ElemType data[QueueSize];//保存队中元素int front, rear;//队头和队尾…

hook其他调试技巧

输出堆栈信息 通过 android.util.Log 输出当前线程的堆栈跟踪信息。 function showStacks() {Java.perform(function () {console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new() )); }) } 可以在需要的…

判断css文字发生了截断,增加悬浮提示

示例&#xff1a; 固定显示宽度&#xff0c;溢出显示...&#xff0c;利用了css的属性&#xff0c;想要实现成下面这样&#xff1a; 针对溢出的文字&#xff0c;hover显示全部。 提示很好加&#xff0c;使用tooltip组件就行了&#xff0c;难点是如何判断是否发生了文字溢出。…