程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> openssl 使用非阻塞 bio,opensslbio

openssl 使用非阻塞 bio,opensslbio

編輯:C++入門知識

openssl 使用非阻塞 bio,opensslbio


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

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

核心流程

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

建立連接

首先,打開 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 直到讀取失敗為止。否則,就會導致在緩沖區中的數據沒有完全讀取的情況。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved