Win IOCP中使用openssl

1 什么是IOCP

什么不知道什么是IOCP?那你可就out了。IOCP(I/O Completion Port),常称I/O完成端口。 IOCP模型属于一种通讯模型,适用于能控制并发执行的高负载服务器的一个技术。 通俗一点说,就是用于高效处理很多很多的客户端进行数据交换的一个模型。或者可以说,就是能异步I/O操作的模型(哈哈,摘录自百度百科)。IOCP是Windows平台特有的一种特性(虽然linux上有epoll,但绝对没有IOCP强大)。基本上Win平台使用了IOCP作为应用程序使用的socket复用技术,性能绝对不会差。这里不再赘述IOCP优点,主要讲下IOCP是如何结合openssl实现强大的支持ssl协议的网络通信库。

完全理解本文的技术,需要对IOCP和openssl编程有一定的基础。

2 关键技术

2.1 IOCP使用openssl实现难点

google了一下关于openssl如何使用IOCP,找到可用的信息很少,分析与IOCP结合技术难点,其主要原因在于ssl的协议在TCP协议之上,属于应用层协议。

那么问题来了,IOCP的工作原理是绑定套接字端口,如果有数据收发消息,会通知调用层有事件到来,并且数据已经被系统接收完成。而我们都知道ssl编程时,由于SSL的复杂性:SSL握手(密钥协商和交换),数据收发(SSL_write和SSL_read,两个函数分别是将明文通过加密通道发送到对端,接收对端发送过来的加密数据并解密后拷贝到数据缓冲区),很难将此和IOCP技术融合到一起,下面是一段SSL客户端实现伪代码:

1.  SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //创建TCP套接字
2.  connect(sclient , (struct sockaddr *)&client, sizeof(client)) //连接服务端
3.  SSL_CTX *ctx = SSL_CTX_new (meth);//创建SSL上下文
3.  ssl = SSL_new (ctx);//创建SSL对象
5.  SSL_set_fd(ssl, sclient);//将明文套接字关联到SSL
6.  SSL_write …  SSL_read… //数据的收发

2.2 IOCP如何集成SSL

2.1章节写了编写SSL客户端的伪代码,其中关联服务socket套接字使用的函数是SSL_set_fd,该函数的实现如下:

int SSL_set_fd(SSL *s, int fd)
{
    int ret = 0;
    BIO *bio = NULL;

    bio = BIO_new(BIO_s_socket());

    …
    BIO_set_fd(bio, fd, BIO_NOCLOSE);
    SSL_set_bio(s, bio, bio);   //关键函数
    ret = 1;
err:
    return (ret);
}

void SSL_set_bio(SSL *s, BIO *rbio, BIO *wbio)
{
    ...
    s->rbio = rbio;
    s->wbio = wbio;
}

从上面两个函数的调用,我们看到了SSL_set_fd函数将套接字封装成了两个BIO(rbio 用于接收,wbio 用于发送, rbio 和wbio 都关联到了fd),并赋值给了SSL对象。我们看下SSL结构体的定义

struct ssl_st {
    …
   /* used by SSL_read */
    BIO *rbio;
    /* used by SSL_write */
    BIO *wbio;
    …

};

ssl_st结构中的注释已经写的很明确了。SSL的读写都是基于BIO操作的,那么有没有可能我们将IOCP将数据先,写入BIO_s_mem然后再使用SSL_write和SSL_reade从BIO中读取数据呢?方案当然是可行的。只不过这里需要注意的是,直接操作收发密文数据不再是用SSL_write和SSL_read,而是使用BIO_read、BIO_write配合SSL_*接口使用,另SSL处于SSL握手阶段,SSL_reade是没有应用数据的。


3 实现原理

3.1 主要口依赖

  • BIO_write BIO的写入数据(从对端收到密文数据)
  • BIO_read BIO的读取数据(读取本地SSL中要发送的密文数据)
  • SSL_write SSL写入数据(将明文数据发送到对端)
  • SSL_read SSL读取数据(从SSL中读取对端发送的明文数据)

基于上面四个接口,实现从IOCP中事件的出发,将密文使用BIO接口读写到对应的BIO中,紧接着使用SSL接口读写数据,实现明文加密,密文解密。

3.2 服务端实现主要流程图

- 握手(握手时没有应用数据,无需使用SSL_读或写)


从图中可以看到,IOCP转发SSL协议协议时的函数调用,通过BIO_write写入本地SSL握手数据,使用BIO_read读取握手数据。


- 1.iocp响应WSARecv使用结果收取对端密文数据
- 2.使用BIO_write写入iocp收到的密文数据到RECV bio
- 3.使用SSL_read读取明文数据
- 4.使用SSL_write写入要发送的明文数据
- 5.使用BIO_read读取要发送到对端的密文数据,使用WSASend发送到对端

4 样例代码

- 文件关键结构定义:

enum OVERLAPPED_TYPE{
    RECV = 0,
    SEND,
    CONNECT
};

enum ADDRESS_TYPE{
    LOCAL = 0,
    REMOTE
};

enum SOCKET_STATUS{
    NONE        = 0x0,
    ACCEPTING    = 0x1,
    CONNECTING    = 0x2,
    HANDSHAKING    = 0x4,
    CONNECTED    = 0x8,
    RECEIVING    = 0x10,
    SENDING        = 0x20,
    CLOSING        = 0x40,
    CLOSED        = 0x80,
    OPERATING    = ACCEPTING | CONNECTING | HANDSHAKING | RECEIVING | SENDING
};

struct session;

struct session_overlapped
{
    OVERLAPPED overlapped;
    DWORD result;
    session *psession;
};

struct session
{
    SOCKET s; // handle to socket
    char socket_buffer[2][BUFFER_SIZE]; // memory used for read/write from/to socket
    char ssl_buffer[2][BUFFER_SIZE]; // memory used for read/write from/to ssl memory bio
    DWORD ssl_buffer_size[2]; // indicates the bytes of valid data in ssl_buffer
    unsigned int status; // stores current socket status, bit-masked value of one or more of SOCKET_STATUS
    session_overlapped overlapped[3]; // structure for overlapped operations
    WSABUF wsabuf[2]; // structure used for pass buffer to overlapped operations
    DWORD bytes_transferred[2]; // store the bytes of buffer that received/sent from/to the socket
    DWORD wsa_flags[2]; // store the flags send/receive from overlapped operations, not used
    SSL *ssl; // SSL structure used by OpenSSL
    BIO *bio[2]; // memory BIO used by OpenSSL
    ssl_lock lock; // synchronization object for multiple-thread data access
};

- ocp处理关键流程

bool session_process(session *psession)
{
    bool fatal_error_occurred = false;
    if(nullptr != psession->ssl)
    {
        if(psession->bytes_transferred[RECV] > 0)
        {
            int bytes = BIO_write(psession->bio[RECV], psession->socket_buffer[RECV], psession->bytes_transferred[RECV]);
            if(bytes == psession->bytes_transferred[RECV])
            {
                psession->bytes_transferred[RECV] = 0;
            }
        }

        if(psession->ssl_buffer_size[RECV] == 0)
        {
            int bytes = 0;
            do
            {
                bytes = SSL_read(psession->ssl, psession->ssl_buffer[RECV], BUFFER_SIZE);

                if ((HANDSHAKING == (psession->status & HANDSHAKING)) && SSL_is_init_finished(psession->ssl))
                {
                    psession->status &= ~HANDSHAKING;
                    psession->status |= CONNECTED;

                    app_on_session_connect(psession);
                }

                if (bytes > 0)
                {
                    psession->ssl_buffer_size[RECV] = bytes;
                    app_on_session_recv(psession);
                    psession->ssl_buffer_size[RECV] = 0;
                }

            } while (bytes > 0);
        }

        if(psession->ssl_buffer_size[SEND] > 0)
        {
            int bytes = SSL_write(psession->ssl, psession->ssl_buffer[SEND], psession->ssl_buffer_size[SEND]);
            if(bytes == psession->ssl_buffer_size[SEND])
            {
                psession->ssl_buffer_size[SEND] = 0;
            }
        }

        if(psession->wsabuf[SEND].len == 0 && (0 != psession->s_listening || BIO_pending(psession->bio[SEND])))
        {
            int bytes = BIO_read(psession->bio[SEND], psession->socket_buffer[SEND], BUFFER_SIZE);
            if(bytes > 0)
            {
                psession->wsabuf[SEND].len = bytes;
            }
        }

        if(fatal_error_occurred)
            session_close(psession);
    }

    session_send(psession);
    session_recv(psession);    
    return !fatal_error_occurred;
}

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值