📖 目錄
- 📌 前言
- 🔍 需求分析
- 🤔 我們需要解決哪些問題?
- 🎯 方案設計
- 💡 服務器架構
- 🚀 什么是協議?為什么要設計協議?
- 📌 結構化數據的傳輸問題
- 📌 協議定制:如何讓服務器正確解析數據?
- ? TCP 直接傳輸的問題
- ? 解決方案:在數據前加上長度信息
- 📌 序列化與反序列化:如何讓數據更容易解析?
- 📌 什么是序列化?
- 📌 選擇合適的序列化方式
- 📜 通信協議設計
- 🔧 核心代碼解析
- 🔹 協議封裝
- 🔹 解析數據
- 🔹 服務器處理請求
- 🔹 網絡通信的實現
- 🔹 服務器并發處理模型
- 🔹 客戶端請求的發送與接收
- 🚀 還有哪些可以改進的地方?
- 🛠 采用更高效的并發模型
- 🔒 增加安全性
- 📡 支持更豐富的計算功能
- 📈 增強日志與監控
- 🌍 讓它支持更多設備(跨平臺 & Web 訪問)
📌 1. 前言
計算機網絡的核心就是通信。遠程計算就是一個很好的例子:
- 本地客戶端 負責發送計算請求(如
10 + 20
) - 遠程服務器 負責解析、計算,并返回結果(如
30
)
這個項目的目標是:實現一個基于 TCP 的遠程計算服務,讓多個客戶端同時發送計算請求,服務器解析并返回結果。
🔍 2. 需求分析
🤔 我們需要解決哪些問題?
1. 計算器的核心功能
- 支持基本的
+ - * / %
運算 - 遠程計算,客戶端發送計算請求,服務器計算并返回結果
- 處理異常情況(如除零)
2. 網絡通信的挑戰
- 如何保證數據完整性?(TCP 是流式傳輸,可能會粘包)
- 如何解析數據?(客戶端發送的
10 + 20
,服務器怎么拆解?) - 如何支持多個客戶端?(服務器要能并發處理請求)
🎯 3. 方案設計
💡 服務器架構
[ 客戶端 ] <--TCP--> [ 服務器 ]| ||-- 用戶輸入 |-- 解析請求|-- 發送計算式 |-- 計算結果|-- 顯示運算結果 |-- 發送結果
🚀 4. 什么是協議?為什么要設計協議?
在計算機網絡中,不同的設備想要互相通信,就必須說同一種語言,否則就會雞同鴨講,無法理解對方的信息。而這種“語言”,在網絡編程中就被稱為協議(Protocol)。
這篇文章,我們就從協議的概念出發,一步步拆解如何基于 TCP 實現一個“遠程計算器”服務,讓客戶端通過網絡發送計算請求,服務器收到請求后計算結果并返回給客戶端。
📌 5. 協議定制:如何讓服務器正確解析數據?
? TCP 直接傳輸的問題
TCP 是面向流的協議,它不會幫我們劃分數據邊界,導致以下問題:
- 粘包問題(多個小數據包合并)
- 拆包問題(一個大數據包被拆成多部分)
? 解決方案:在數據前加上長度信息
在數據包前加上 固定長度的頭部,存儲數據長度:
[數據長度][計算表達式]
📌 6. 序列化與反序列化:如何讓數據更容易解析?
📌 什么是序列化?
序列化(Serialization)就是將數據轉換為可傳輸的格式,然后在接收端反序列化(Deserialization)回原始格式。
📌 選擇合適的序列化方式
這里我們采用 JSON,因為它易讀易解析。
📜 通信協議設計
客戶端發送的 JSON 請求格式如下:
{"expr": "10+20"
}
服務器返回的 JSON 結果格式如下:
{"result": 30
}
🔧 7. 核心代碼解析
🔹 協議封裝
std::string encode_request(const std::string& expr)
{json j;j["expr"] = expr;return j.dump();
}
🔹 解析數據
std::string decode_response(const std::string& response)
{json j = json::parse(response);return j["result"].get<int>();
}
🔹 服務器處理請求
std::string process_request(const std::string& request)
{json j = json::parse(request);std::string expr = j["expr"];int result = eval(expr); // 計算表達式json response;response["result"] = result;return response.dump();
}
🔹 網絡通信的實現
void handle_client(int client_sock)
{char buffer[1024] = {0};read(client_sock, buffer, 1024);std::string response = process_request(buffer);send(client_sock, response.c_str(), response.length(), 0);close(client_sock);
}
🔹 服務器并發處理模型
void start_server()
{int server_fd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in address;bind(server_fd, (struct sockaddr*)&address, sizeof(address));listen(server_fd, 5);while (true){int client_sock = accept(server_fd, NULL, NULL);std::thread(handle_client, client_sock).detach();}
}
🔹 客戶端請求的發送與接收
void send_request(const std::string& expr)
{int sock = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in serv_addr;connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));std::string request = encode_request(expr);send(sock, request.c_str(), request.length(), 0);char buffer[1024] = {0};read(sock, buffer, 1024);std::cout << "Server response: " << decode_response(buffer) << std::endl;close(sock);
}
最終效果
🚀 8. 還有哪些可以改進的地方?
雖然我們的遠程計算器已經可以正常工作,但仍然有許多優化空間。下面列出了一些可以改進的方向,并給出大致的思路:
🛠 1. 采用更高效的并發模型
目前服務器采用多進程方式處理多個客戶端,但每次連接都會 fork()
一個子進程,進程創建和回收的開銷較大。如果連接數增加,性能可能會下降。
? 改進方向:
- 線程池:可以使用
std::thread
+ 線程池,避免頻繁創建/銷毀進程,提高并發能力。 epoll
/select
:基于 I/O 復用的方式,實現單進程管理多個連接,減少資源占用。- 協程方案:使用
libco
或Boost.Asio
實現高并發的計算服務。
🔒 2. 增加安全性
目前客戶端可以隨意輸入數據,如果用戶輸入了 "100 / 0"
,就會導致除零異常。此外,服務器目前沒有身份驗證機制,任何人都可以連接并發送計算請求。
? 改進方向:
-
輸入校驗
:在解析
x op y
之前,檢查運算是否合法,比如:
if (op == '/' && y == 0) {return "ERROR: Division by zero"; }
-
身份驗證:可以添加 用戶名 + 密碼 認證,確保只有授權用戶才能訪問計算服務。
-
SQL 注入防護(如果涉及數據庫)
📡 3. 支持更豐富的計算功能
目前計算器只支持 + - * / %
,如果想讓它更強大,可以擴展為數學計算引擎,支持 sin()、cos()、log()、pow() 等函數。
? 改進方向:
- 解析數學表達式:可以用
Shunting Yard Algorithm
解析復雜表達式,如3 + 5 * (2 - 8) / sin(30)
。 - 結合開源數學庫:如
ExprTk
解析數學表達式,甚至支持微積分計算。
📈 4. 增強日志與監控
目前服務器沒有日志系統,如果某個請求失敗,我們很難知道發生了什么問題。
? 改進方向:
- 日志系統:使用
log4cpp
或spdlog
記錄服務器運行狀態,方便排查問題。 - 監控系統:可以結合
Prometheus + Grafana
監控請求數量、CPU 使用率等數據,確保服務器穩定運行。
🌍 5. 讓它支持更多設備(跨平臺 & Web 訪問)
目前我們的計算器是 C++ 客戶端 + C++ 服務器,但如果想要讓網頁、手機、Python 腳本也能調用計算服務,我們可以提供一個HTTP API 或 WebSocket 版本。
? 改進方向:
-
RESTful API
:讓客戶端用
curl
或
Python
直接調用:
GET /calculate?expr=10+2 HTTP/1.1
-
WebSocket 支持:讓前端網頁也能實時計算。