IOCP + OpenSSL 搭建 HTTPS 服务器的问题

Sandrer 2018-09-06 12:50:18
OpenSSL 版本为 1.1, IOCP 框架早就已经搭建好了, 现在只是将 OpenSSL 集成进去
握手完成后, 客户端通过 WinHTTP 发送了一个 POST 请求过来, 请求 head + body 总共 10 多 kb (head 的内容只有几百字节)
IOCP 框架中每个 tcp 数据包的大小限制为 1024 kb, 每次收到数据包后用 BIO_write 写入到 SSL
SSL_read 将解码后的数据读出, 对于请求的 head 都能成功解码并读出
但到了解码 body 时就出问题了, 每个 tcp 数据包写入后, SSL_read 一直返回 WANT_READ 错误
直到最后一个数据包到来并写入后, SSL_read 才成功读出 1024 个字节(因为我限制每次只读 1024 个字节)
使用 BIO_eof 是否读取完毕, 返回 1, 即已读完了
那么 body 后面的数据去哪了??????

注: SSL 中并没有绑定 SOCKET 的, SOCKET 的状态由 IOCP 去管理, SSL 只负责编码及解码
...全文
674 6 打赏 收藏 转发到动态 举报
写回复
用AI写文章
6 条回复
切换为时间正序
请发表友善的回复…
发表回复
Sandrer 2018-09-06
  • 打赏
  • 举报
回复
BOOL CHTTPServer::OnReceived(sandrer::CIocpBufferImpl *pBuffer, sandrer::IOCPCTX pContext)
{
CTX_Lock(pContext);

iocp_trace("Context [0x%p] enter SSL received, buffer [0x%p](index %d).", pContext, pBuffer, pBuffer->GetIndex());

SSLDATA *p_session = (SSLDATA *)CTX_GetUserData(pContext);
if (p_session == NULL)
{
CTX_Unlock(pContext);
return FALSE;
}

//ERR_clear_error();

BOOL bHandshaked = FALSE;
int nBuffSize = (int)pBuffer->GetSize();
int nTransBytes = (int)pBuffer->GetUsed();
PBYTE pTransData = pBuffer->GetBuffer();

char ip[16];
UINT port = CTX_GetRemoteIP(pContext, ip);
iocp_trace("received %d bytes from %s:%u", nTransBytes, ip, port);

// 将加密数据写入 BIO 中
int nSSLBytes = BIO_write(p_session->bio_recv, pTransData, nTransBytes);
if (nSSLBytes != nTransBytes)
{
int nSSLError = SSL_get_error(p_session->ssl, nSSLBytes);
iocp_trace("BIO_write error %d when we received data.", nSSLError);
if (!BIO_should_retry(p_session->bio_recv))
{
CTX_Unlock(pContext);
DisconnectClient(pContext);
return FALSE;
}
}

// 尝试从 BIO 中取出解密数据, 如果出错则表示数据未完整需要等待下次获取
int i = 0;
for (;;)
{
nSSLBytes = SSL_read(p_session->ssl, pTransData, nBuffSize - 1);
iocp_trace("Context [0x%p] SSL_read %d times %d bytes", pContext, ++i, nSSLBytes);
// 判断一下握手操作是否已完成
if (!p_session->is_handshake && SSL_is_init_finished(p_session->ssl))
{
iocp_trace("handshake finish with %s:%u", ip, port);
bHandshaked = TRUE;
}

if (nSSLBytes > 0)
{
#pragma message("接收到非握手数据, 交给上层处理")
pTransData[nSSLBytes] = 0;
printf("%d bytes non-handshake data from %s:%u:\n%s\n", nSSLBytes, ip, port, (LPCSTR)pTransData);

// 判断是否读完
if (!BIO_eof(p_session->bio_recv))
continue;
}
else if (__sslFailed(p_session->ssl, nSSLBytes))
{
CTX_Unlock(pContext);
DisconnectClient(pContext);
return FALSE;
}
else
{
iocp_trace("Context [0x%p] SSL_read result %d", pContext, nSSLBytes);
}

break;
}

// 与客户端的握手操作未完成, 需要从 BIO 中尝试取得握手回复消息发送给对方
if (!p_session->is_handshake)
{
i = 0;
p_session->is_handshake = bHandshaked;

// 循环读取直到读完
for (;;)
{
nSSLBytes = BIO_read(p_session->bio_send, pTransData, nBuffSize);
iocp_trace("Context [0x%p] BIO_read %d times %d bytes", pContext, ++i, nSSLBytes);
if (nSSLBytes > 0)
{
pBuffer->SetUsed(nSSLBytes);
CIocpModel::SendData(pBuffer, pContext);
if (BIO_eof(p_session->bio_send))
{
CTX_Unlock(pContext);
return TRUE;
}

pBuffer = AllocateBuffer(sandrer::IOCPBP_WRITETCP, TRUE);
if (pBuffer == NULL)
{
CTX_Unlock(pContext);
DisconnectClient(pContext);
return TRUE;
}

pTransData = pBuffer->GetBuffer();
nBuffSize = (int)pBuffer->GetSize();

continue;
}
else if (__sslFailed(p_session->ssl, nSSLBytes))
{
CTX_Unlock(pContext);
DisconnectClient(pContext);
return FALSE;
}

break;
}
}

CTX_Unlock(pContext);

return FALSE;
}


这是数据接收的处理
Sandrer 2018-09-06
  • 打赏
  • 举报
回复
问题解决了, 参考的是另外一个帖子 https://bbs.csdn.net/topics/380158884
我把 SSL_read 时判断是否读完, 不用 BIO_eof 了, 直接循环调用 SSL_read 直到出错为止
xian_wwq 2018-09-06
  • 打赏
  • 举报
回复
引用 3 楼 Sandrer 的回复:
客户端是浏览器或其它不可控数据流, 你无法定义包头

是的,刚才没有考虑到这一点
lz可以参考下 HP-Socket v5.4.1
Sandrer 2018-09-06
  • 打赏
  • 举报
回复
引用 2 楼 xian_wwq 的回复:


对于 BIO_write 来说, 无论数据包是否粘包, BIO 内部已经帮我们解决了
我只关心 SSL_read 是否成功读出数据, 能读出数据的前提, 是 SSL 已成功解密一个 frame
Sandrer 2018-09-06
  • 打赏
  • 举报
回复
引用 2 楼 xian_wwq 的回复:


客户端是浏览器或其它不可控数据流, 你无法定义包头
xian_wwq 2018-09-06
  • 打赏
  • 举报
回复
1。定长不是个好办法
大多数应用,都是包头+数据,在包头中携带数据长度,接收端根据数据长度
来实现“粘包”处理。
因为tcp是基于流的,所以想每次保证都接收到1024字节是种理想状态

2。数据接收和数据解析要分离,否则在大压力下,
会出现网络带宽占用低,但是接收超时的情况

3。没有看到后续读投递的代码

1、本课程是一个干货课程,主要讲解如何封装服务器底层,使用Tcp/ip长连接,IDE使用vs2019 c++开发以及使用c++11的一些标准,跨平台windows和linux,服务器性能高效,单服务器压力测试上万无压力,服务器框架是经历过上线产品的验证,框架简单明了,不熟悉底层封装的人,半个小时就能完全掌握服务器框架上手写业务逻辑。2、本课程是一个底层服务器框架教程,主要是教会学员在windows或linux下如何封装一个高效的,避免踩坑的商业级框架,服务器底层使用初始化即开辟内存的技术,使用内存池,服务器运行期间内存不会溢出,非常稳定,同时服务器使用自定义哈希hashContainer,在处理新的连接,新的数据,新的封包,以及解包,发包,粘包的过程,哈希容器性能非常高效,增、删、查、改永远不会随着连接人数的上升而降低性能,增、删、查、改的复杂度永远都是恒定的O(1)。3、服务器底层封装没有使用任何第三方网络库以及任何第三方插件,自由度非常的高,出了任何BUG,你都有办法去修改,查找问题也非常方便,在windows下使用iocp,linux下使用epoll.4、讲解c++纯客户端,主要用于服务器之间通信,也就是说你想搭建多层结构的服务器服务器服务器之间使用socket通信。还可以使用c++客户端做压力测试,开辟多线程连接服务器,教程提供了压力测试,学员可以自己做压力测试服务器性能。5、赠送ue4和unity3d通信底层框架以及多人交互demo,登录,注册,玩家离开,同步主要是教会学员服务器与客户端如何交互。6、赠送c++连接mysql数据库框架demo,登录,注册,玩家离开数据持久化.7、服务器教程使用自定义通信协议,同时也支持protobuf,选择权在开发者自己手里,想用什么协议都可以,自由度高。8、服务器教程使用手动敲代码逐句讲解的方式开展教学课程。非喜勿喷,谢谢大家。9、服务器教程提供源码,大家可以在平台提供的地址下载或者联系我,服务器使用c++11部分标准,std::thread,条件变量,线程锁,智能指针等,需要学员具备一定c++知识,购买前请慎重考虑。

18,356

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 网络编程
c++c语言开发语言 技术论坛(原bbs)
社区管理员
  • 网络编程
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧