openssl之BIO系列之16---BIO對(pair)類型BIO,openssl16---bio
BIO對(pair)類型BIO
---根據openssl doc\crypto\bio_s_bio.pod翻譯和自己的理解寫成
(作者:DragonKing, Mail: wzhah@263.net ,發佈於:http://gdwzh.126.com之o
penssl專業論壇)
前面我們已經介紹過BIO對的概念,其實更進一步,BIO對也是作為一種source/sin
k類型的BIO來處理的,也就是說,BIO裡面還提供了一種專門的BIO_METHO方法來處理BI
O對的各種操作。BIO對類型的BIO各種相關的函數定義如下(openssl\bio.h):
BIO_METHOD *BIO_s_bio(void);
#define BIO_make_bio_pair(b1,b2) (int)BIO_ctrl(b1,BIO_C_MAKE_BIO_PAIR,0
,b2)
#define BIO_destroy_bio_pair(b) (int)BIO_ctrl(b,BIO_C_DESTROY_BIO_PAIR,
0,NULL)
#define BIO_shutdown_wr(b) (int)BIO_ctrl(b, BIO_C_SHUTDOWN_WR, 0, NULL)
#define BIO_set_write_buf_size(b,size) (int)BIO_ctrl(b,BIO_C_SET_WRITE_
BUF_SIZE,size,NULL)
#define BIO_get_write_buf_size(b,size) (size_t)BIO_ctrl(b,BIO_C_GET_WRI
TE_BUF_SIZE,size,NULL)
int BIO_new_bio_pair(BIO **bio1, size_t writebuf1, BIO **bio2, size_t w
ritebuf2);
#define BIO_get_write_guarantee(b) (int)BIO_ctrl(b,BIO_C_GET_WRITE_GUAR
ANTEE,0,NULL)
size_t BIO_ctrl_get_write_guarantee(BIO *b);
#define BIO_get_read_request(b) (int)BIO_ctrl(b,BIO_C_GET_READ_REQUEST,
0,NULL)
size_t BIO_ctrl_get_read_request(BIO *b);
int BIO_ctrl_reset_read_request(BIO *b);
可以看到,這些函數中大多數是宏定義函數並且都是基於BIO_ctrl函數的。
BIO對類型的BIO是一對source/sink型的BIO,資料通常是從一個BIO緩衝寫入,從另
一個BIO讀出。其實,從原始碼(bss_bio.c)可以看出,所謂的BIO對只是將兩個BIO的
終端輸出(BIO結構中參數peer的ptr成員)相互設定為對方,從而形成一種對稱的結構
,如下:
bio1->peer->ptr=bio2
bio2->peer->ptr=bio1
資料流向1(寫bio1,讀bio2):--->bio1--->bio2--->
資料流行2(寫bio2,讀bio1):--->bio2--->bio1--->
因為沒有提供內部資料結構的記憶體鎖結構(lock),所以,一般來說這個BIO對的兩個
BIO都必須在一個線程下使用。因為BIO鏈通常是以一個source/sink BIO結束的,所以就
可以實現應用程式通過控制BIO對的一個BIO從而控制整個BIO鏈的資料處理。其實,也就
相當於BIO對給應用程式提供了一個處理整個BIO鏈的入口。上次我們說BIO對的時候就說
過,BIO對的一個典型應用就是在應用程式裡面控制TLS/SSL的I/O介面,一般來說,在應
用程式想在TLS/SSL中使用非標準的傳輸方法或者不適合使用標準的socket方法的時候就
可以採用這樣的方法來實現。
前面提過,BIO對釋放的時候,需要分別釋放兩個BIO,如果在使用BIO_free或者BI
O_free_all釋放了其中一個BIO的時候,另一個BIO就也必須要釋放。
當BIO對使用在雙嚮應用程式的時候,如TLS/SSL,一定要對寫緩衝區裡面的資料執
行flush操作。當然,也可以通過在BIO對中的另一個BIO調用BIO_pending函數,如果有
資料在緩衝區中,那麼就將它們讀出並發送到底層的傳輸通道中區。為了使請求或BIO_
should_read函數調用成功(為true),在執行任何正常的操作(如select)之前,都必
須這樣做才行。
下面舉一個例子說明執行flush操作的重要性:
考慮在TLS/SSL握手過程中,採用了BIO_write函數發送了資料,相應的操作應該使
BIO_read。BIO_write操作成功執行並將資料寫入到寫緩衝區中。BIO_read調用開始會失
敗,BIO_should_retry返回true。如果此時對寫緩衝區不執行flush操作,那麼BIO_rea
d調用永遠不會成功,因為底層傳輸通道會一直等待直到資料有效(但資料卻在寫緩衝區
裡,沒有傳到底層通道)。
【BIO_s_bio】
該函數返回一個BIO對類型的BIO_METHOD,其定義如下:
static BIO_METHOD methods_biop =
{ BIO_TYPE_BIO,
"BIO pair",
bio_write,
bio_read,
bio_puts,
NULL /* 沒有定義 bio_gets */,
bio_ctrl,
bio_new,
bio_free,
NULL /* 沒有定義 bio_callback_ctrl */
};
從定義中可以看到,該類型的BIO不支援BIO_gets的功能。
BIO_read函數從緩衝BIO中讀取資料,如果沒有資料,則發出一個重試請求。
BIO_write函數往緩衝BIO中寫入資料,如果緩衝區已滿,則發出一個重試請求。
BIO_ctrl_pending和BIO_ctrl_wpending函數可以用來查看在讀或寫緩衝區裡面有效
的資料的數量。
BIO_reset函數將寫緩衝區裡面的資料清除。
【BIO_make_bio_pair】
該函數將兩個單獨的BIO串連起來成為一個BIO對。
【BIO_destroy_pair】
該函數跟上面的函數相反,它將兩個串連起來的BIO對拆開;如果一個BIO對中的任
何一個BIO被釋放,該操作會自動執行。
【BIO_shutdown_wr】
該函數關閉BIO對的其中一個BIO,一個BIO被關閉後,針對該BIO的任何寫操作都會
返回錯誤。從另一個BIO讀資料的時候要麼返回剩餘的有效資料,要麼返回EOF。
【BIO_set_write_buf_size】
該函數設定BIO的緩衝區大小。如果該BIO的緩衝區大小沒有初始化,那麼就會使用
預設的值,大小為17k,這對於一個TLS記錄來說是足夠大的了。
【BIO_get_write_buf_size】
該函數返回寫緩衝區的大小。
【BIO_new_bio_pair】
該函數我們在前面的《BIO系列之9---BIO對的建立和應用》中已經做了詳細的介紹
,其實,它是調用了BIO_new,BIO_make_bio_pair和BIO_set_write_buf_size函數來建立
一對BIO對的。如果兩個緩衝區長度的參數都為零,那麼就會使用預設的緩衝區長度。
【BIO_get_write_guarantee和BIO_ctrl_get_write_guarantee】
這兩個函數返回當前能夠寫入BIO的資料的最大長度。如果往BIO寫入的資料長度比
該函數返回的資料長度大,那麼BIO_write返回的寫入資料長度會小於要求寫入的資料,
如果緩衝區已經滿了,則會發出一個重試的請求。這兩個函數的唯一不同之處是一個使
用函數實現的,一個是使用宏定義實現的。
【BIO_get_read_request和BIO_ctrl_get_read_request】
這兩個函數返回要求發送的資料的長度,這通常是在對該BIO對的另一個BIO執行讀
操作時因為緩衝區資料為空白導致失敗時發出的請求。所以,這通常用來表明現在應該寫
入多少資料才能使接下來的讀操作能夠成功執行,這在TLS/SSL應用程式中是非常有用的
,因為對於這個協議來說,讀取的資料長度比緩衝區的資料長度通常要有意義的多。如
果在讀操作成功之後調用這兩個函數會返回0,如果在調用該函數之前有新的資料寫入(
不管是部分還是全部滿足需要讀取的資料的要求),那麼調用該函數也會返回0。理所當
然,該函數返回的資料長度肯定不會大於BIO_get_write_guarantee函數返回的資料長度
。
【BIO_ctrl_reset_read_request】
該函數就是把BIO_get_read_request要返回值設定為0。
【參考文檔】
《BIO系列之9---BIO對的建立和應用》