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种错误码,那么要在下一次操作中继续握手。
1void ssl_conn_t::empty_handler(short ev)
2{
3}
4
5void ssl_conn_t::handshake_handler(short ev)
6{
7 handshake();
8}
9
10void 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
24int 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事件的进行,防止读写饿死。
1ssize_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
30void ssl_conn_t::write_handler(short ev)
31{
32 (this->*read_handler_)(EV_WRITE);
33}
34
35ssize_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
64void 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秒再关闭,如果超时就直接关闭。
1void ssl_conn_t::shutdown(bool is_timeout/**//*=false*/)
2{
3 if (do_shutdown(is_timeout) != OP_AGAIN)
4 delete this;
5}
6
7int 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
43void 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
春秋十二月 阅读(13815)
评论(0) 编辑 收藏 引用 所属分类:
Network