openssl 使用非阻塞 bio

来源:http://www.cnblogs.com/vinson0526/archive/2016/03/04/5242397.html
-Advertisement-
Play Games

在 openssl 中使用非阻塞的bio方法紀要。通過與 epoll 的配合,完成高效的加密連接處理。


在項目中需要訪問 https 加密的網頁,為了保證併發性,需要用到非阻塞的 socket,搜索發現,這種使用場景的相關介紹不是很多,所以這裡記錄一下使用的過程。

在項目中,所使用的 ssl 庫是老牌 sll 庫 —— openssl。所使用的 io多路復用 技術是 epoll。

核心流程

整體流程與訪問非加密網站類似,不同之處在於有一下幾點:

  1. 在 socket 建立 tcp 連接之後,需要綁定 socket 句柄在 SSL 中
  2. 讀取,發送數據,使用 SSL 庫的方法,替代 linux 系統調用
  3. 關閉連接前,需要先執行 SSL 關閉流程

建立連接

首先,打開 socket 句柄,然後設置必要的屬性

 

1 int sock_fd = -1;
2 int flags = -1;
3 sock_fd = socket(AF_INET, SOCK_STREAM, 0);
4 flags = fcntl(sockfd, F_GETFL, 0);
5 fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

 

然後,將句柄加入 epoll 的管理

1 epoll_event ev;
2 ev.events = EPOLLIN | EPOLLOUT | EPOLLET
3 ev.data.ptr = your_ev_info;
4 epoll_ctl(epfd, EPOLL_CTL_ADD, url_item->sockfd, &ev);

現在,可以開始真正的連接過程了,與普通的 tcp 連接一樣,調用 connect 系統調用。在非阻塞 io 中,需要通過 connect 的返回值和 errno 來判斷連接狀態,採取不同的策略

 1 struct sockaddr_in serv_addr;
 2 
 3 if (connect(sock_fd, (sockaddr *) & serv_addr, sizeof (sockaddr)) < 0) {
 4     // 沒有立刻連接成功,需要判斷 errno
 5     if (errno != EINPROGRESS && errno != EINTR) {
 6         // 失敗了, 從epoll裡面幹掉
 7         epoll_ctl(epfd, EPOLL_CTL_DEL, sock_fd, NULL);
 8     }
 9 } else {
10     // 立刻成功了
11     prepare_connect_ssl(your_ev_info);
12 }

如果沒有立刻連接成功,在成功後,會觸發 epoll,我們需要在 your_ev_info 中,需要保存現在的狀態,以便在  epoll_wait 之後,通過狀態來決定需要調用的函數。這些屬於 epoll 的細節了,在此不展開說。

假設,現在已經連接成功,則開始做 SSL 握手之前的準備工作。

1 SSL_CTX *ssl_ctx;
2 SSL *ssl;
3 
4 ssl_ctx = SSL_CTX_new(TLSv1_method());
5 ssl = SSL_new(url_item->ssl_ctx);
6 SSL_set_mode(url_item->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
7 
8 // 綁定 SSL 和 socket 句柄
9 SSL_set_fd(ssl, sock_fd);

這一步之所以和後面的 SSL 握手過程分開,是因為 SSL 握手在非阻塞io 的情況下,有可能會被調用多次,而這部分只需要一次調用即可。

現在開始 SSL 握手

 1 int ssl_conn_ret = SSL_connect(ssl);
 2 if (1 == ssl_conn_ret) {
 3     // 開始和對端交互
 4 } else if (-1 == ssl_conn_ret) {
 5     // 沒有立刻握手成功,需要通過錯誤碼來判斷現在的狀態
 6     int ssl_conn_err = SSL_get_error(ssl, ssl_conn_ret);
 7     if (SSL_ERROR_WANT_READ == ssl_conn_err || 
 8              SSL_ERROR_WANT_WRITE == ssl_conn_err) {
 9          //需要更多時間來進行握手
10     }
11 } else {
12     // 連接失敗了,做必要處理
13     if (0 != ssl_conn_ret) {
14         SSL_shutdown(ssl);
15     }
16     SSL_free(ssl);
17     SSL_CTX_free(ssl_ctx);
18 }

在沒有立刻握手成功的時候,需要在 epoll 觸發後,在次調用此段代碼,來繼續握手的過程。

至此,建立連接的過程就完成了。

發送與讀取數據

由於發送與讀取數據都有可能沒有完全完成我們所指定的長度,所以需要判斷對應返回值,來決定是否繼續發送或讀取

 

 

1 // 發送數據
2 int ret = SSL_write(ssl, buf + last_write_pos, buf_len - last_write_pos);
3 
4 // 讀取數據
5 int ret = SSL_read(ssl, buf + last_read_pos, buf_len - last_read_pos);

 

關閉連接

 

// 關閉 ssl 連接
SSL_shutdown(ssl);
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);

// 然後關閉 socket
close(sock_fd);

要點記錄

在使用過程中,整體流程是十分順利的。一個最重要的點是關於 openssl 與 epoll 的邊緣觸發配合的問題。

當需要使用 epoll 的邊緣觸發時,一定要註意,SSL_read 最多只會讀取一個完整的加密段,所以,當一次可以讀取的數據量大於此值時,需要迴圈調用 SSL_read 直到讀取失敗為止。否則,就會導致在緩衝區中的數據沒有完全讀取的情況。

 


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

-Advertisement-
Play Games
更多相關文章
  • 在C#中,如果要實現兩個列表的左鏈接查詢,我們的一般用法就是用的linq表達式就是 List<Pet> pets = new List<Pet>{ new Pet { Name="Barley", Age=8 }, new Pet { Name="Boots", Age=4 }, new Pet {
  • 本人也尚在學習使用之中,錯誤之處請大家指正。 開發環境:vs2015 UP1 項目環境:asp.net 4.6.1 模板為:asp.net 5 模板 identity版本為:asp.net identity 3.0.0 如圖: 建成後的項目已經和之前的模板建成的項目有非常大的不同了。identity
  • 每次寫分頁導航的時候都要在html頁面寫一堆標簽和樣式,太麻煩了,所以乾脆自己動手封裝一個自己喜歡的類直接生成。 一、PageHelper類: /// <summary> /// 分頁導航 /// </summary> /// <param name="pageNum">當前第幾頁</param>
  • 好久沒有寫博客了,這段時間準備寫一下字元串函數 QQ群: 499092562;歡迎交流 字元串函數: 1、LEN(需要獲取長度的字元串) 返回:字元串的長度 示例: SELECT LEN('小搬運工很帥!') 2、RIGHT(需要被從右邊截取的字元串,截取的開始下標,截取的長度) 返回:右邊的字元串
  • http://www.ourd3js.com/wordpress/
  • Lua中的協程和多線程很相似,每一個協程有自己的堆棧,自己的局部變數,可以通過yield-resume實現在協程間的切換。不同之處是:Lua協程是非搶占式的多線程,必須手動在不同的協程間切換,且同一時刻只能有一個協程在運行。並且Lua中的協程無法在外部將其停止,而且有可能導致程式阻塞。 協同程式(C
  • 2014年3月,Java 8發佈,Lambda表達式作為一項重要的特性隨之而來。或許現在你已經在使用Lambda表達式來書寫簡潔靈活的代碼。比如,你可以使用Lambda表達式和新增的流相關的API,完成如下的大量數據的查詢處理: int total = invoices.stream() .filt
  • Queue介面與List、Set同一級別,都是繼承了Collection介面。LinkedList實現了Queue接 口。Queue介面窄化了對LinkedList的方法的訪問許可權(即在方法中的參數類型如果是Queue時,就完全只能訪問Queue介面所定義的方法 了,而不能直接訪問 LinkedLi
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...