一、问题描述
最近做了一道简单的系统设计题,大概描述如下:
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 algorithm
对std::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();
}
代码运行结果如下图所示: