WinIOCP中使⽤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);enum怎么用
session_recv(psession);
return !fatal_error_occurred;
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论