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
春秋十二月 阅读(13865)
评论(0) 编辑 收藏 引用 所属分类:
Network