随笔-156  评论-223  文章-30  trackbacks-0
   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 春秋十二月 阅读(13777) 评论(0)  编辑 收藏 引用 所属分类: Network

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理