Linux之網絡部分-應用層自定義協議與序列化

一、應用層

1.1、理解協議

協議是一種 "約定". socket api 的接口, 在讀寫數據時, 都是按 "字符串" 的方式來發送接收的。如果我們要傳輸一些 "結構化的數據" 怎么辦呢?

其實,協議就是雙方約定好的結構化的數據。

1.2、網絡版計算器

例如, 我們需要實現一個服務器版的加法器. 我們需要客戶端把要計算的兩個加數發過去, 然后由服務器進行計算, 最后再把結果返回給客戶端。

約定方案一:

  • 客戶端發送一個形如"1+1"的字符串;
  • 這個字符串中有兩個操作數, 都是整形;
  • 兩個數字之間會有一個字符是運算符, 運算符只能是 + ;
  • 數字和運算符之間沒有空格;
  • ......

約定方案二:

  • 定義結構體來表示我們需要交互的信息;
  • 發送數據時將這個結構體按照一個規則轉換成字符串, 接收到數據的時候再按照相同的規則把字符串轉化回結構體;
  • 這個過程叫做 "序列化" 和 "反序列化"。

1.3、序列化 和 反序列化

上面計算機例子中,無論我們采用方案一, 還是方案二, 還是其他的方案, 只要保證, 一端發送時構造的數據,在另一端能夠正確的進行解析, 就是 ok 的。這種約定, 就是應用層協議。

二、重新理解 read、write、recv、send 和 tcp 為什么支持全雙工

  • 在任何一臺主機上,TCP 連接既有發送緩沖區,又有接受緩沖區,所以,在內核中,可以在發消息的同時,也可以收消息,即全雙工。這就是為什么一個 tcp sockfd 讀寫都是它的原因
  • 實際數據什么時候發,發多少,出錯了怎么辦?由 TCP 控制,所以 TCP 叫做傳輸控制協議。

三、自定義實現協議

代碼結構:

Calculate.hpp ????????Makefile ????????Socket.hpp ????????TcpServer.hpp ????????Daemon.hpp?

Protocol.hpp ????????TcpClientMain.cc ????????TcpServerMain.cc

期望的報文格式:

示例代碼鏈接:Linux_blog: Linux博客示例代碼 - Gitee.comhttps://gitee.com/algnmlgb/linux_blog/tree/master/lesson24/NetCal

四、關于流式數據的處理

完整的處理過程應該是:

五、補充

5.1、Jsoncpp

Jsoncpp 是一個用于處理 JSON 數據的 C++ 庫。它提供了將 JSON 數據序列化為字符串以及從字符串反序列化為 C++ 數據結構的功能。Jsoncpp 是開源的,廣泛用于各種需要處理 JSON 數據的 C++ 項目中。

特性:

  • 簡單易用:Jsoncpp 提供了直觀的 API,使得處理 JSON 數據變得簡單。
  • 高性能:Jsoncpp 的性能經過優化,能夠高效地處理大量 JSON 數據。
  • 全面支持:支持 JSON 標準中的所有數據類型,包括對象、數組、字符串、數 字、布爾值和 null。
  • 錯誤處理:在解析 JSON 數據時,Jsoncpp 提供了詳細的錯誤信息和位置,方便開發者調試。

當使用 Jsoncpp 庫進行 JSON 的序列化和反序列化時,確實存在不同的做法和工具類可供選擇。以下是對 Jsoncpp 中序列化和反序列化操作的詳細介紹:

安裝:

C++

ubuntu:sudo apt-get install libjsoncpp-dev

Centos: sudo yum install jsoncpp-devel

序列化:

序列化指的是將數據結構或對象轉換為一種格式,以便在網絡上傳輸或存儲到文件中。Jsoncpp 提供了多種方式進行序列化:

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;
}

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;
}

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;
}
#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;
}

反序列化:

反序列化指的是將序列化后的數據重新轉換為原來的數據結構或對象。Jsoncpp 提供了以下方法進行反序列化:

1.?使用 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;
}

2.?使用 Json::CharReader 的派生類:

  • 在某些情況下,你可能需要更精細地控制解析過程,可以直接使用Json::CharReader 的派生類。
  • 但通常情況下,使用 Json::parseFromStream 或 Json::Reader 的 parse方法就足夠了。

總結:

  • toStyledString、StreamWriter 和 FastWriter 提供了不同的序列化選項, 你可以根據具體需求選擇使用。
  • Json::Reader 和 parseFromStream 函數是 Jsoncpp 中主要的反序列化工具, 它們提供了強大的錯誤處理機制。
  • 在進行序列化和反序列化時,請確保處理所有可能的錯誤情況,并驗證輸入和輸出的有效性。

Json::Value:

Json::Value 是 Jsoncpp 庫中的一個重要類,用于表示和操作 JSON 數據結構。以下是一些常用的 Json::Value 操作列表:

1.?構造函數

  • Json::Value():默認構造函數,創建一個空的 Json::Value 對象。
  • Json::Value(ValueType type, bool allocated = false):根據給定的ValueType(如 nullValue, intValue, stringValue 等)創建一個 Json::Value 對象。

2. 訪問元素

  • Json::Value& operator[](const char* key):通過鍵(字符串)訪問對象 中的元素。如果鍵不存在,則創建一個新的元素。
  • Json::Value& operator[](const std::string& key):同上,但使用std::string 類型的鍵。
  • Json::Value& operator[](ArrayIndex index):通過索引訪問數組中的元素。如果索引超出范圍,則創建一個新的元素。
  • Json::Value& at(const char* key):通過鍵訪問對象中的元素,如果鍵不存在則拋出異常。
  • Json::Value& at(const std::string& key):同上,但使用 std::string類型的鍵。

3. 類型檢查

  • bool isNull():檢查值是否為 null。
  • bool isBool():檢查值是否為布爾類型。
  • bool isInt():檢查值是否為整數類型。
  • bool isInt64():檢查值是否為 64 位整數類型。
  • bool isUInt():檢查值是否為無符號整數類型。
  • bool isUInt64():檢查值是否為 64 位無符號整數類型。
  • bool isIntegral():檢查值是否為整數或可轉換為整數的浮點數。
  • bool isDouble():檢查值是否為雙精度浮點數。
  • bool isNumeric():檢查值是否為數字(整數或浮點數)。
  • bool isString():檢查值是否為字符串。
  • bool isArray():檢查值是否為數組。
  • bool isObject():檢查值是否為對象(即鍵值對的集合)。

4. 賦值與類型轉換

  • Json::Value& operator=(bool value):將布爾值賦給 Json::Value 對象。
  • Json::Value& operator=(int value):將整數賦給 Json::Value 對象。
  • Json::Value& operator=(unsigned int value):將無符號整數賦給Json::Value 對象。
  • Json::Value& operator=(Int64 value):將 64 位整數賦給 Json::Value對象。
  • Json::Value& operator=(UInt64 value):將 64 位無符號整數賦給Json::Value 對象。
  • Json::Value& operator=(double value):將雙精度浮點數賦給Json::Value 對象。
  • Json::Value& operator=(const char* value):將 C 字符串賦給Json::Value 對象。
  • Json::Value& operator=(const std::string& value):將 std::string賦給 Json::Value 對象。

  • bool asBool():將值轉換為布爾類型(如果可能)。
  • int asInt():將值轉換為整數類型(如果可能)。
  • Int64 asInt64():將值轉換為 64 位整數類型(如果可能)。
  • unsigned int asUInt():將值轉換為無符號整數類型(如果可能)。
  • UInt64 asUInt64():將值轉換為 64 位無符號整數類型(如果可能)。
  • double asDouble():將值轉換為雙精度浮點數類型(如果可能)。
  • std::string asString():將值轉換為字符串類型(如果可能)。

5. 數組和對象操作

  • size_t size():返回數組或對象中的元素數量。
  • bool empty():檢查數組或對象是否為空。
  • void resize(ArrayIndex newSize):調整數組的大小。
  • void clear():刪除數組或對象中的所有元素。
  • void append(const Json::Value& value):在數組末尾添加一個新元素。
  • Json::Value& operator[](const char* key, const Json::Value& defaultValue = Json::nullValue):在對象中插入或訪問一個元素,如果鍵不存 在則使用默認值。
  • Json::Value& operator[](const std::string& key, const Json::Value& defaultValue = Json::nullValue):同上,但使用 std::string類型的

六、手寫序列化與反序列化

本質:就是對字符串的處理

示例代碼:

#pragma once#include <iostream>
#include <memory>
#include <jsoncpp/json/json.h>#define SelfDefine 1namespace Protocol
{// 問題// 1. 結構化數據的序列和反序列化// 2. 還要解決用戶區分報文邊界 --- 數據包粘報問題// 總結:// 我們今天定義了幾組協議呢??我們可以同時存在多個協議嗎???可以// "protocol_code\r\nlen\r\nx op y\r\n" : \r\n不屬于報文的一部分,約定const std::string ProtSep = " ";const std::string LineBreakSep = "\r\n";// "len\r\nx op y\r\n" : \r\n不屬于報文的一部分,約定std::string Encode(const std::string &message){std::string len = std::to_string(message.size());std::string package = len + LineBreakSep + message + LineBreakSep;return package;}bool Decode(std::string &package, std::string *message){// 除了解包,我還想判斷報文的完整性, 能否正確處理具有"邊界"的報文auto pos = package.find(LineBreakSep);if (pos == std::string::npos)return false;std::string lens = package.substr(0, pos);int messagelen = std::stoi(lens);int total = lens.size() + messagelen + 2 * LineBreakSep.size();if (package.size() < total)return false;// 至少package內部一定有一個完整的報文了!*message = package.substr(pos + LineBreakSep.size(), messagelen);package.erase(0, total);return true;}class Request{public:Request() : _data_x(0), _data_y(0), _oper(0){}Request(int x, int y, char op) : _data_x(x), _data_y(y), _oper(op){}void Debug(){std::cout << "_data_x: " << _data_x << std::endl;std::cout << "_data_y: " << _data_y << std::endl;std::cout << "_oper: " << _oper << std::endl;}void Inc(){_data_x++;_data_y++;}// 結構化數據->字符串bool Serialize(std::string *out){
#ifdef SelfDefine // 條件編譯*out = std::to_string(_data_x) + ProtSep + _oper + ProtSep + std::to_string(_data_y);return true;
#elseJson::Value root;root["datax"] = _data_x;root["datay"] = _data_y;root["oper"] = _oper;Json::FastWriter writer;*out = writer.write(root);return true;
#endif}bool Deserialize(std::string &in) // "x op y" [){
#ifdef SelfDefineauto left = in.find(ProtSep);if (left == std::string::npos)return false;auto right = in.rfind(ProtSep);if (right == std::string::npos)return false;_data_x = std::stoi(in.substr(0, left));_data_y = std::stoi(in.substr(right + ProtSep.size()));std::string oper = in.substr(left + ProtSep.size(), right - (left + ProtSep.size()));if (oper.size() != 1)return false;_oper = oper[0];return true;
#elseJson::Value root;Json::Reader reader;bool res = reader.parse(in, root);if(res){_data_x = root["datax"].asInt();_data_y = root["datay"].asInt();_oper = root["oper"].asInt();}return res;
#endif}int GetX() { return _data_x; }int GetY() { return _data_y; }char GetOper() { return _oper; }private:// _data_x _oper _data_y// 報文的自描述字段// "len\nx op y\n" : \n不屬于報文的一部分,約定// 很多工作都是在做字符串處理!int _data_x; // 第一個參數int _data_y; // 第二個參數char _oper;  // + - * / %};class Response{public:Response() : _result(0), _code(0){}Response(int result, int code) : _result(result), _code(code){}bool Serialize(std::string *out){
#ifdef SelfDefine*out = std::to_string(_result) + ProtSep + std::to_string(_code);return true;
#elseJson::Value root;root["result"] = _result;root["code"] = _code;Json::FastWriter writer;*out = writer.write(root);return true;
#endif}bool Deserialize(std::string &in) // "_result _code" [){
#ifdef SelfDefineauto pos = in.find(ProtSep);if (pos == std::string::npos)return false;_result = std::stoi(in.substr(0, pos));_code = std::stoi(in.substr(pos + ProtSep.size()));return true;
#elseJson::Value root;Json::Reader reader;bool res = reader.parse(in, root);if(res){_result = root["result"].asInt();_code = root["code"].asInt();}return res;
#endif}void SetResult(int res) { _result = res; }void SetCode(int code) { _code = code; }int GetResult() { return _result; }int GetCode() { return _code; }private:// "len\n_result _code\n"int _result; // 運算結果int _code;   // 運算狀態};// 簡單的工廠模式,建造類設計模式class Factory{public:std::shared_ptr<Request> BuildRequest(){std::shared_ptr<Request> req = std::make_shared<Request>();return req;}std::shared_ptr<Request> BuildRequest(int x, int y, char op){std::shared_ptr<Request> req = std::make_shared<Request>(x, y, op);return req;}std::shared_ptr<Response> BuildResponse(){std::shared_ptr<Response> resp = std::make_shared<Response>();return resp;}std::shared_ptr<Response> BuildResponse(int result, int code){std::shared_ptr<Response> req = std::make_shared<Response>(result, code);return req;}};
}

七、進程間關系與守護進程

7.1、進程組

7.1.1、什么是進程組

之前我們提到了進程的概念, 其實每一個進程除了有一個進程 ID(PID)之外還屬于一 個進程組。進程組是一個或者多個進程的集合, 一個進程組可以包含多個進程。 每一 個進程組也有一個唯一的進程組 ID(PGID), 并且這個 PGID 類似于進程 ID, 同樣是 一個正整數, 可以存放在 pid_t 數據類型中。

C++

$ ps -eo pid,pgid,ppid,comm | grep test

#結果如下

PID ????????PGID ????????PPID ????????COMMAND

2830 ?????2830???????? 2259 ?????????????test

# -e 選項表示 every 的意思, 表示輸出每一個進程信息

# -o 選項以逗號操作符(,)作為定界符, 可以指定要輸出的列

7.1.2、組長進程

每一個進程組都有一個組長進程。 組長進程的 ID 等于其進程 ID。我們可以通過 ps 命令看到組長進程的現象:

Shell

[node@localhost code]$? ps -o pid,pgid,ppid,comm | cat

# 輸出結果

PID ????????PGID ????????PPID ????????COMMAND

2806? ? ? ?2806? ? ? ? ? 2805? ? ? ? ? ? bash

2880? ? ? ?2880? ? ? ? ? 2806? ? ? ? ? ? ps

2881? ? ? ?2880? ? ? ? ? 2806? ? ? ? ? ? cat

從結果上看 ps 進程的 PID 和 PGID 相同, 那也就是說明 ps 進程是該進程組的組長進程, 該進程組包括 ps 和 cat 兩個進程。

  • 進程組組長的作用: 進程組組長可以創建一個進程組或者創建該組中的進程
  • 進程組的生命周期: 從進程組創建開始到其中最后一個進程離開為止。

注意:主要某個進程組中有一個進程存在,則該進程組就存在,這與其組長進程是否已經終止無關。

7.2、會話

7.2.1、什么是會話

會話其實和進程組息息相關,會話可以看成是一個或多個進程組的集合, 一個會話可以包含多個進程組。每一個會話也有一個會話 ID(SID)

通常我們都是使用管道將幾個進程編成一個進程組。 如上圖的進程組 2 和進程組 3 可能是由下列命令形成的:

shell

[node@localhost code]$ proc2 | proc3 &

[node@localhost code]$ proc4 | proc5 | proc6 &

&表示將進程組放在后臺執行

我們舉一個例子觀察一下這個現象:

Shell

# 用管道和 sleep 組成一個進程組放在后臺運行

[node@localhost code]$ sleep 100 | sleep 200 | sleep 300 &

# 查看 ps 命令打出來的列描述信息

[node@localhost code]$ ps axj | head -n1

# 過濾 sleep 相關的進程信息

[node@localhost code]$ ps axj | grep sleep | grep -v grep

# a 選項表示不僅列當前?戶的進程,也列出所有其他?戶的進程

# x 選項表示不僅列有控制終端的進程,也列出所有?控制終端的進程

# j 選項表示列出與作業控制相關的信息, 作業控制后續會講

# grep 的-v 選項表示反向過濾, 即不過濾帶有 grep 字段相關的進程

# 結果如下

PPID? ? PID? ? PGID? ? SID? ? TTY? ????????TPGID? ? STAT? ? UID? ? TIME? ? COMMAND

2806? ? 4223? ?4223? ? 2780? ?pts/2? ? ? ? ?4229? ? ? ?S? ? ? ? 1000? ? 0:00? ? ? ?sleep 100

2806? ? 4224? ?4223? ? 2780? ?pts/2? ? ? ? ?4229? ? ? ?S? ? ? ? 1000? ? 0:00? ? ? ?sleep 200

2806? ? 4225? ?4223? ? 2780? ?pts/2? ? ? ? ?4229? ? ? ?S? ? ? ? 1000? ? 0:00? ? ? ?sleep 300

從上述結果來看 3 個進程對應的 PGID 相同, 即屬于同一個進程組。

7.2.2、如何創建會話

可以調用 setseid 函數來創建一個會話, 前提是調用進程不能是一個進程組的組長。

C

#include<unistd.h>

/*

????????*功能:創建會話

????????*返回值:創建成功返回 SID, 失敗返回-1

*/

pid_t setsid(void);

該接口調用之后會發生:

  • 調用進程會變成新會話的會話首進程。 此時, 新會話中只有唯一的一個進程。
  • 調用進程會變成進程組組長。 新進程組 ID 就是當前調用進程 ID
  • 該進程沒有控制終端。 如果在調用 setsid 之前該進程存在控制終端, 則調用之后會切斷聯系

需要注意的是: 這個接口如果調用進程原來是進程組組長, 則會報錯, 為了避免這種情況, 我們通常的使用方法是先調用 fork 創建子進程, 父進程終止, 子進程繼續執行, 因為子進程會繼承父進程的進程組 ID, 而進程 ID 則是新分配的, 就不會出現錯誤的情況。

7.2.3、會話 ID(SID)

上邊我們提到了會話 ID, 那么會話 ID 是什么呢? 我們可以先說一下會話首進程, 會話首進程是具有唯一進程 ID 的單個進程, 那么我們可以將會話首進程的進程 ID 當做是會話 ID。注意:會話 ID 在有些地方也被稱為 會話首進程的進程組 ID, 因為會話首進程總是一個進程組的組長進程, 所以兩者是等價的。

7.3、控制終端

什么是控制終端?

在 UNIX 系統中,用戶通過終端登錄系統后得到一個 Shell 進程,這個終端成為 Shell 進程的控制終端。控制終端是保存在 PCB 中的信息,我們知道 fork 進程會復制 PCB中的信息,因此由 Shell 進程啟動的其它進程的控制終端也是這個終端。默認情況下 沒有重定向,每個進程的標準輸入、標準輸出和標準錯誤都指向控制終端,進程從標 準輸入讀也就是讀用戶的鍵盤輸入,進程往標準輸出或標準錯誤輸出寫也就是輸出到 顯示器上。另外會話、進程組以及控制終端還有一些其他的關系,我們在下邊詳細介紹一下:

  • 一個會話可以有一個控制終端,通常會話首進程打開一個終端(終端設備或 偽終端設備)后,該終端就成為該會話的控制終端。
  • 建立與控制終端連接的會話首進程被稱為控制進程。
  • 一個會話中的幾個進程組可被分成一個前臺進程組以及一個或者多個后臺進程組。
  • 如果一個會話有一個控制終端,則它有一個前臺進程組,會話中的其他進程 組則為后臺進程組。
  • 無論何時進入終端的中斷鍵(ctrl+c)或退出鍵(ctrl+\),就會將中斷信號發送給前臺進程組的所有進程。
  • 如果終端接口檢測到調制解調器(或網絡)已經斷開,則將掛斷信號發送給控制進程(會話首進程)。

這些特性的關系如下圖所示:

7.4、作業控制

7.4.1、什么是作業(job)和作業控制(Job Control)?

作業是針對用戶來講,用戶完成某項任務而啟動的進程,一個作業既可以只包含一個進程,也可以包含多個進程,進程之間互相協作完成任務, 通常是一個進程管道。

Shell 分前后臺來控制的不是進程而是作業 或者進程組。一個前臺作業可以由多個進程組成,一個后臺作業也可以由多個進程組成,Shell 可以同時運?一個前臺作業和任意多個后臺作業,這稱為作業控制。

例如下列命令就是一個作業,它包括兩個命令,在執?時 Shell 將在前臺啟動由兩個進程組成的作業:

Shell

[node@localhost code]$ cat /etc/filesystems | head -n 5

運?結果如下所示:

xfs

ext4

ext3

ext2

nodev

proc

7.4.2、作業號

放在后臺執?的程序或命令稱為后臺命令,可以在命令的后面加上&符號從而讓 Shell 識別這是一個后臺命令,后臺命令不用等待該命令執?完成,就可立即接收 新的命令,另外后臺進程執行完后會返回一個作業號以及一個進程號(PID)。

例如下面的命令在后臺啟動了一個作業, 該作業由兩個進程組成, 兩個進程都在后臺運?。

Shell

[node@localhost code]$ cat /etc/filesystems | grep ext &

執?結果如下:

[1] 2202

ext4

ext3

ext2

# 按下回車

[1]+ 完成? ?????????cat /etc/filesystems | grep -- color=auto ext

  • 第一?表示作業號和進程 ID, 可以看到作業號是 1, 進程 ID 是 2202
  • 第 3-4 ?表示該程序運?的結果, 過濾/etc/filesystems 有關 ext 的內容
  • 第 6 號分別表示作業號、默認作業、作業狀態以及所執?的命令

關于默認作業:對于一個用戶來說,只能有一個默認作業(+),同時也只能有一 個即將成為默認作業的作業(-),當默認作業退出后,該作業會成為默認作業。

  • + : 表示該作業號是默認作業
  • -:表示該作業即將成為默認作業
  • 無符號: 表示其他作業

7.4.3、作業狀態

常見的作業狀態如下表所示:

7.4.4、作業的掛起與切回

(1) 作業掛起

我們在執?某個作業時,可以通過 Ctrl+Z 鍵將該作業掛起,然后 Shell 會顯示相關的作業號、狀態以及所執?的命令信息。

例如我們運?一個死循環的程序, 通過 Ctrl+Z 將該作業掛起, 觀察一下對應的作業狀態:

#include<stdio.h>?

int main()

{

????????while (1)

????????{

????????????????printf("hello\n");

????????}

????????return 0;

}

下面我運?這個程序, 通過 Ctrl+Z 將該作業掛起:

Shell

# 運行可執行程序

[node@localhost code]$ ./test

#鍵入 Ctrl + Z 觀察現象

運?結果如下:

Shell

# 結果依次對應作業號 默認作業 作業狀態 運行程序信息

[1]+???????? 已停止 ????????./test7

可以發現通過 Ctrl+Z 將作業掛起, 該作業狀態已經變為了停止狀態

(2) 作業切回

如果想將掛起的作業切回,可以通過 fg 命令,fg 后面可以跟作業號或作業的命 令名稱。如果參數缺省則會默認將作業號為 1 的作業切到前臺來執?,若當前系 統只有一個作業在后臺進?,則可以直接使用 fg 命令不帶參數直接切回。 具體的參數參考如下:

例如我們把剛剛掛起來的./test 作業切回到前臺:

Shell

[node@localhost code]$ fg? %%

運?結果為開始無限循環打印 hello, 可以發現該作業已經切換到前臺了。

注意: 當通過 fg 命令切回作業時,若沒有指定作業參數,此時會將默認作業切到前臺執行,即帶有“+”的作業號的作業

7.4.5、查看后臺執行或掛起的作業

我們可以直接通過輸入 jobs 命令查看本用戶當前后臺執?或掛起的作業

  • 參數-l 則顯示作業的詳細信息
  • 參數-p 則只顯示作業的 PID

例如, 我們先在后臺及前臺運?兩個作業, 并將前臺作業掛起, 來用 jobs 命令 查看作業相關的信息:

Shell

# 在后臺運行一個作業 sleep

[node@localhost code]$ sleep 300 &

# 運行剛才的死循環可執行程序

[node@localhost code]$ ./test

# 鍵入 Ctrl + Z 掛起作業

# 使用 jobs 命令查看后臺及掛起的作業

[node@localhost code]$ jobs -l

運?結果如下所示:

Shell

# 結果依次對應作業號? ?默認作業? ?作業狀態? ? 運行程序信息

[1]-? ? ?2265? ? ?運行中? ? sleep 300 &

[2]+? ? 2267? ? ?停止? ? ? ?./test7

7.4.6、作業控制相關的信號

上面我們提到了鍵入 Ctrl + Z 可以將前臺作業掛起,實際上是將 STGTSTP 信號 發送至前臺進程組作業中的所有進程, 后臺進程組中的作業不受影響。 在 unix系統中, 存在 3 個特殊字符可以使得終端驅動程序產生信號, 并將信號發送至前臺進程組作業, 它們分別是:

  • Ctrl + C: 中斷字符, 會產生 SIGINT 信號
  • Ctrl + \: 退出字符, 會產生 SIGQUIT 信號
  • Ctrl + Z:掛起字符, 會產生 STGTSTP 信號

終端的 I/O(即標準輸入和標準輸出)和終端產生的信號總是從前臺進程組作業連接打破實際終端。我們可以通過下體來看到作業控制的功能:

7.4.7、前后臺作業轉化

  • fg + 作業號:將后臺進程放入前臺執行。
  • bg?+ 作業號:將前臺進程放入后臺執行。

7.5、守護進程

守護進程(Daemon Process)是操作系統中的一種特殊后臺進程,通常在系統啟動時啟動,持續運行以提供某種服務或執行特定任務,獨立于用戶終端且不受用戶登錄/注銷的影響。以下是其核心特點:

  • 后臺運行:無控制終端(TTY),不與用戶直接交互,通常在后臺默默執行任務(如日志管理、網絡服務等)。
  • 生命周期長:隨系統啟動而啟動,直到系統關閉才終止,提供持續服務(如 httpd 提供Web服務)。
  • 脫離終端與會話:通過fork()創建子進程后,父進程退出,子進程調用setsid()脫離原有會話和終端,避免被信號干擾。父進程需要退出是因為調用 setseid 函數來創建一個會話, 前提是調用進程不能是一個進程組的組長。

7.6、如何將進程守護化

示例代碼:

#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);}}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/92740.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/92740.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/92740.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

機器學習week3-分類、正則化

1. 邏輯回歸1.1. 線性回歸 vs 邏輯回歸對比維度線性回歸邏輯回歸任務類型回歸&#xff08;預測連續值&#xff09;分類&#xff08;預測離散類別&#xff09;輸出范圍(?∞,∞)[0,1]&#xff08;概率值&#xff09;損失函數均方誤差&#xff08;MSE&#xff09;對數損失&#x…

FastAdmin 中生成插件

在 FastAdmin 中生成一個 OCR 發票識別插件&#xff0c;可以按照以下步驟進行開發。這里假設你已經熟悉 FastAdmin 插件開發的基本流程&#xff0c;并會使用 Composer 和 PHP 擴展。1. 創建插件骨架使用 FastAdmin 命令行工具生成插件基礎結構&#xff1a;php think addon -a o…

DevExpress WinForms中文教程:Grouping(分組)- 如何自定義分組算法?

DevExpress WinForms擁有180組件和UI庫&#xff0c;能為Windows Forms平臺創建具有影響力的業務解決方案。DevExpress WinForms能完美構建流暢、美觀且易于使用的應用程序&#xff0c;無論是Office風格的界面&#xff0c;還是分析處理大批量的業務數據&#xff0c;它都能輕松勝…

PHP 與 Vue.js 結合的前后端分離架構

PHP 與 Vue.js 結合是構建現代 Web 應用的流行技術棧&#xff0c;通常采用 前后端分離架構。以下是關鍵要點和推薦實現方案&#xff1a; 一、技術棧組合 角色技術選項后端 (PHP)Laravel (推薦)、Symfony、CodeIgniter前端 (Vue)Vue 2/3、Vue Router、Pinia/Vuex、Vite通信協議…

XML高效處理類 - 專為Office文檔XML處理優化

/**** 提供XML讀取、寫入、修改、查詢等高級功能&#xff0c;支持命名空間和復雜XML結構* * 主要功能&#xff1a;* 1. 復雜路徑解析&#xff08;支持屬性篩選、索引、通配符&#xff09;* 2. 完整節點類型支持&#xff08;元素、文本、CDATA、注釋、PI&#xff09;* 3. 高效元…

星慈光編程蟲2號小車講解第一篇--向前向后

星慈光編程蟲2號小車是一款基于微控制器&#xff08;如Arduino&#xff09;的編程教學小車&#xff0c;常用于學習機器人控制和編程基礎。本講解將重點介紹小車的基本運動&#xff1a;前進、后退、左轉和右轉。這些運動通過控制電機實現&#xff0c;通常涉及調整電機的方向和速…

iOS 加固工具有哪些?快速發布團隊的實戰方案

在當今快速迭代的 iOS 開發環境中&#xff0c;團隊需要在高頻上線與應用安全之間找到平衡。快速發布不應犧牲安全性&#xff0c;而安全加固也不應成為阻礙上線的瓶頸。這就要求開發者在加固工具的選型與流程設計上&#xff0c;做到既高效又可靠。 那么&#xff0c;iOS 加固工具…

結構型模式-架構解耦與擴展實踐

結構型模式聚焦于對象間的組合關系&#xff0c;通過優化類與對象的裝配方式&#xff0c;實現系統的靈活性與可擴展性。在分布式系統中&#xff0c;由于多節點協作、跨網絡通信及異構環境集成等特性&#xff0c;傳統結構型模式需進行適應性改造&#xff0c;以應對分布式特有的復…

scratch筆記和練習-第三課

角色的大小變化 亮度等特效設置 流程圖圖形符號 Figma攻略&#xff1a;26個流行流程圖符號及其解釋 練習 實現在閃動10次后角色緩緩變回原形

Redis MCP 安裝與配置完整指南

一、Redis MCP 簡介 Redis MCP (Managed Control Plane) 是一個獨立于 Redis 服務運行的管理控制平臺&#xff0c;用戶可通過該平臺快速高效地管理和配置 Redis 實例。Redis MCP 可配合開源 Redis 或 Redis Cloud 使用。 二、安裝 Redis MCP 服務 Redis MCP 提供多種安裝方式&a…

Spring Boot配置文件加載全指南:從基礎到Spring Cloud集成

??? ??一、核心概念? 配置文件默認存在加載順序優先級主要用途必需依賴bootstrap.yml? 無1(最先)最高Spring Cloud上下文初始化spring-cloud-starter-bootstrapbootstrap.properties? 無1(略高于.yml)最高同上同上application.yml? 自動創建2中等應用核心配置無appl…

Python通關秘籍(六)數據結構——字典

前文復習 五、數據結構 5.1 列表(List) 列表是一種有序的可變數據集合,可以包含不同類型的元素。

自學嵌入式 day33 TCP、HTTP協議(超文本傳輸協議)

6、黏包問題&#xff08;1&#xff09;、原因&#xff1a;發送方發送數據太快或者接收方接收數據太慢&#xff0c;導致數據在緩沖區緩存。&#xff08;2&#xff09;、解決方法&#xff1a;①發送指定大小數據&#xff08;結構體&#xff09;問題&#xff1a;結構體對齊問題&am…

LinuxShell 的 Here-Document(<< EOF) 筆記250723

LinuxShell 的 Here-Document(<< EOF) 筆記250723 Here-Document(<< EOF) Linux Shell Here Document (<< EOF) 終極指南 Here Document&#xff08;立即文檔&#xff09;是 Shell 中用于多行輸入重定向的強大功能&#xff0c;其核心語法為 << DELI…

【windows修復】解決windows10,沒有【相機] 功能問題

問題: windows10,相機模塊,好像是被卸載了,想重新安裝 方法簡介: 先下載windows store, 然后,在windows store 里面下載 相機功能: 解決: 直接下載官方離線包并手動安裝(成功率 90%+) 1 用瀏覽器打開 https://store.rg-adguard.net 這是微軟 CDN 解析站,安…

Python 中字典和 if-else 的選擇

一、為什么要寫這篇文章&#xff1f; 在 Python 編程中&#xff0c;我們經常需要根據不同的條件做不同的事情。比如&#xff1a; 根據用戶等級顯示不同的內容根據成績給出不同的評價根據天氣決定穿什么衣服 這時候&#xff0c;我們通常有兩種選擇&#xff1a; 用 if-else 語句用…

【開源解析】基于HTML5的智能會議室預約系統開發全攻略:從零構建企業級管理平臺

&#x1f680; 【開源解析】基于HTML5的智能會議室預約系統開發全攻略&#xff1a;從零構建企業級管理平臺 &#x1f308; 個人主頁&#xff1a;創客白澤 - CSDN博客 &#x1f4a1; 熱愛不止于代碼&#xff0c;熱情源自每一個靈感閃現的夜晚。愿以開源之火&#xff0c;點亮前行…

中央廣播電視總臺聯合阿里云研究院權威發布《中國人工智能應用發展報告(2025)》:我國依舊需要大力注重人工智能人才的培養

你好&#xff0c;我是杰哥。 中央廣播電視總臺聯合阿里云研究院權威發布《中國人工智能應用發展報告&#xff08;2025&#xff09;》&#xff0c;以下為報告核心看點&#xff1a; 報告首提 “654”體系&#xff1a;揭秘 6大技術趨勢、5 新應用場景、4 力產業模型&#xff1b;成…

Visual Studio 2010-.Net Framework 4.0-DevExpress安裝

最新版的DevExpress已不支持.Net Framework 4.0&#xff0c;需要下載18.1及以下版本。 17.2.5版DevExpress下載&#xff1a; 百度網盤 請輸入提取碼

借助Aspose.HTML控件,在 Python 中將 HTML 轉換為 Markdown

在這個人工智能時代&#xff0c;Markdown因其易用性而備受重視。這種標記語言易于人類和機器理解。此外&#xff0c;與 HTML 和 DOCX 相比&#xff0c;這種格式更有助于法學碩士 (LLM) 理解文檔結構。因此&#xff0c;本指南將介紹如何以 Python 編程方式將HTML轉換為 Markdown…