項目-SERVER模塊-Socket模塊

Socket模塊

  • 一、Socket模塊是什么?
  • 二、代碼實現
    • 1.成員變量
    • 2.構造、析構函數
    • 3.獲取套接字文件描述符
    • 4.創建套接字
    • 5.綁定地址信息
    • 6.開始監聽連接請求
    • 7.向服務器發起連接
    • 8.獲取新連接
    • 9.接收數據
    • 10.非阻塞接收數據
    • 11.發送數據
    • 12.非阻塞發送數據
    • 13.關閉套接字
    • 14.創建一個服務端連接
    • 15.創建一個客戶端連接
    • 16.設置套接字選項——開啟地址端口重用
    • 17. 設置套接字阻塞屬性——設置為非阻塞
    • 18.測試代碼
    • 19.整體源代碼


一、Socket模塊是什么?

Socket模塊是對套接字操作封裝的?個模塊,主要實現的socket的各項操作。
在這里插入圖片描述

二、代碼實現

1.成員變量

這行代碼表示在類(class)的私有(private)部分聲明了一個整型變量 _sockfd,用于存儲某個類的套接字文件描述符。在類的私有部分聲明的成員只能在該類的成員函數內部訪問,外部無法直接訪問。

private:int _sockfd;

2.構造、析構函數

這段代碼展示了一個名為Socket的類的構造函數和析構函數的定義:

  1. Socket() : _sockfd(-1) {}: 這是一個無參數的構造函數,用于初始化Socket類的對象。在這個構造函數中,通過初始化列表將_sockfd成員變量設置為-1,表示套接字文件描述符的初始值為-1。

  2. Socket(int sockfd) : _sockfd(sockfd) {}: 這是一個帶有整型參數的構造函數,用于初始化Socket類的對象并指定套接字文件描述符的值。在這個構造函數中,通過初始化列表將_sockfd成員變量設置為傳入的參數sockfd的值。

  3. ~Socket() { Close(); }: 這是Socket類的析構函數,用于釋放資源和清理工作。在析構函數中調用了Close()函數,該函數應該是Socket類的一個成員函數,用于關閉套接字。通過在析構函數中調用Close()函數,確保在對象被銷毀時及時關閉相關資源,避免資源泄漏。

public:Socket() : _sockfd(-1) {}Socket(int sockfd) : _sockfd(sockfd) {}~Socket() { Close(); }

3.獲取套接字文件描述符

這段代碼定義了一個名為get_fd()的成員函數,用于獲取類中私有成員變量_sockfd的值(套接字文件描述符)。該函數返回整型值,表示獲取到的套接字文件描述符。

通過定義這樣的成員函數,可以在類外部獲取Socket類對象的套接字文件描述符,同時保持_sockfd作為私有成員的封裝性。這樣的設計方式遵循了面向對象編程的封裝原則,將類的數據隱藏起來,只允許通過成員函數來進行訪問和操作,從而提高了代碼的安全性和可維護性。

    // 獲取套接字文件描述符int get_fd() { return _sockfd; }

4.創建套接字

    // 創建套接字bool Create(){// 調用socket函數創建套接字// int socket(int domain, int type, int protocol)_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// 檢查套接字創建是否成功if (_sockfd < 0){// 套接字創建失敗時輸出日志信息INF_LOG("Socket creation failed");return false;}// 套接字創建成功return true;}

5.綁定地址信息

// 綁定地址和端口
bool Bind(const std::string &ip, uint16_t port)
{// 創建一個 sockaddr_in 結構體并設置相關參數struct sockaddr_in addr;addr.sin_family = AF_INET;  // 設置地址族為IPv4// 將端口號轉換為網絡字節順序addr.sin_port = htons(port);// 將IP地址轉換為網絡字節順序并填入結構體中addr.sin_addr.s_addr = inet_addr(ip.c_str());// 計算地址結構體的長度socklen_t len = sizeof(struct sockaddr_in);// 調用bind函數將套接字和地址綁定int ret = bind(_sockfd, (struct sockaddr*)&addr, len);if (ret < 0){// 綁定失敗時輸出錯誤日志信息ERR_LOG("BIND ADDRESS FAILED!");return false;}// 綁定成功return true;
}

6.開始監聽連接請求

// 開始監聽連接請求
bool Listen(int backlog = MAX_LISTEN)
{// 調用listen函數開始監聽連接請求int ret = listen(_sockfd, backlog);if (ret < 0){// 監聽失敗時輸出錯誤日志信息ERR_LOG("SOCKET LISTEN FAILED!");return false;}// 監聽成功return true;
}

7.向服務器發起連接

// 向服務器發起連接
bool Connection(const std::string &ip, uint16_t port)
{// 創建一個 sockaddr_in 結構體并設置相關參數struct sockaddr_in addr;addr.sin_family = AF_INET;  // 設置地址族為IPv4// 將端口號轉換為網絡字節順序addr.sin_port = htons(port);// 將IP地址轉換為網絡字節順序并填入結構體中addr.sin_addr.s_addr = inet_addr(ip.c_str());// 計算地址結構體的長度socklen_t len = sizeof(struct sockaddr_in);// 調用connect函數發起連接請求int ret = connect(_sockfd, (struct sockaddr *)&addr, len);if (ret < 0){// 連接失敗時輸出錯誤日志信息ERR_LOG("CONNECT SERVER FAILED!");return false;}// 連接成功return true;
}

8.獲取新連接

// 獲取新連接
int Accept()
{// 調用accept函數接受新的連接// int accept(int sockfd, struct sockaddr *addr, socklen_t *len);int newfd = accept(_sockfd, NULL, NULL);if (newfd < 0){// 接受連接失敗時輸出錯誤日志信息ERR_LOG("SOCKET ACCEPT FAILED!");return -1;}// 返回新的文件描述符return newfd;
}

9.接收數據

// 接收數據
ssize_t Recv(void *buf, size_t len, int flag = 0)
{// 調用recv函數接收數據ssize_t s = recv(_sockfd, buf, len, flag);if (s <= 0){if (errno == EINTR || errno == EAGAIN){// 如果是由于被信號中斷或者暫時沒有數據可讀造成的接收失敗,則記錄日志并返回0INF_LOG("Recv has not ready!");return 0;}// 其他接收失敗的情況,記錄錯誤日志信息并返回-1ERR_LOG("read failed!");return -1;}// 返回實際接收的數據長度return s;
}

10.非阻塞接收數據

// 非阻塞接收數據
ssize_t NonBlockRecv(void *buf, size_t len)
{// 調用Recv函數,設置MSG_DONTWAIT標志表示非阻塞接收return Recv(buf, len, MSG_DONTWAIT);
}

11.發送數據

// 發送數據
ssize_t Send(const void *buf, size_t len, int flag = 0)
{// 調用send函數發送數據// ssize_t send(int sockfd, void *data, size_t len, int flag);ssize_t ret = send(_sockfd, buf, len, flag);if (ret < 0){// 發送失敗時記錄錯誤日志信息并返回-1ERR_LOG("SOCKET SEND FAILED!!");return -1;}// 返回實際發送的數據長度return ret;
}

12.非阻塞發送數據

// 非阻塞發送數據
ssize_t NonBlockSend(void *buf, size_t len)
{// 調用Send函數,設置MSG_DONTWAIT標志表示非阻塞發送return Send(buf, len, MSG_DONTWAIT);
}

13.關閉套接字

// 關閉套接字
void Close()
{// 檢查套接字是否有效,如果有效則關閉套接字if (_sockfd != -1)close(_sockfd);// 將套接字文件描述符設置為無效值_sockfd = -1;
}

14.創建一個服務端連接

// 創建一個服務端連接
bool CreateServer(uint16_t port, const std::string &ip = DEFAULT_IP, bool block_flag = false)
{// 1. 創建套接字 // 2. 綁定地址 // 3. 開始監聽 // 4. 設置非阻塞 // 5. 啟動地址重用// 如果創建套接字失敗,則返回falseif (Create() == false) return false;// 如果需要設置為非阻塞模式,則調用NonBlock函數if (block_flag) NonBlock();// 綁定地址,如果綁定失敗則返回falseif (Bind(ip, port) == false) return false;// 開始監聽,如果監聽失敗則返回falseif (Listen() == false) return false;// 啟動地址重用ReuseAddress();return true;
}

15.創建一個客戶端連接

// 創建一個客戶端連接
bool CreateClient(uint16_t port, const std::string &ip)
{// 1. 創建套接字 // 2. 指向連接服務器// 如果創建套接字失敗,則返回falseif (Create() == false)  return false;// 連接服務器,如果連接失敗則返回falseif (Connection(ip, port) == false)return false;return true;
}

16.設置套接字選項——開啟地址端口重用

// 設置套接字選項——開啟地址端口重用
void ReuseAddress()
{// 使用setsockopt函數設置SO_REUSEADDR和SO_REUSEPORT選項開啟地址和端口重用int val = 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, (void*)&val, sizeof(int));val = 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEPORT, (void*)&val, sizeof(int));
}

17. 設置套接字阻塞屬性——設置為非阻塞

// 設置套接字阻塞屬性——設置為非阻塞
void NonBlock()
{// 使用fcntl函數獲取當前套接字的屬性,并設置為非阻塞模式//int fcntl(int fd,int cmd,.../* arg */);int flag = fcntl(_sockfd, F_GETFL, 0);fcntl(_sockfd, F_SETFL, flag | O_NONBLOCK);
}

18.測試代碼

這段代碼是一個簡單的基于Socket的服務器端程序,它創建一個服務器Socket并監聽指定端口(8500)。然后在一個無限循環中接受客戶端連接,接收客戶端發送的數據,并將數據原樣發送回客戶端,最后關閉與客戶端的連接。注意:需要打開兩個終端分別運行服務器端和客戶端

//服務器端
#include"../source/server.hpp"
int main(){Socket lst_sock;lst_sock.CreateServer(8500);while(1){int newfd =lst_sock.Accept();if(newfd <0){continue;}  Socket cli_sock(newfd);char buf[1024]={0};int ret= cli_sock.Recv(buf, 1023);if(ret < 0){cli_sock.Close();continue;}cli_sock.Send(buf, ret);cli_sock.Close();}lst_sock.Close();return 0;
}
//客戶端口
#include"../source/server.hpp"
int main()
{Socket cli_sock;cli_sock.CreateClient(8500,"127.0.0.1");std::string str="hello have a good day~";cli_sock.Send(str.c_str(),str.size());char buf[1024]={0};cli_sock.Recv(buf,1023);DBG_LOG("%s",buf);return 0;
}

測試結果:
在這里插入圖片描述

19.整體源代碼

// Socket //
#define MAX_LISTEN 1024
#define DEFAULT_IP "0.0.0.0"
class Socket
{
private:int _sockfd;
public:Socket() : _sockfd(-1) {}Socket(int sockfd) : _sockfd(sockfd) {}~Socket() { Close(); }// 獲取套接字文件描述符int get_fd() { return _sockfd; }// 創建套接字bool Create(){// 調用socket函數創建套接字// int socket(int domain, int type, int protocol)_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// 檢查套接字創建是否成功if (_sockfd < 0){// 套接字創建失敗時輸出日志信息INF_LOG("Socket creation failed");return false;}// 套接字創建成功return true;}// 綁定地址和端口bool Bind(const std::string &ip, uint16_t port){// 創建一個 sockaddr_in 結構體并設置相關參數struct sockaddr_in addr;addr.sin_family = AF_INET;  // 設置地址族為IPv4// 將端口號轉換為網絡字節順序addr.sin_port = htons(port);// 將IP地址轉換為網絡字節順序并填入結構體中addr.sin_addr.s_addr = inet_addr(ip.c_str());// 計算地址結構體的長度socklen_t len = sizeof(struct sockaddr_in);// 調用bind函數將套接字和地址綁定int ret = bind(_sockfd, (struct sockaddr*)&addr, len);if (ret < 0){// 綁定失敗時輸出錯誤日志信息ERR_LOG("BIND ADDRESS FAILED!");return false;}// 綁定成功return true;}// 開始監聽連接請求bool Listen(int backlog = MAX_LISTEN){// 調用listen函數開始監聽連接請求int ret = listen(_sockfd, backlog);if (ret < 0){// 監聽失敗時輸出錯誤日志信息ERR_LOG("SOCKET LISTEN FAILED!");return false;}// 監聽成功return true;}// 向服務器發起連接bool Connection(const std::string &ip, uint16_t port){// 創建一個 sockaddr_in 結構體并設置相關參數struct sockaddr_in addr;addr.sin_family = AF_INET;  // 設置地址族為IPv4// 將端口號轉換為網絡字節順序addr.sin_port = htons(port);// 將IP地址轉換為網絡字節順序并填入結構體中addr.sin_addr.s_addr = inet_addr(ip.c_str());// 計算地址結構體的長度socklen_t len = sizeof(struct sockaddr_in);// 調用connect函數發起連接請求int ret = connect(_sockfd, (struct sockaddr *)&addr, len);if (ret < 0){// 連接失敗時輸出錯誤日志信息ERR_LOG("CONNECT SERVER FAILED!");return false;}// 連接成功return true;}// 獲取新連接int Accept(){// 調用accept函數接受新的連接// int accept(int sockfd, struct sockaddr *addr, socklen_t *len);int newfd = accept(_sockfd, NULL, NULL);if (newfd < 0){// 接受連接失敗時輸出錯誤日志信息ERR_LOG("SOCKET ACCEPT FAILED!");return -1;}// 返回新的文件描述符return newfd;}// 接收數據ssize_t Recv(void *buf, size_t len, int flag = 0){// 調用recv函數接收數據ssize_t s = recv(_sockfd, buf, len, flag);if (s <= 0){if (errno == EINTR || errno == EAGAIN){// 如果是由于被信號中斷或者暫時沒有數據可讀造成的接收失敗,則記錄日志并返回0INF_LOG("Recv has not ready!");return 0;}// 其他接收失敗的情況,記錄錯誤日志信息并返回-1ERR_LOG("read failed!");return -1;}// 返回實際接收的數據長度return s;}// 非阻塞接收數據ssize_t NonBlockRecv(void *buf, size_t len){// 調用Recv函數,設置MSG_DONTWAIT標志表示非阻塞接收return Recv(buf, len, MSG_DONTWAIT);}// 發送數據ssize_t Send(const void *buf, size_t len, int flag = 0){// 調用send函數發送數據// ssize_t send(int sockfd, void *data, size_t len, int flag);ssize_t ret = send(_sockfd, buf, len, flag);if (ret < 0){// 發送失敗時記錄錯誤日志信息并返回-1ERR_LOG("SOCKET SEND FAILED!!");return -1;}// 返回實際發送的數據長度return ret;}// 非阻塞發送數據ssize_t NonBlockSend(void *buf, size_t len){// 調用Send函數,設置MSG_DONTWAIT標志表示非阻塞發送return Send(buf, len, MSG_DONTWAIT);}// 關閉套接字void Close(){// 檢查套接字是否有效,如果有效則關閉套接字if (_sockfd != -1)close(_sockfd);// 將套接字文件描述符設置為無效值_sockfd = -1;}// 創建一個服務端連接bool CreateServer(uint16_t port, const std::string &ip = DEFAULT_IP, bool block_flag = false){// 1. 創建套接字 // 2. 綁定地址 // 3. 開始監聽 // 4. 設置非阻塞 // 5. 啟動地址重用// 如果創建套接字失敗,則返回falseif (Create() == false) return false;// 如果需要設置為非阻塞模式,則調用NonBlock函數if (block_flag) NonBlock();// 綁定地址,如果綁定失敗則返回falseif (Bind(ip, port) == false) return false;// 開始監聽,如果監聽失敗則返回falseif (Listen() == false) return false;// 啟動地址重用ReuseAddress();return true;}// 創建一個客戶端連接bool CreateClient(uint16_t port, const std::string &ip){// 1. 創建套接字 // 2. 指向連接服務器// 如果創建套接字失敗,則返回falseif (Create() == false)  return false;// 連接服務器,如果連接失敗則返回falseif (Connection(ip, port) == false)return false;return true;}// 設置套接字選項——開啟地址端口重用void ReuseAddress(){// 使用setsockopt函數設置SO_REUSEADDR和SO_REUSEPORT選項開啟地址和端口重用int val = 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, (void*)&val, sizeof(int));val = 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEPORT, (void*)&val, sizeof(int));}// 設置套接字阻塞屬性——設置為非阻塞void NonBlock(){// 使用fcntl函數獲取當前套接字的屬性,并設置為非阻塞模式//int fcntl(int fd,int cmd,.../* arg */);int flag = fcntl(_sockfd, F_GETFL, 0);fcntl(_sockfd, F_SETFL, flag | O_NONBLOCK);}
};

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

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

相關文章

何時使用子查詢?一個使用子查詢的SQL示例及其工作原理

何時使用子查詢&#xff1f;給出一個使用子查詢的SQL示例&#xff0c;并解釋其工作原理。 子查詢&#xff0c;也稱為內部查詢或嵌套查詢&#xff0c;是嵌入在另一個SQL查詢中的查詢。外部查詢&#xff0c;有時稱為外部查詢或主查詢&#xff0c;是包含子查詢的查詢。子查詢可以…

20240301作業

1.使用fwrite、fread將一張隨意的bmp圖片&#xff0c;修改成德國的國旗 #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> int main(int argc, const char *argv[]) {FILE* fp fopen("./gaoda.bmp","…

24.3.1 《CLR via C#》 筆記9

第十二章 泛型 泛型&#xff1a;支持泛型值類型、泛型引用類型、泛型接口、泛型委托&#xff1b;允許在引用類型、值類型和接口中定義泛型方法&#xff1b;泛型參數變量要么稱為T&#xff0c;要么以T開頭 具有泛型類型參數的類型稱為開放類型&#xff0c;不允許構造實例&#…

Java畢業設計 基于SpringBoot vue 社區團購系統

Java畢業設計 基于SpringBoot vue 社區團購系統 SpringBoot vue 社區團購系統 功能介紹 前端用戶: 首頁 圖片輪播 商品信息 商品分類展示 搜索 商品詳情 點我收藏 添加到購物車 立即購買 我要開團 去參團 評論 公告資訊 資訊詳情 登錄 注冊 個人中心 更新信息 點我充值 我的訂…

Mysql筆記3

1、快速創建表 原理&#xff1a; 將一個查詢結果當做一張表新建 這個可以完成表的快速復制 create table emp2 as select * from emp; mysql> select * from emp2; ---------------------------------------------------------------------- …

【vscode提取函數快捷鍵】提取函數,減少大方法的復雜度

在 Visual Studio Code 中&#xff0c;提取函數的快捷鍵取決于你所使用的編程語言和安裝的插件。以下是一些常用的快捷鍵組合&#xff0c;可以用來在 Visual Studio Code 中提取函數&#xff1a; 執行以下步驟來提取函數&#xff1a; 選中要提取的代碼塊。右鍵單擊選中的代碼…

System Verilog學習筆記(十二)——數組(2)

System Verilog學習筆記&#xff08;十二&#xff09;——數組&#xff08;2&#xff09; 動態數組 在編譯時不會為其定制尺寸&#xff0c;而是在仿真運行時來確定動態數組一開始為空&#xff0c;需要使用new[ ]來為其分配空間聲明方式 int dyn[],d2[]; //聲明了兩個動態數組…

git之遠程操作

一.分布式版本控制系統 分布式版本控制系統通常也有?臺充當“中央服務器”的電腦&#xff0c;但這個服務器的作?僅僅是?來?便“交換”?家的修改&#xff0c;沒有它?家也?樣?活&#xff0c;只是交換修改不?便?已。有了這個“中央服務器”的電腦&#xff0c;這樣就不怕…

ChatGPT學習第四周

&#x1f4d6; 學習目標 ChatGPT實踐操作 通過實際操作和練習&#xff0c;加深對ChatGPT功能的理解。 項目&#xff1a;創建一個ChatGPT應用案例 設計一個基于ChatGPT的小項目&#xff0c;將理論應用于實踐。 ?? 學習活動 學習資料 《萬字干貨&#xff01;ChatGPT 從零完…

[C++核心編程](一):內存分區

目錄 代碼區 全局區 棧區 堆區 new操作符 不同區域存放的數據&#xff0c;賦予不同的生命周期&#xff0c;給予開發人員更大的靈活編程。 代碼區 存放二進制代碼&#xff0c;由操作系統管理未執行程序&#xff08;.exe&#xff09;前已經存在共享&#xff0c;對頻繁執行…

達夢數據庫查詢語句內存溢出問題解決

背景&#xff1a;達夢數據庫使用過程中&#xff0c;某天突然服務宕機&#xff0c;導致各類后端服務無法注冊到nacos上&#xff0c;重啟之后nacos正常啟動&#xff0c;可執行一條兩千多條數據量的連表查詢時間很長&#xff0c;甚至會報錯&#xff0c;經查看日志發現在查詢過程中…

【C語言】常見的動態內存管理錯誤

前言 上一篇介紹了C語言中 動態內存管理函數&#xff0c;本片講解的是 在我們使用動態內存管理時 常見的錯誤&#xff0c;一起來看看吧~ 歡迎關注個人主頁&#xff1a;逸狼 創造不易&#xff0c;可以點點贊嗎~ 如有錯誤&#xff0c;歡迎指出~ 目錄 1.對NULL指針的解引?操作 錯…

什么是前端框架中的數據綁定(data binding)?有哪些類型的數據綁定?

聚沙成塔每天進步一點點 ? 專欄簡介 前端入門之旅&#xff1a;探索Web開發的奇妙世界 歡迎來到前端入門之旅&#xff01;感興趣的可以訂閱本專欄哦&#xff01;這個專欄是為那些對Web開發感興趣、剛剛踏入前端領域的朋友們量身打造的。無論你是完全的新手還是有一些基礎的開發…

YOLOv5白皮書-第Y4周:common.py文件解讀

YOLOv5白皮書-第Y4周:common.py文件解讀 YOLOv5白皮書-第Y4周:common.py文件解讀0.導入需要的包和基本配置1.基本組件1.1 autopad1.2 Conv1.3 Focus1.4 Bottleneck1.5 BottleneckCSP1.6 C31.7 SPP1.8 Concat1.9 Contract、Expand 2.重要類2.1 非極大值抑制&#xff08;NMS&…

vue3中的基本語法

目錄 基礎素材 vue3的優化 使用CompositionAPI理由 1. reactive() 函數 2. ref() 函數 2.1. ref的使用 2.2. 在 reactive 對象中訪問 ref 創建的響應式數據 3. isRef() 函數 4. toRefs() 函數 5. computed() 5.1. 通過 set()、get()方法創建一個可讀可寫的計算屬性 …

函數——遞歸6(c++)

角谷猜想 題目描述 日本一位中學生發現一個奇妙的 定理&#xff0c;請角谷教授證明&#xff0c;而教授 無能為力&#xff0c;于是產生了角谷猜想。 猜想的內容&#xff1a;任給一個自然數&#xff0c; 若為偶數則除以2&#xff0c;若為奇數則乘 3加1&#xff0c;得到一個新的…

git命令整理

一、什么是git Git 是為了幫助管理 Linux 內核開發而開發的一個開放源碼的版本控制軟件。 分布式管理系統&#xff0c;可以快速的查看文件各個版本的改動。比如在第5行加了一個單詞“Linux”&#xff0c;在第8行刪了一個單詞“Windows”。而圖片、視頻這些二進制文件&#xf…

PyTorch深度學習快速入門

PyTorch深度學習快速入門 1.PyTorch環境配置及安裝2.python編輯器的選擇、安裝、配置&#xff08;pycharm、JupyTer安裝&#xff09;3.為什么torch.cuda.is_available()返回false4.python學習中兩大法寶函數&#xff08;也可用在pytorch&#xff09;5.pycharm和jupyter&#xf…

golang goroutine 如何退出?

上一講說到調度器將maingoroutine推上舞臺&#xff0c;為它鋪好了道路&#xff0c;開始執行runtime.main函數。這一講&#xff0c;我們探索maingoroutine以及普通goroutine從執行到退出的整個過程。 //Themaingoroutine. funcmain(){ //gmaingoroutine&#xff0c;不再是g0了 …

Python列表中添加刪除元素不走彎路

1.append() 向列表中添加單個元素&#xff0c;一般用于尾部追加 list1 ["香妃", "乾隆", "賈南風", "趙飛燕", "漢武帝"]list1.append("周瑜") print(list1) # [香妃, 乾隆, 賈南風, 趙飛燕, 漢武帝, 周瑜]…