1. 程式人生 > >GSoap工作原理簡析

GSoap工作原理簡析

  1. 前言
    因為工作原因,接觸GSoap已有大半年時間,春節閒來無事,簡單寫一下半年來對GSoap的理解。

  2. 服務端

    int main() 
    {
        #ifdef WITH_OPENSSL
        //1、初始化OpenSSL(系統會自動初始化SSL,故可跳過此函式)
        //函式會呼叫SSL_library_init()、OpenSSL_add_all_algorithms()、SSL_load_error_strings()等,用於初始化SSL、載入加密演算法及錯誤資訊等。初始化後soap_ssl_init_done=1,表示已初始化以避免重複初始化。
        soap_ssl_init();
    
        //2、對於多執行緒應用,OpenSSL要求顯示安裝互斥鎖,由此需呼叫 CRYPTO_thread_setup()、CRYPTO_thread_cleanup()
    //函式定義:sslclient.c //作用: //1)新建41個鎖物件(crypto.h,巨集CRYPTO_NUM_LOCKS定義鎖數目) //2)設定獲取當前執行緒ID、動態建立鎖、進入/退出鎖、釋放鎖等回撥函式 if (CRYPTO_thread_setup()) { fprintf(stderr, "Cannot setup thread mutex\n"); exit(1); } #endif struct soap soap, *tsoap; //3、初始化GSoap,包括設定測試/傳送/接收日誌、accept/connect/close/send/recv回撥函式等。
    soap_init(&soap); //4、初始化SSL上下文環境 //定義於stdsoap2.cpp //作用:初始化 struct soap結構體、設定驗證結果回撥函式(主要用於輸出錯誤資訊) if (soap_ssl_server_context( &soap, SOAP_SSL_DEFAULT, // flag "server.pem", // 服務端金鑰檔案,當客戶端需對服務端進行身份驗證時,提供此引數 "password", // 服務端金鑰檔案的密碼
    "cacert.pem", // CA證書,包含所有可信任的客戶端證書。當服務端需對客戶端進行身份驗證時,提供此引數 NULL, // CA證書證書所在的路徑 。當希望避免在不安全系統中使用預設路徑儲存CA證書時,可在指定CA證書時直接指定路徑或在此引數指定路徑。 "dh512.pem", // DH(金鑰交換協議/演算法)檔名或位元組所表示的DH金鑰長(最小長度為 512, e.g."512"),若為 NULL,則使用RSA NULL, // 隨機檔案,若認證過程需利用包含隨機資料的檔案確定隨機性,則填寫隨機檔名,否則填寫 NULL NULL // 啟用 SSL會話快取的伺服器標識(標識必須唯一) )) { soap_print_fault(&soap, stderr); exit(1); } //5、設定等待請求與傳送/接受資料超時時間 soap.accept_timeout = 60; soap.send_timeout = soap.recv_timeout = 30; SOAP_SOCKET mbindSocket; //6、設定服務端IP、監聽埠號、請求佇列最大長度 //host=NULL,表示當前主機;backlog:請求佇列最大長度,Linux系統預設為128,Windows系統下無確定值。 SOAP_SOCKET mbindSocket = soap_bind(&soap, "127.0.0.1", 9000, 1000); if (!soap_valid_socket(mbindSocket)) { exit(1); } for (;;) { //7、等待客戶端的連線 //定義:stdsoap2.cpp //原理:首先利用select模型判斷判斷read/write/error socket狀態,然後呼叫accept() SOAP_SOCKET s = soap_accept(&soap); if (!soap_valid_socket(s)) { if (soap->errnum) { continue; // retry } soap_destroy(soap); // dealloc C++ data soap_end(soap); // dealloc data and clean up continue; } //8、建立新的soap執行時環境(Runtime enviroment),深拷貝,新建立的 soap 與原先的soap runtime enviroment 是相互獨立的 //should call soap_ssl_accept on a copy struct soap *tsoap = soap_copy(&soap); if (!tsoap) { continue; } pthread_t tid; pthread_create(&tid, NULL, &process_request, (void*)tsoap); } soap_destroy(&soap); // 釋放所有動態分配的物件,包括soap類例項連結串列clist中所有例項 soap_end(&soap); // 除類例項之外,刪除臨時資料和反序列化資料 包括屬性連結串列等臨時資料、記憶體分配 (malloc) 列表、關閉SSL連線和Socket soap_done(&soap); // 釋放ctx,包括Reset, close communications, and remove callbacks等。必須在CRYPTO_thread_cleanup之前呼叫。 CRYPTO_thread_cleanup(); // 取消各回調函式、銷燬鎖物件。在多執行緒環境中必須使用。 return 0; } void *process_request(void *soap) { pthread_detach(pthread_self()); // 將子執行緒的狀態設定為detached,則執行緒結束後將自動釋放所有資源。pthread_self():獲取執行緒本身的ID。非阻塞函式。 #ifdef WITH_OPENSSL //soap_ssl_accept():執行SSL/TLS握手,建立安全的SSL通道 //serve():處理客戶端請求 if (soap_ssl_accept(m_psoap) != SOAP_OK || serve() != SOAP_OK) { } #else if (serve() != SOAP_OK) { } #endif soap_destroy((struct soap*)soap); // 釋放所有動態分配的物件 soap_end((struct soap*)soap); // 除類例項之外,刪除臨時資料和反序列化資料 soap_free((struct soap*)soap); // 釋放 context return NULL; }
  3. soap_ssl_server_context:flags

    巨集 描述
    SOAP_SSL_NO_AUTHENTICATION 0x00 不啟用證書驗證,通常用於測試
    SOAP_SSL_REQUIRE_SERVER_AUTHENTICATION 0x01 服務端需對客戶端進行身份驗證
    SOAP_SSL_REQUIRE_CLIENT_AUTHENTICATION 0x02 客戶端需對服務端進行身份驗證
    SOAP_SSL_SKIP_HOST_CHECK 0x04 不檢查證書中主機的common name
    SOAP_SSL_ALLOW_EXPIRED_CERTIFICATE 0x08 不檢查證書的失效日期
    SOAP_SSL_NO_DEFAULT_CA_PATH 0x10 不使用 default_verify_paths
    SOAP_SSL_RSA 0x20 使用RSA
    SOAP_SSLv3 0x40 SSL v3 only
    SOAP_TLSv1 0x80 TLS v1 only
    SOAP_SSLv3_TLSv1 0x00 SSL v3 and TLS v1 support by default (no SSL v1/v2)
    SOAP_SSL_CLIENT 0x100 client context
    SOAP_SSL_DEFAULT SOAP_SSL_REQUIRE_SERVER_AUTHENTICATION“或操作符” SOAP_SSLv3_TLSv1
  4. soap_ssl_accept()
    簡介:soap_accept() 之後呼叫,用於執行 SSL/TLS 握手,建立安全的SSL通道。

    作用:
    1)fsslauth();
    // 實際呼叫的是ssl_auth_init()。

    2)若未初始化OpenSSL,呼叫soap_ssl_init()。

    3)soap->ctx = SSL_CTX_new(SSLv23_method());
    // 建立SSL所用的method、申請SSL會話的環境(建立新的 SSL_CTX物件 作為建立 TLS/SSL 的框架)

    4)SSL_CTX_set_mode(soap->ctx,SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_AUTO_RETRY);
    // 設定握手模式,允許寫完或部分寫完時成功返回,SSL_write(…, n) to return r with 0 < r < n。只有握手並且成功完成後,read/write才會返回

    5)RAND_load_file(soap->randfile, -1);
    // 從隨機數種子檔案中讀取資料,-1:讀取整個檔案

    6)SSL_CTX_load_verify_locations(soap->ctx, soap->cafile, soap->capath);
    // 載入證書:SSL_CTX*,證書檔案的名稱,證書檔案的路徑

    7)SSL_CTX_set_client_CA_list(soap->ctx, SSL_load_client_CA_file(soap->cafile));
    // SOAP_SSL_REQUIRE_CLIENT_AUTHENTICATION:需要客戶端驗證時,伺服器從CAfile載入可信任CA證書,併發往客戶端

    8)SSL_CTX_set_default_verify_paths(soap->ctx);
    // 若!SOAP_SSL_NO_DEFAULT_CA_PATH,即使用預設驗證路徑,則從預設路徑載入CA證書

    9)SSL_CTX_use_certificate_chain_file(soap->ctx, soap->keyfile);
    // 為SSL載入本應用證書所屬的證書鏈

    10)SSL_CTX_set_default_passwd_cb_userdata(soap->ctx, (void*)soap->password);
    // 設定讀取私鑰的密碼

    11)RSA *rsa = RSA_generate_key(SOAP_SSL_RSA_BITS, RSA_F4, NULL, NULL);
    SSL_CTX_set_tmp_rsa(soap->ctx, rsa);
    // 若使用RSA金鑰交換演算法,生成金鑰對; 設定RSA金鑰
    // 若採用非RSA金鑰交換演算法,則根據指定的演算法 生成並設定金鑰引數

    12)SSL_CTX_set_verify(soap->ctx, mode, soap->fsslverify);
    // 指定驗證方式,mode:是否驗證客戶端或服務端等,fsslverify:驗證結果回撥函式,主要用於當驗證失敗時輸出錯誤資訊。

    13)SSL_CTX_set_verify_depth(soap->ctx, 9);
    // 設定允許ctx可驗證的最大證書鏈深度

    14)struct soap *soap;
    soap->ssl = SSL_new(soap->ctx);
    // 申請一個SSL套接字

    15)BIO *bio = BIO_new_socket((int)sk, BIO_NOCLOSE);
    // 根據給定的sock返回一個socket型別的BIO SOAP_SOCKET sk —>>> 輸入引數

    16)SSL_set_bio(soap->ssl, bio, bio);
    // 將SSL與bio聯絡起來

    17)r = SSL_accept(soap->ssl);
    // 等待一個TLS/SSL客戶端啟動TLS/SSL握手

    18)tcp_select();
    // 採用select模型,判斷是否有新的客戶端連線或可讀資料到達

    19)若指定:SOAP_SSL_REQUIRE_CLIENT_AUTHENTICATION,即伺服器需對客戶端進行身份驗證,則:
    SSL_get_verify_result(soap->ssl);
    // 對SSL中證書進行驗證,並返回驗證結果
    // 若驗證通過,則:
    X509 *peer = SSL_get_peer_certificate(soap->ssl);
    // 從SSL套接字中提取對方的證書資訊
    X509_free(peer);
    // 釋放證書

    20)若soap指定了recv_timeout/send_timeout,則soap被設定為阻塞模式,否則為非阻塞模式。

  5. serve()
    soap_begin_serve(struct soap*):
    // 開始處理soap
    具體包括:
    1)soap_begin(soap);
    // 初始化soap的成員變數,並釋放可能存在的記憶體佔用

    2)soap_begin_recv(soap);
    // 準備接收資料。實際上在這裡就完成了報文的接收。
    // 內部呼叫了函式:#define soap_get1(soap) (((soap)->bufidx>=(soap)->buflen && soap_recv(soap)) ? EOF : (unsigned char)(soap)->buf[(soap)->bufidx++]),該函式將報文全部存入緩衝區soap->buf,bufidx標明報文長度,buflen標明buf緩衝區長度。

    3)soap_envelope_begin_in(struct soap *soap);
    // 從buf中找尋envelope開始標籤(begin),in代表是從buf中解析結構 out代表把結構填充到buf。

    4)soap_recv_header(struct soap *soap);
    // 解析xml資料放置於soap->header。

    5)soap_body_begin_in(soap));
    // 與前面的兩個函式功能類似。

    dispatch():
    // 將soap->tag與各介面名逐一對比,匹配成功後,呼叫相應介面函式。