openssl 使用非阻塞 bio

在项目中需要访问 https 加密的网页,为了保证并发性,需要用到非阻塞的 socket,搜索发现,这种使用场景的相关介绍不是很多,所以这里记录一下使用的过程。

在项目中,所使用的 ssl 库是老牌 sll 库 —— openssl。所使用的 io多路复用 技术是 epoll。

核心流程

整体流程与访问非加密网站类似,不同之处在于有一下几点:

  1. 在 socket 建立 tcp 连接之后,需要绑定 socket 句柄在 SSL 中
  2. 读取,发送数据,使用 SSL 库的方法,替代 linux 系统调用
  3. 关闭连接前,需要先执行 SSL 关闭流程

建立连接

首先,打开 socket 句柄,然后设置必要的属性

 

1 int sock_fd = -1;
2 int flags = -1;
3 sock_fd = socket(AF_INET, SOCK_STREAM, 0);
4 flags = fcntl(sockfd, F_GETFL, 0);
5 fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

 

然后,将句柄加入 epoll 的管理

1 epoll_event ev;
2 ev.events = EPOLLIN | EPOLLOUT | EPOLLET
3 ev.data.ptr = your_ev_info;
4 epoll_ctl(epfd, EPOLL_CTL_ADD, url_item->sockfd, &ev);

现在,可以开始真正的连接过程了,与普通的 tcp 连接一样,调用 connect 系统调用。在非阻塞 io 中,需要通过 connect 的返回值和 errno 来判断连接状态,采取不同的策略

 1 struct sockaddr_in serv_addr;
 2 
 3 if (connect(sock_fd, (sockaddr *) & serv_addr, sizeof (sockaddr)) < 0) {
 4     // 没有立刻连接成功,需要判断 errno
 5     if (errno != EINPROGRESS && errno != EINTR) {
 6         // 失败了, 从epoll里面干掉
 7         epoll_ctl(epfd, EPOLL_CTL_DEL, sock_fd, NULL);
 8     }
 9 } else {
10     // 立刻成功了
11     prepare_connect_ssl(your_ev_info);
12 }

如果没有立刻连接成功,在成功后,会触发 epoll,我们需要在 your_ev_info 中,需要保存现在的状态,以便在  epoll_wait 之后,通过状态来决定需要调用的函数。这些属于 epoll 的细节了,在此不展开说。

假设,现在已经连接成功,则开始做 SSL 握手之前的准备工作。

1 SSL_CTX *ssl_ctx;
2 SSL *ssl;
3 
4 ssl_ctx = SSL_CTX_new(TLSv1_method());
5 ssl = SSL_new(url_item->ssl_ctx);
6 SSL_set_mode(url_item->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
7 
8 // 绑定 SSL 和 socket 句柄
9 SSL_set_fd(ssl, sock_fd);

这一步之所以和后面的 SSL 握手过程分开,是因为 SSL 握手在非阻塞io 的情况下,有可能会被调用多次,而这部分只需要一次调用即可。

现在开始 SSL 握手

 1 int ssl_conn_ret = SSL_connect(ssl);
 2 if (1 == ssl_conn_ret) {
 3     // 开始和对端交互
 4 } else if (-1 == ssl_conn_ret) {
 5     // 没有立刻握手成功,需要通过错误码来判断现在的状态
 6     int ssl_conn_err = SSL_get_error(ssl, ssl_conn_ret);
 7     if (SSL_ERROR_WANT_READ == ssl_conn_err || 
 8              SSL_ERROR_WANT_WRITE == ssl_conn_err) {
 9          //需要更多时间来进行握手
10     }
11 } else {
12     // 连接失败了,做必要处理
13     if (0 != ssl_conn_ret) {
14         SSL_shutdown(ssl);
15     }
16     SSL_free(ssl);
17     SSL_CTX_free(ssl_ctx);
18 }

在没有立刻握手成功的时候,需要在 epoll 触发后,在次调用此段代码,来继续握手的过程。

至此,建立连接的过程就完成了。

发送与读取数据

由于发送与读取数据都有可能没有完全完成我们所指定的长度,所以需要判断对应返回值,来决定是否继续发送或读取

 

 

1 // 发送数据
2 int ret = SSL_write(ssl, buf + last_write_pos, buf_len - last_write_pos);
3 
4 // 读取数据
5 int ret = SSL_read(ssl, buf + last_read_pos, buf_len - last_read_pos);

 

关闭连接

 

// 关闭 ssl 连接
SSL_shutdown(ssl);
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);

// 然后关闭 socket
close(sock_fd);

要点记录

在使用过程中,整体流程是十分顺利的。一个最重要的点是关于 openssl 与 epoll 的边缘触发配合的问题。

当需要使用 epoll 的边缘触发时,一定要注意,SSL_read 最多只会读取一个完整的加密段,所以,当一次可以读取的数据量大于此值时,需要循环调用 SSL_read 直到读取失败为止。否则,就会导致在缓冲区中的数据没有完全读取的情况。

 

转载于:https://www.cnblogs.com/vinson0526/p/5242397.html

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用OpenSSL进行数据的BIOBIOOpenSSL库中的一种抽象I/O对象)操作可以在内存缓冲区和其他数据源之间进行数据的读写。下面是一个示例代码,展示了如何使用OpenSSLBIO接口将数据从一个BIO对象读取到另一个BIO对象中: ```c #include <stdio.h> #include <string.h> #include <openssl/bio.h> int main() { // 创建两个内存BIO对象 BIO *bio_src = BIO_new(BIO_s_mem()); BIO *bio_dest = BIO_new(BIO_s_mem()); // 将数据写入源BIO对象 const char *data = "Hello, OpenSSL!"; BIO_write(bio_src, data, strlen(data)); // 从源BIO对象读取数据到目标BIO对象 char buffer[256]; int len; while ((len = BIO_read(bio_src, buffer, sizeof(buffer))) > 0) { BIO_write(bio_dest, buffer, len); } // 从目标BIO对象中读取数据并打印 char *dest_data; len = BIO_get_mem_data(bio_dest, &dest_data); printf("Data in destination BIO: %.*s\n", len, dest_data); // 释放内存和资源 BIO_free_all(bio_src); BIO_free_all(bio_dest); return 0; } ``` 在这个示例中,我们创建了一个源BIO对象 `bio_src` 和一个目标BIO对象 `bio_dest`。然后,我们使用 `BIO_write` 将数据写入源BIO对象。接下来,我们使用 `BIO_read` 从源BIO对象读取数据,并使用 `BIO_write` 将数据写入目标BIO对象。最后,我们使用 `BIO_get_mem_data` 获取目标BIO对象中的数据,并打印出来。 请注意,这只是一个简单的示例,实际使用中可能会涉及更多的错误处理和其他操作。你可以根据自己的需求进行适当的修改和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值