一、問題描述
最近做了一道簡單的系統設計題,大概描述如下:
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 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未被當前系統中的進程占用,則返回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();
}
代碼運行結果如下圖所示: