文章目錄
- 91. TCP斷開連接的時候為什么必須4次而不是3次?
- 92. 為什么要區分用戶態和內核態?
- 93. 說說編寫socket套接字的步驟
- 1. 服務器端編寫步驟
- 1.1 創建套接字
- 1.2 綁定套接字
- 1.3 監聽連接
- 1.4 接受連接
- 1.5 數據傳輸
- 1.6 關閉套接字
- 2. 客戶端編寫步驟
- 2.1 創建套接字
- 2.2 連接服務器
- 2.3 數據傳輸
- 2.4 關閉套接字
- 94. 什么是大小端模式,編寫代碼區分大小端
- 如何檢查自己的電腦 是大端還是小端?
- 第一種方法:
- 第二種方法:
- 95. 代碼實現:實現簡單的智能指針
91. TCP斷開連接的時候為什么必須4次而不是3次?
92. 為什么要區分用戶態和內核態?
93. 說說編寫socket套接字的步驟
編寫一個基于套接字(socket)的網絡程序通常包括以下步驟,無論是客戶端還是服務器都需要遵循這些步驟。下面分別說明服務器和客戶端編寫的步驟,這些是簡單的代碼示例,僅僅幫助大家去理解這個過程。
1. 服務器端編寫步驟
1.1 創建套接字
使用 socket() 函數創建一個套接字。這個函數返回一個套接字描述符,用于后續的操作。
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {perror("socket failed");exit(EXIT_FAILURE);
}
1.2 綁定套接字
將創建的套接字綁定到指定的 IP 地址和端口號上,使用 bind() 函數。
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");close(server_fd);exit(EXIT_FAILURE);
}
網絡字節序是大端模式
address.sin_addr.s_addr = INADDR_ANY;指綁定端口到本地所有網絡接口
1.3 監聽連接
使用 listen() 函數使套接字進入監聽狀態,等待客戶端連接請求。
if (listen(server_fd, 3) < 0) {perror("listen");close(server_fd);exit(EXIT_FAILURE);
}
1.4 接受連接
使用 accept() 函數接受客戶端的連接請求,返回一個新的套接字描述符,用于與客戶端通信。
int new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
if (new_socket < 0) {perror("accept");close(server_fd);exit(EXIT_FAILURE);
}
1.5 數據傳輸
使用 read() 和 write() 函數(或 recv() 和 send() 函數)進行數據的接收和發送。
char buffer[1024] = {0};
read(new_socket, buffer, 1024);
printf("Message from client: %s\n", buffer);
send(new_socket, "Hello from server", strlen("Hello from server"), 0);
1.6 關閉套接字
完成通信后,使用 close() 函數關閉套接字。
close(new_socket);
close(server_fd);
2. 客戶端編寫步驟
2.1 創建套接字
與服務器端類似,使用 socket() 函數創建一個套接字。
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {perror("socket failed");exit(EXIT_FAILURE);
}
2.2 連接服務器
使用 connect() 函數將套接字連接到服務器端的指定 IP 地址和端口號。
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {perror("Invalid address/ Address not supported");close(sock);exit(EXIT_FAILURE);
}if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {perror("Connection Failed");close(sock);exit(EXIT_FAILURE);
}
2.3 數據傳輸
同樣使用 read() 和 write() 或 recv() 和 send() 進行數據的發送和接收。
send(sock, "Hello from client", strlen("Hello from client"), 0);
char buffer[1024] = {0};
read(sock, buffer, 1024);
printf("Message from server: %s\n", buffer);
2.4 關閉套接字
完成通信后,使用 close() 函數關閉套接字。
close(sock);
94. 什么是大小端模式,編寫代碼區分大小端
如何檢查自己的電腦 是大端還是小端?
第一種方法:
#include <iostream> bool isLittleEndian() { int num = 1; // 數值 1 的 32 位表示是:0x00 00 00 01(從高到低 4 個字節)。// 大端(big-endian)把最高有效字節放在最低地址,所以內存從低地址到高地址是:// 00 00 00 01// 于是 reinterpret_cast<char*>(&num) 指向的第一個字節就是 0x00// 小端(little-endian)相反,把最低有效字節放在最低地址,所以是:// 01 00 00 00// 這時第一個字節是 0x01// 換個更形象的例子:如果 num = 0x12 34 56 78,// 大端內存排布(低→高地址):12 34 56 78// 小端內存排布(低→高地址):78 56 34 12char *c = reinterpret_cast<char*>(&num); return *c == 1; // 如果最低有效字節在最低地址處,則為小端字節序
} int main() { if (isLittleEndian()) { std::cout << "This is a little-endian system." << std::endl; } else { std::cout << "This is a big-endian system." << std::endl; } return 0;
}
第二種方法:
利用數據類型的存儲方式來判斷當前系統的字節序。常見的實現方法是使用 union 聯合體來共享內存,并通過訪問不同的成員來檢查數據的存儲順序。
#include <iostream>
#include <cstdint>// 定義一個聯合體,包含一個整數和一個字符數組
// 聯合體(union)的所有成員共享同一段內存;寫一個成員,換個成員讀,能看到相同內存里的原始字節
union {// uint8_t:精確 8 位 無符號整數,范圍 0 ~ 255。// uint32_t:精確 32 位 無符號整數,范圍 0 ~ 4,294,967,295。uint32_t i; // 32 位整數uint8_t c[4]; // 4 字節字符數組
} test;int main() {test.i = 0x12345678; // 將一個已知的 32 位整數存入聯合體中// 根據第一個字節的值判斷大小端if (test.c[0] == 0x78) {std::cout << "小端模式 (Little-endian)" << std::endl;} else if (test.c[0] == 0x12) {std::cout << "大端模式 (Big-endian)" << std::endl;} else {std::cout << "無法確定字節序" << std::endl;}return 0;
}
95. 代碼實現:實現簡單的智能指針
下面是一個簡單的智能指針實現的例子,用于管理動態分配的內存,避免內存泄漏。這個示例實現了一個類似于 std::shared_ptr 的簡單智能指針,叫做 SimpleSmartPointer,它使用引用計數來管理對象的生命周期。
是在定義一個新對象 sp2,帶著一個“初始值”sp1。
在 C++ 里,帶初始值的定義叫“拷貝初始化(copy-initialization)”,它會調用拷貝構造函數(或能匹配的移動構造),不會調用賦值運算符。賦值運算符只在對象已經存在之后再用 = 給它“換內容”時才會被調用。SimpleSmartPointer<int> a(new int(10));// 1) 拷貝初始化:調用拷貝構造函數
SimpleSmartPointer<int> b = a; // == SimpleSmartPointer<int> b(a);// 2) 直接初始化:也調用拷貝構造函數
SimpleSmartPointer<int> c(a);// 3) 先默認構造一個對象,再賦值:調用賦值運算符 operator=
SimpleSmartPointer<int> d; // 等價于 SimpleSmartPointer<int> d(nullptr);
d = a; // 這里才會走你的 operator=
#include <iostream>// 簡單智能指針類
template<typename T>
class SimpleSmartPointer {
private:T* ptr; // 原生指針unsigned* count; // 引用計數public:// 構造函數,接受一個原生指針//這個explicit 主要目的是防止隱式類型轉換。提高代碼可讀性和安全性explicit SimpleSmartPointer(T* p = nullptr) : ptr(p) {if (p) {count = new unsigned(1); // 初始化引用計數為1} else {count = nullptr;}}// 拷貝構造函數SimpleSmartPointer(const SimpleSmartPointer<T>& sp) : ptr(sp.ptr), count(sp.count) {if (count) {(*count)++; // 增加引用計數}}// 賦值運算符重載SimpleSmartPointer<T>& operator=(const SimpleSmartPointer<T>& sp) {if (this == &sp) {return *this; // 防止自我賦值}// 釋放當前資源//--(*count):對 *count(引用計數值)進行自減操作,表示當前對象不再使用該資源//如果 *count 為 0,說明已經沒有其他智能指針對象在使用這個資源了,此時需要釋放資源if (count && --(*count) == 0) {delete ptr;delete count;}// delete ptr; 釋放的是 ptr 指向的那塊堆內存(并調用析構),并不會“刪掉變量 ptr 本身”// 賦值新資源ptr = sp.ptr;count = sp.count;if (count) {(*count)++;}return *this;}// 解引用運算符重載// 返回類型:對 T 的引用。有了引用返回,*sp 就是一個可當左值用的對象(能讀也能改)。// 函數名是個特殊運算符函數:重載“一元解引用運算符 *”。// 調用方式:*sp 等價于 sp.operator*()// operator* 是運算符重載函數,重載的是“一元解引用運算符 *”(注意不是乘法;乘法是二元 *)。// 這是一個成員函數,當你寫 *sp 時,編譯器會把它當作:// sp.operator*() // 調用你這個函數// ptr 是你類里存的裸指針(T*)。// *ptr 是對這個裸指針的解引用,得到“那個 T 對象本身”。結合返回類型 T&,就把“托管對象”的引用交給了調用者。T& operator*() const {return *ptr;}// 指針訪問運算符重載// T* operator->() const { return ptr; }// 把內部的裸指針 ptr(類型 Point*)拿出來,然后再用普通指針的 -> 去調用 print() / move() / 訪問 x、y。所以 p->成員 就能像真指針那樣用起來了// 前提是裸指針對應的數據結構里面有定義成員變量或者成員函數// operator->() 只有在 T 有成員時才有用;你現在用的是 SimpleSmartPointer<int>,int 沒成員,所以用不上。給它換個有成員的類型,比如 Point,就能直接寫 sp->成員/方法 了。T* operator->() const {return ptr;}// 返回當前共享計數;空指針時按慣例返回 0unsigned use_count() const {return count ? *count : 0;}// 獲取原生指針T* get() const {return ptr;}// 析構函數 RAII~SimpleSmartPointer() {if (count && --(*count) == 0) {delete ptr;delete count;}}
};// 測試函數
void testSimpleSmartPointer() {// sp1.ptr 指向“int(10)”;// sp1.count 指向一塊 unsigned,其值 *count == 1SimpleSmartPointer<int> sp1(new int(10)); // 創建一個智能指針,管理整數10std::cout << "sp1: " << *sp1 << std::endl; // 輸出sp1所指向的值std::cout << "sp1.use_count() = " << sp1.use_count() << "\n";{// 拷貝構造函數 ← 就是被 “SimpleSmartPointer<int> sp2 = sp1;” 調用的這個SimpleSmartPointer<int> sp2 = sp1; // sp2與sp1共享同一塊內存*sp2 = 33;std::cout << "sp2: " << *sp2 << std::endl; // 輸出sp2所指向的值std::cout << "sp2.use_count() = " << sp2.use_count() << "\n";std::cout << "sp1.use_count() = " << sp1.use_count() << "\n";} // sp2超出作用域,引用計數減1// 再次輸出sp1所指向的值std::cout << "sp1: " << *sp1 << std::endl;std::cout << "sp1.use_count() = " << sp1.use_count() << "\n";
}int main() {testSimpleSmartPointer();return 0;
}
之后我會持續更新,如果喜歡我的文章,請記得一鍵三連哦,點贊關注收藏,你的每一個贊每一份關注每一次收藏都將是我前進路上的無限動力 !!!↖(▔▽▔)↗感謝支持!