一.協議
協議是一種 "約定". socket api 的接口, 在讀寫數據時, 都是按 "字符串" 的方式來發送接 收的. 如果我們要傳輸一些 "結構化的數據" 怎么辦呢? 其實,協議就是雙方約定好的結構化的數據
像下面,兩端都知道數據結構data的結構,我們可以直接傳data數據的二進制嗎?
理論可以,但如果兩邊平臺不同,不兼容等導致出錯。
eg.大小端問題 64位指針大小8 32位指針大小4
所以我們一般不通過二進制協議進行傳輸
所以我們一個怎么處理更復雜的、結構化的數據的傳輸呢?
二.序列化和反序列化
1.什么是序列化 反序列化
比如說我們傳一個結構體data,里面包含 int x,char oper ,int y。
我們不要一個一個傳,可以把成員元素整合成一個字符串,再傳。這個就是序列化
但當我們獲取到了這個字符串,怎么獲取到里面包含的消息呢?
我們可以自己進行規定,每個元素間用空格進行隔開,依次進行獲取。
根據制定的規則進行解包,獲取元素,就是反序列化。
序列化: 你將結構體的各個成員轉換成某種格式的字符串,以便傳輸或存儲。例如,使用空格分隔成員。
反序列化: 接收端根據預定的規則解析字符串,并將其恢復為原始數據結構(結構體、對象等)。
2.JSON 序列化
Jsoncpp 是一個用于處理 JSON 數據的 C++ 庫。它提供了將 JSON 數據序列化為字
符串以及從字符串反序列化為 C++ 數據結構的功能。Jsoncpp 是開源的,廣泛用于各
種需要處理 JSON 數據的 C++ 項目中。
序列化方法:
1.使用 Json::Value 的 toStyledString 序列化:
優點:將 Json::Value 對象直接轉換為格式化的 JSON 字符串
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";std::string s = root.toStyledString();std::cout << s << std::endl;return 0;
}
$ . / test.exe
{
"name" : "joe",
"sex" : "男"
}
2.使用 Json::StreamWriter:
優點:提供了更多的定制選項,如縮進、換行符等。
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";Json::StreamWriterBuilder wbuilder; // StreamWriter 的工廠std::unique_ptr<Json::StreamWriter> writer(wbuilder.newStreamWriter());std::stringstream ss;writer->write(root, &ss);std::cout << ss.str() << std::endl;return 0;
}
$ . / test.exe
{
"name" : "joe",
"sex" : "男"
}
3.使用 Json::FastWriter:
比 StyledWriter 更快,因為它不添加額外的空格和換行符。
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";Json::FastWriter writer;std::string s = writer.write(root);std::cout << s << std::endl;return 0;
}
$ . / test.exe
{ "name":"joe","sex" : "男" }#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";// Json::FastWriter writer;Json::StyledWriter writer;std::string s = writer.write(root);std::cout << s << std::endl;return 0;
}
$ . / test.exe
{
"name" : "joe",
"sex" : "男"
}
反序列化方法:
使用 Json::Reader:
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main() {// JSON 字符串std::string json_string = "{\"name\":\"張三\",\"age\":30, \"city\":\"北京\"}";// 解析 JSON 字符串Json::Reader reader;Json::Value root;// 從字符串中讀取 JSON 數據 序列化的字符串 進行反序列bool parsingSuccessful = reader.parse(json_string,root);if (!parsingSuccessful) {// 解析失敗,輸出錯誤信息std::cout << "Failed to parse JSON: " <<reader.getFormattedErrorMessages() << std::endl;return 1;}// 訪問 JSON 數據std::string name = root["name"].asString();int age = root["age"].asInt();std::string city = root["city"].asString();// 輸出結果std::cout << "Name: " << name << std::endl;std::cout << "Age: " << age << std::endl;std::cout << "City: " << city << std::endl;return 0;
}
$ . / test.exe
Name : 張三
Age : 30
City : 北京
訪問 JSON 數據,int age = root["age"].asInt(); 如果是char c=root["age"].asInt(),也是用.asInt,沒有.aschar。因為char類型就是整數
三.重新理解 read、write、recv、send 和 tcp 為什么支持全雙工
全雙工:通信雙方可以同時進行雙向數據傳輸。也就是說,發送和接收可以在同一時刻發生。
在任何一臺主機上,TCP 連接既有發送緩沖區,又有接受緩沖區,所以,在內核
中,可以在發消息的同時,也可以收消息,即全雙工。
這就是為什么一個 tcp sockfd 讀寫都是它的原因。
實際數據什么時候發,發多少,出錯了怎么辦,由 TCP 控制,所以 TCP 叫做傳輸控制協議。
在系統內部都有大量的報文,一部分是從發送緩沖區進行傳輸,另一部分是向接收緩沖區進行傳輸。如何進行管理,先描述,再組織。每個報頭間用鏈表進行連接。
在socket結構中就包含兩個隊列,分別是發送隊列,接收隊列,對這兩部份報頭進行管理。
這兩個隊列分別用于存儲發送和接收的報文頭。每個隊列可以通過鏈表來管理報文的順序。
發送緩沖區和發送隊列:發送緩沖區的內容一般是按照隊列的順序被處理的。數據首先進入發送隊列(即緩沖區),然后按順序被發送到遠程主機。在TCP協議中,發送隊列不僅僅是一個簡單的隊列,還會涉及到流量控制、擁塞控制等機制,確保發送方的速度不會超過接收方的處理能力。
接收緩沖區和接收隊列:當數據到達本地系統時,接收隊列會存放這些數據。接收隊列的順序和緩沖區管理相結合,確保應用程序能夠以正確的順序讀取到接收到的數據。
四.ps axj 當前系統的進程信息
PID PGID SID TTY TIME CMD123 123 123 tty1 00:00:01 bash234 123 123 tty1 00:00:00 ps567 567 567 ? 00:00:10 my_process
PID(進程ID):進程的唯一標識符。
PGID(進程組ID):多個進程共享的組ID。(處在同一組的進程PGID相同,一般第一個是組長)
SID(會話ID):進程所屬的會話ID。
TTY(終端):進程關聯的終端(如果有)。
TIME(CPU時間):進程使用的CPU時間。
CMD:啟動該進程的命令。
五.守護進程
1.前臺進程 后臺進程
像在命令行中直接sleep 100 啟動的就是前臺進程,如果在后面加& ,sleep 100 &就把這個進程在后臺啟動。
啟動一個前臺進程(例如,運行 sleep 100),該進程會占用終端。前臺進程會占據整個終端的輸入/輸出流,因此你無法在命令行中執行其他命令(如 ls)直到這個前臺進程完成。
2.fd 任務號 后臺進程->前臺進程
當我們啟動了一個后臺進程,怎么再把它放到前臺呢?
fd+任務號
使用 jobs 命令可以列出當前 shell 會話中所有的后臺任務,并顯示每個任務的任務號(Job ID)和當前狀態。
3.Ctrl+Z bg 任務號?前臺進程->后臺進程
1.ctrl+z 掛起前臺進程,變為暫停狀態。
2.bg+任務號 將掛起的進程放到后臺,不能直接把前臺進程放在后臺。
4.后臺進程和守護進程
當我們進行登錄,系統會創建一個會話,這個會話中bash進程當作前臺進程,與終端直接連接,建立后臺進程不會直接接收用戶的輸入,也不會直接向終端輸出數據,一般不輸入輸出重定向到其它文件。
如果我們退出登錄,該會話的前臺進程 終端都會關閉,里面的后臺進程也會受到影響。
有沒有辦法退出時讓該后端進程不受影響?
把該后端進程放到一個新的會話中,并且持續運行,不與任何終端關聯,就是守護進程。
如何創建守護進程?
手動:
1.調用 fork():首先,進程調用 fork() 創建一個子進程,父進程退出,確保子進程不成為孤兒進程。
2.調用 setsid():子進程通過 setsid() 調用成為新會話的會話領導者,這樣它就不再與控制終端關聯。(不是是進程組的組長調用它。fork()子進程調用)
3.重定向輸入輸出:守護進程會將輸入輸出重定向到日志文件或 /dev/null (文件黑洞 不會保存數據),以確保不會干擾終端。
4.持續運行:守護進程通常進入無限循環,保持持續運行,處理后臺任務。#pragma once#include <iostream> #include <cstdlib> #include <signal.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h>#define ROOT "/" #define devnull "/dev/null"void Daemon(bool ischdir, bool isclose) {// 1. 守護進程一般要屏蔽到特定的異常信號signal(SIGCHLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);// 2. 成為非組長if (fork() > 0)exit(0);// 3. 建立新會話setsid();// 4. 每一個進程都有自己的CWD,是否將當前進程的CWD更改成為 / 根目錄if (ischdir)chdir(ROOT);// 5. 已經變成守護進程啦,不需要和用戶的輸入輸出,錯誤進行關聯了if (isclose){::close(0);::close(1);::close(2);}else{int fd = ::open(devnull, O_WRONLY);if (fd > 0){// 各種重定向dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);close(fd);}} }
daemon:
int daemon(int nochdir, int noclose);
nochdir:如果為 1,守護進程將不改變當前工作目錄;如果為 0,守護進程會將當前工作目錄更改為根目錄(/),以避免占用一個文件系統的目錄。
noclose:如果為 1,守護進程不會關閉文件描述符。如果為 0,守護進程會關閉標準輸入、標準輸出和標準錯誤輸出文件描述符。#include <stdio.h> #include <stdlib.h> #include <unistd.h>int main() {// 將進程轉變為守護進程if (daemon(0, 0) == -1) {perror("daemon");exit(1);}// 守護進程的工作while (1) {// 這里可以添加守護進程執行的任務sleep(10); // 每10秒執行一次任務}return 0; }
在原會話的后臺進程會受到終端信號干擾、父進程退出以及會話管理的影響。
通過使用
setsid()
來創建新的會話,守護進程能夠脫離終端和父進程的控制,確保在系統后臺獨立穩定地運行,避免受到干擾。
守護進程處于新的會話:
1.確保進程不被終端信號干擾
在原有會話中運行的進程通常會收到一些終端信號,尤其是當用戶退出時,例如
SIGHUP
信號,通常會導致進程終止。守護進程必須避免這些信號的干擾,才能保證其在系統后臺長時間穩定運行。調用
setsid()
后,守護進程會脫離原會話,并成為一個新的會話的會話領導者,控制終端不再影響它。這確保了守護進程不會因為終端的關閉或會話的結束而被中斷。2.防止守護進程成為孤兒進程
在操作系統中,進程分為父進程和子進程。如果一個進程的父進程終止,該進程會被操作系統的 "init" 進程收養,成為孤兒進程。然而,守護進程希望在系統后臺長期運行,它不希望被父進程或任何其他進程“收養”,而是希望有完全的獨立性。
通過調用
setsid()
,守護進程脫離了原會話,成為一個新的會話的會話領導者,這樣它不再依賴任何父進程,確保它不會成為孤兒進程,也不會因為父進程的退出而終止。