序linux
在项目中须要访问 https 加密的网页,为了保证并发性,须要用到非阻塞的 socket,搜索发现,这种使用场景的相关介绍不是不少,因此这里记录一下使用的过程。并发
在项目中,所使用的 ssl 库是老牌 sll 库 —— openssl。所使用的 io多路复用 技术是 epoll。socket
总体流程与访问非加密网站相似,不一样之处在于有一下几点:tcp
创建链接函数
首先,打开 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 来判断链接状态,采起不一样的策略url
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 的细节了,在此不展开说。spa
假设,如今已经链接成功,则开始作 SSL 握手以前的准备工做。code
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 直到读取失败为止。不然,就会致使在缓冲区中的数据没有彻底读取的状况。