linux環境編程(3): 使用POSIX IPC完成進程間通信

来源:https://www.cnblogs.com/kfggww/archive/2023/02/06/17087335.html
-Advertisement-
Play Games

1. 寫在前面 之前的文章總結了使用管道進行進程間通信的方法,除了pipe和fifo,Linux內核還為我們提供了其他更高級的IPC方式,包括共用記憶體,消息隊列,信號量等,本篇文章會通過一個具有完整邏輯功能的示例說明如何使用這些IPC方法。畢竟單純地查手冊,寫代碼...周而複始,這個過程還是比較枯燥 ...


1. 寫在前面

之前的文章總結了使用管道進行進程間通信的方法,除了pipe和fifo,Linux內核還為我們提供了其他更高級的IPC方式,包括共用記憶體,消息隊列,信號量等,本篇文章會通過一個具有完整邏輯功能的示例說明如何使用這些IPC方法。畢竟單純地查手冊,寫代碼...周而複始,這個過程還是比較枯燥的,而且並沒有哪個IPC方法能解決所有的進程間通信問題,每種方法都不是孤立存在的,通過一個小例子把它們串聯起來,是一種更好的學習方式。下文中的代碼實現可以參考我的代碼倉庫

2. POSIX IPC概述

進程間通信,主要解決兩個問題,即數據傳遞和同步。POSIX IPC提供了下麵三種方法:

  • 消息隊列
  • 共用記憶體
  • 信號量

操作系統中運行的進程,彼此之間是隔離的,要想實現通信,就必須有一個媒介,是通信雙方都可以訪問到的。從這個角度看,操作系統內核正是每個進程都可以訪問到的那個媒介,就像一個"全局變數"。消息隊列不過是內核維護的一個隊列,保存了用戶進程發送來的消息,其他進程可以從隊列中取走消息,每個消息可以設置優先順序,進程發送和接收消息的行為可以是阻塞或者非阻塞的,這一點類似管道;共用記憶體就是利用了虛擬地址空間以及物理地址空間,讓不同進程的虛擬地址映射到同一個物理頁面上,這樣就實現了共用,對於映射的地址空間可以設定可讀,可寫以及可執行的標誌;信號量就像一個內核中的整型變數,這個變數的數值記錄了某種資源的數量,進程可以對它進行加減操作,合理使用的話就能完成想要的進程之間的同步邏輯。可以看到,這三種IPC方法在內核中都對應了一種數據結構,為了能夠讓用戶進程訪問到這些數據結構,POSIX IPC延續了“一切皆文件”的設計思路,我們可以用類似“/somename”這種形式的文件名去創建或者打開這些IPC對象,然後對它們進行各種操作,和文件的訪問許可權類似,進程操作IPC對象時也會進行許可權檢查。可能上面對三種POSIX IPC的描述存在不嚴謹的地方,但對於使用者來說,我們只要在腦子裡建立一個合適的,能夠描述它們工作方式的模型就可以了,而不是不斷重覆手冊中對每個api的敘述。下麵的表格列出了常用的POSIX IPC api:

消息隊列 共用記憶體 信號量
打開 mq_open shm_open sem_open
關閉 mq_close shm_close sem_close
操作 mq_send/mq_receive 記憶體讀寫 sem_wait/sem_post
刪除 mq_unlink shm_unlink sem_unlink

3. POSIX IPC使用

3.1 項目功能說明

下麵將使用三種POSIX IPC實現一個簡單的項目,用來記錄IPC的使用方法。項目包含一個server進程和若幹個client進程,他們各自的功能如下:

  • server進程
    • 首先運行,等待client的連接到來;
    • 收到client的連接,fork出一個新的進程去處理client的請求;
  • client進程
    • 可以同時運行多個;
    • 啟動時和server建立連接,連接建立完成之後,接受用於輸入,向server發起請求;
    • 可以完成主動斷開連接,終止server進程,以及其他操作;

首先啟動server進程,然後啟動多個client進程向server發送請求,項目實現之後的效果如下:

img

3.2 項目實現原理

  1. client如何和server建立連接?

    client進程和server進程都可以訪問一段共用記憶體,當server進程啟動時,會對這段共用記憶體進行初始化,初始化完成之後,server對信號量A執行post操作,表明共用記憶體準備完畢,之後server進程就通過信號量B等待新連接的建立;當有新的client進程想建立連接時,會先通過對信號量A執行wait操作,等待共用記憶體可用,如果可用,client會把請求參數寫到共用記憶體之中,寫入完成後會對信號量B執行post操作,通知server進程有新的連接已經建立。

  2. client建立連接之後如何發送請求?

    client通過兩個消息隊列實現發送請求和接收響應。在client建立連接時,會在共用記憶體中寫入用於和server通信的兩個消息隊列的名字,server在處理連接時會打開消息隊列,然後和client進行通信。對於每個新建立的連接,server會fork出一個新的進程去處理該連接對應的client發送來的請求。

  3. client如何通過發送請求關閉server?

    client通過向請求消息隊列中寫入kill_server請求,可以實現關閉server。當server進程fork出的進程從消息隊列中讀到kill_server請求,該進程會通過管道寫入數據,通知server的主進程結束運行。

  4. server和client之間的時序關係:

    通過前面3點的描述可以看出,這個簡單的項目幾乎用到了全部常用的IPC方法,下麵這個時序圖更直觀地說明瞭其工作原理:

    img

    server和client之間的同步操作主要集中在步驟6,7,8,9。當server準備好共用記憶體之後,通過第6步的信號量A通知client可以建立連接了,之後client向共用記憶體寫入數據,再操作第9步的信號量B通知server連接數據已經寫入,最後server會創建子進程去處理client的請求。實際上server的主進程是一個迴圈,處理請求都是在server的子進程中完成的,以上內容說明瞭server主進程在迴圈中完成的工作。

  5. 資源清理

    當我們使用POSIX IPC時,內核會建立相應的數據結構,並且通過文件系統介面展示給用戶,但IPC資源不能無限創建,當我們的程式運行結束之後應該清理自己用到的IPC資源。運行程式時創建的POSIX IPC對象可以在/dev/shm以及/dev/mqueue下查看,程式結束之後,server和client會釋放掉自己創建的IPC資源。所以,要查看server和client創建的共用記憶體,信號量以及消息隊列,需要在程式運行期間查看上述的兩個目錄。

3.3 主要代碼功能

  • 消息格式:

    server和client之間通過消息隊列傳遞請求和響應數據,消息隊列中消息格式定義如下:

    struct msgbuf {
        int type;
        union {
            struct {
                int a;
                int b;
            } request_add;
    
            struct {
                int c;
            } response_add;
    
            struct {
                int disconect;
            } request_disconnect;
    
            struct {
                int kill_server;
            } request_kill_server;
        } data;
    };
    
  • server主進程:

    int main(int argc, char **argv) {
    
        int err = server_init();
        if (err) {
            log_warning("server_init failed\n");
            return -1;
        }
        server_start();
        server_shutdown();
    
        return 0;
    }
    

    其中,在server_init中,server會創建需要使用的共用記憶體,信號量以及管道。

    int server_init() {
        memset(&ipc_server, 0, sizeof(ipc_server));
    
        // shared memory init
        ipc_server.conn_buf_fd =
            shm_open(CONNECTION_SHM, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    
        ...
    
        if (ftruncate(ipc_server.conn_buf_fd, CONNECTION_SHM_SIZE) < 0) {
            log_warning("server failed ftruncate\n");
            return -1;
        }
    
        ipc_server.conn_buf = (struct connection *)mmap(
            NULL, CONNECTION_SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
            ipc_server.conn_buf_fd, 0);
    
        ...
    
        memset(ipc_server.conn_buf, 0, CONNECTION_SHM_SIZE);
    
        ipc_server.conn_buf_ready =
            sem_open(CONNECTION_BUF_SEM, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, 0);
    
        ...
    
        ipc_server.conn_new_ready =
            sem_open(CONNECTION_NEW_SEM, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, 0);
        
        ...
    
        // pipe init
        int pipefd[2];
        if (pipe2(ipc_server.pipefd, O_NONBLOCK)) {
            log_warning("server failed pipe2\n");
            return -1;
        }
    
        log_info("server init done\n");
        return 0;
    }
    

    在server_start中會迴圈處理來自client的連接。

    void server_start() {
        int err = sem_post(ipc_server.conn_buf_ready);
    
        ...
    
        struct connection conn;
        int stop = 0;
        while (!stop) {
            // handle new connection
            sem_wait(ipc_server.conn_new_ready);
            if (read(ipc_server.pipefd[0], &stop, sizeof(int)) <= 0)
                stop = 0;
    
            if (ipc_server.conn_buf->valid) {
                log_info("new connection established\n");
                memcpy(&conn, ipc_server.conn_buf, sizeof(conn));
                handle_connection(&conn);
                memset(ipc_server.conn_buf, 0, sizeof(struct connection));
                sem_post(ipc_server.conn_buf_ready);
            }
        }
    }
    

    當server主進程退出之後,server_shutdown會清理IPC資源。

  • client進程:

    client啟動之後,首先會嘗試和server建立連接,建立連接之後會迴圈處理用戶輸入,通過消息隊列向server的服務進程發送請求。

    int main(int argc, char **argv) {
    
        if (build_connection()) {
            log_info("client failed build_connection\n");
            return -1;
        }
        handle_command();
        cleanup();
    
        log_info("client %d exit\n", getpid());
        return 0;
    }
    

    client建立連接的過程如下:建立連接時client需要等待共用記憶體可用,並且在寫入連接數據之後通知server,這些同步操作都是通過信號量實現的。

    int build_connection() {
        
        ...
    
        connection.mqreq_fd =
            mq_open(connection.mqreq, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, &attr);
        connection.mqrsp_fd =
            mq_open(connection.mqrsp, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, &attr);
    
        ...
    
        // open and map shared memory
        int fd = shm_open(CONNECTION_SHM, O_RDWR, 0);
        void *conn_buf = mmap(NULL, CONNECTION_SHM_SIZE, PROT_READ | PROT_WRITE,
                            MAP_SHARED, fd, 0);
    
        // write connection to conn_buf, notify server new conncection is comming
        sem_t *conn_buf_ready = sem_open(CONNECTION_BUF_SEM, O_RDWR);
        sem_t *conn_new_ready = sem_open(CONNECTION_NEW_SEM, O_RDWR);
    
        ...
    
        sem_wait(conn_buf_ready);
        connection.valid = 1;
        memcpy(conn_buf, &connection, sizeof(connection));
        sem_post(conn_new_ready);
    
        ...
    
        return 0;
    }
    

寫在最後

通過一個包含server和client的代碼示例,說明瞭POSIX IPC中共用記憶體,消息隊列以及信號量的使用方法。具體實現可以參考我的代碼倉庫

本文來自博客園,作者:kfggww,轉載請註明原文鏈接:https://www.cnblogs.com/kfggww/p/17087335.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • https://www.cnblogs.com/yeungchie/ 函數功能 Perl 中 shift 函數可以返回列表的第一個元素,並將後續所有元素向前移位( 索引值減 1 )。輸入可選,預設為 @_ 或者 @ARGV。 my @foo = qw( 1 2 3 4 ); say shift @f ...
  • 今日內容概要 昨日回顧 反序列化類校驗部分源碼解析(瞭解) 斷言 drf之請求(配置解析類) drf之響應(配置響應類,Resposne的源碼的屬性) 視圖組件介紹及兩個視圖基類與五個視圖擴展類的用法介紹 . . . . 昨日回顧 # 1 序列化類的常用欄位 -CharField 。。。。 -Lis ...
  • Eureka是Netflix開發的服務發現框架,本身是一個基於REST的服務,主要用於定位運行在AWS域中的中間層服務,以達到負載均衡和中間層服務故障轉移的目的。Eureka提供了完整的Service Registry和Service Discovery實現實現,也是Spring Cloud體系中最... ...
  • 前言 最近有項目需要開發檔案打包下載功能,其中包含很多大附件,項目使用minio存儲且不在同一臺伺服器上,為了優化速度決定使用windows共用功能進行文件傳輸 SMB1.0 集成jcifs類庫,主要適用於一些老舊系統,但下載速度比較慢,僅作參考 此類庫沒有maven引用,官網地址:http://j ...
  • 原文鏈接 https://bysocket.com/nginx-unexpected-end-of-file-expecting-in-key-file/ 一、Nginx unexpected end of file 問題 通過 letsencrypt 申請證書後,預設伺服器安裝了 Nginx 1. ...
  • 寫在前面 很早之前的電腦ThinkPad E440,一直沒怎麼用。最近整理了一下電腦的資料,全部備份到雲盤。整理的過程感覺電腦很慢很慢,難受極了。整理完後,終於我要對它下手了! 我製作了啟動U盤,把Ubuntu 22.04的鏡像燒錄進去,通過U盤啟動,把系統裝在ThinkPad上。居然電腦出奇的好用 ...
  • 背景 REST作為一種現代網路應用非常流行的軟體架構風格,自從Roy Fielding博士在2000年他的博士論文中提出來到現在已經有了20年的歷史。它的簡單易用性,可擴展性,伸縮性受到廣大Web開發者的喜愛。 REST 的 API 配合JSON格式的數據交換,使得前後端分離、數據交互變得非常容易, ...
  • 賽事介紹 賽事起源 CTF(Capture The Flag)中文譯作奪旗賽,原為西方傳統運動,兩隊人馬互相前往對方的基地奪取旗幟。在網路空間安全領域被用來指代技術人員之間進行技術競技的比賽形式。 CTF起源於1996年的DEFCON全球黑客大賽,用於代替黑客間發起真是攻擊的競賽形式。DEFCON ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...