Web服務器為了支持https訪問,通常會使用第三方庫openssl實現,而且為了高性能采用異步事件驅動模型,因此連接套接字被設為非阻塞類型,本文在nginx ssl模塊的基礎上,簡化提取它的核心框架,使用面向對象的方式描述,從握手、讀寫和關閉3個方面進行了分析,由于這3個操作都是異步的,因此操作失敗后要調用SSL_get_error來獲取錯誤碼,有如下4種情況。
● SSL_ERROR_WANT_READ:操作沒完成,需要在下一次讀事件中繼續
● SSL_ERROR_WANT_WRITE:操作沒完成,需要在下一次寫事件中繼續
● SSL_ERROR_ZERO_RETURN:連接正常關閉
● 其它:連接出錯
下面的示例代碼使用了libevent實現IO事件驅動,connection表示普通連接類,假設已經處理好了http數據的邏輯,實現在成員函數handle_read和handle_write內,虛函數recv、send和close分別調用系統的API recv、send和close;ssl_conn_t表示安全連接類,由于只是IO處理不同:收到數據后解密,發送數據前加密。解密后的操作和connection是一樣的,因此ssl_conn_t繼承connection,重寫了recv、send和close,其中close調用了shutdown。
異步握手
當在SSL端口接受到連接時,首先要進行握手,握手成功后才能收發數據,如果握手失敗而且返回前2種錯誤碼,那么要在下一次操作中繼續握手。
1
void ssl_conn_t::empty_handler(short ev)
2

{
3
}
4
5
void ssl_conn_t::handshake_handler(short ev)
6

{
7
handshake();
8
}
9
10
void ssl_conn_t::handshake()
11

{
12
int ret = do_handshake();
13
14
switch(ret)
{
15
case OP_OK:
16
read_handler_ = &connection::handle_read;
17
write_handler_ = &connection::handle_write;
18
handle_read(EV_READ);
19
break;
20
21
}
22
}
23
24
int ssl_conn_t::do_handshake()
25

{
26
ssl_clear_error();
27
28
int ret = SSL_do_handshake(ssl_);
29
if(1==ret)
{
30

31
return OP_OK;
32
}
33
34
int sslerr = SSL_get_error(ssl_,ret), err;
35
switch(sslerr)
{
36
case SSL_ERROR_WANT_READ:
37
read_handler_ = (io_handler)&ssl_conn_t::handshake_handler;
38
write_handler_ = (io_handler)&ssl_conn_t::empty_handler;
39
return OP_AGAIN;
40
41
case SSL_ERROR_WANT_WRITE:
42
read_handler_ = (io_handler)&ssl_conn_t::empty_handler;
43
write_handler_ = (io_handler)&ssl_conn_t::handshake_handler;
44
return OP_AGAIN;
45

46
}
47
}
以上do_handshake是核心函數,調用了SSL_do_handshake來實現握手,當SSL_do_handshake失敗時,如果返回SSL_ERROR_WANT_READ,就改變讀函數指針為handshake_handler,寫函數指針為empty_handler;如果返回SSL_ERROR_WANT_WRITE,就改變讀函數指針為empty_handler,寫函數指針為handshake_handler。handshake_handler實現在讀寫事件中繼續處理握手,而empty_handler是個空函數,什么也不做。
異步讀寫
對于讀操作失敗,如果返回SSL_ERROR_WANT_WRITE,那么說明要在下一次寫事件中繼續讀數據,因此要改變寫函數指針,使其讀數據,當讀成功后,要還原寫函數針,并激發一次寫事件;對于寫操作失敗,如果返回SSL_ERROR_WANT_READ,那么說明要在下一次讀事件中繼續寫數據,因此要改變讀函數指針,使其寫數據,當寫成功后,要還原讀函數指針,并激發一次讀事件。如果不還原讀或寫函數指針,那么會發生寫或讀混亂;還原后,要激發一次讀或寫事件,這是為了延續IO事件的進行,防止讀寫餓死。
1
ssize_t ssl_conn_t::recv(void *buf,size_t len)
2

{
3
ssl_clear_error();
4
5
int ret = SSL_read(ssl_,buf,len);
6
if(ret>0)
{
7
if(old_write_handler_)
{
8
write_handler_ = old_write_handler_;
9
old_write_handler_ = NULL;
10
active_event(false);
11
}
12
return ret;
13
}
14
15
int sslerr = SSL_get_error(ssl_,ret), err;
16
switch(sslerr)
{
17
case SSL_ERROR_WANT_READ:
18
return OP_AGAIN;
19
20
case SSL_ERROR_WANT_WRITE:
21
if(NULL==old_write_handler_)
{
22
old_write_handler_ = write_handler_;
23
write_handler_ = (io_handler)&ssl_conn_t::write_handler;
24
}
25
return OP_AGAIN;
26

27
}
28
}
29
30
void ssl_conn_t::write_handler(short ev)
31

{
32
(this->*read_handler_)(EV_WRITE);
33
}
34
35
ssize_t ssl_conn_t::send(const void *buf,size_t len)
36

{
37
ssl_clear_error();
38
39
int ret = SSL_write(ssl_,buf,len);
40
if(ret>0)
{
41
if(old_read_handler_)
{
42
read_handler_ = old_read_handler_;
43
old_read_handler_ = NULL;
44
active_event(true);
45
}
46
return ret;
47
}
48
49
int sslerr = SSL_get_error(ssl_,ret), err;
50
switch(sslerr)
{
51
case SSL_ERROR_WANT_WRITE:
52
return OP_AGAIN;
53
54
case SSL_ERROR_WANT_READ:
55
if(NULL==old_read_handler_)
{
56
old_read_handler_ = read_handler_;
57
read_handler_ = (io_handler)&ssl_conn_t::read_handler;
58
}
59
return OP_AGAIN;
60

61
}
62
}
63
64
void ssl_conn_t::read_handler(short ev)
65

{
66
(this->*write_handler_)(EV_READ);
67
}
以上recv調用SSL_read,如果失敗并且返回SSL_ERROR_WANT_WRITE,就保存老的寫函數指針,改變寫函數指針為write_handler,write_handler實現在寫事件中繼續讀數據;send調用SSL_write,如果失敗并且返回SSL_ERROR_WANT_READ,就保存老的讀函數指針,改變讀函數指針為read_handler,read_handler實現在讀事件中繼續寫數據。
異步關閉
當握手或讀寫因連接關閉或出錯而失敗時,就要關閉連接了,如果失敗并且返回SSL_ERROR_WANT_READ或SSL_ERROR_WANT_WRITE,那么要在下一次讀或寫事件中繼續關閉。在這里,為了收到對方發送的協議退出包而完全退出,等待30秒再關閉,如果超時就直接關閉。
1
void ssl_conn_t::shutdown(bool is_timeout/**//*=false*/)
2

{
3
if (do_shutdown(is_timeout) != OP_AGAIN)
4
delete this;
5
}
6
7
int ssl_conn_t::do_shutdown(bool is_timeout)
8

{
9
int ret,mode;
10
11
if(is_timeout)
{
12
mode = SSL_RECEIVED_SHUTDOWN|SSL_SENT_SHUTDOWN;
13
SSL_set_quiet_shutdown(ssl_,1);
14
}else
{
15

16
}
17
SSL_set_shutdown(ssl_,mode);
18
19
ssl_clear_error();
20
21
ret = SSL_shutdown(ssl_);
22
if(1==ret)
23
return OP_OK;
24
25
int sslerr = SSL_get_error(ssl_,ret), err;
26
switch(sslerr)
{
27

28
case SSL_ERROR_WANT_READ:
29
case SSL_ERROR_WANT_WRITE:
30
read_handler_ = (io_handler)&ssl_conn_t::shutdown_handler;
31
write_handler_ = (io_handler)&ssl_conn_t::shutdown_handler;
32
33
if(SSL_ERROR_WANT_READ==sslerr)
{
34
struct timeval tv;
35
tv.tv_sec = 30,tv.tv_usec = 0;
36
add_event(true,tv);
37
}
38
return OP_AGAIN;
39

40
}
41
}
42
43
void ssl_conn_t::shutdown_handler(short ev)
44

{
45
shutdown(ev&EV_TIMEOUT);
46
}
以上do_shutdown是核心函數,調用SSL_shutdown實現,如果失敗并且返回SSL_ERROR_WANT_READ或SSL_ERROR_WANT_WRITE,就改變讀寫函數指針為shutdown_handler,shutdown_handler實現在讀寫事件中繼續關閉處理。
posted on 2014-04-11 17:26
春秋十二月 閱讀(13956)
評論(0) 編輯 收藏 引用 所屬分類:
Network