文章目录
- SSL学习笔记
-
- OpenSSL库基础
- 示例
-
- Server
- Client
- 证书生成
- 问题记录
-
- 获取错误原因
- 数据发送对方接受不到
- SSL_connect在服务端异常时阻塞卡住
- SSL_shutdown崩溃Broken pipe
-
- 问题
- 解决方式
- 原理
- 参考
SSL学习笔记 OpenSSL库基础 根据 官方的例子,OpenSSL常用的结构体和函数如下:
- 初始化OpenSSL库
- SSL_library_init():初始化SSL算法库函数
- SSL_load_error_strings():载入所有SSL 错误消息
- OpenSSL_add_all_algorithms(): 载入所有SSL 算法
- 加载和验证证书
- 通过SSL_CTX_new(SSL_METHOD *method)创建一个SSL_CTX *实例,用来保存证书的私钥。其中method通过TLS_client_method()、TLS_server_method() 和TLS_method()创建(一些写法中会提到SSLv23_method(),SSLv23_server_method(), SSLv23_client_method()等API,官方文档 明确提到已经过时了)。
- 通过SSL_CTX_use_certificate_file()和SSL_CTX_use_PrivateKey_file()载入证书和私钥,SSL_CTX_check_private_key()来检查是否匹配。
- 创建SSL实例,绑定socket
- SSL_new(ctx)来创建一个ssl的实例,这个实例后续握手、读写、关闭等需要
- 通过SSL_set_fd()来把SSL实力和socket句柄关联起来
- SSL握手,有2种方式
- SSL_accept(),被动的等待客户端,注意这里会阻塞
- SSL_do_handshake
- 收发
- SSL_read()/SSL_write()来进行数据加解密和读写
- 注意需要对返回值调用SSL_get_error()进一步判断,如果返回的是SSL_ERROR_WANT_READ,SSL_ERROR_WANT_WRITE代表需要再次调用
- 释放
- 释放ssl实例,SSL_shutdown()来关闭ssl连接,SSL_free()释放内存,别忘记还要调用close()关闭socket句柄
- 释放ctx上下文实例,因为ctx是全局的,应该在程序退出前调用SSL_CTX_free()进行释放
我的代码可以参见 code/tcp/05-simple-multi-thread-in-ssl/server/ssl_server.cpp
Server
/** @file main.h
* @brief OpenSSL库使用示例,为了兼顾简单和实用,这里拿PerConnectionPerThread模型来演示。即一个连接一个线程
*,注意:实际中为了更高的性能,通常是搭配epoll I/O复用来实现。有单Reactor单线程,单Reactor多线程,主从Reactor多线程几种模型
* @author teng.qing
* @date 2021/5/13
*/#include
#include
#include
#include
#include
#include
#include
#include
#include #include /** @fn create_socket
* @brief 创建一个监听的socket
* @param [in]listenPort:监听端口
* @return
*/
int create_socket(int listenPort) {
int sockFd = 0;
struct sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(listenPort);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
sockFd = socket(PF_INET, SOCK_STREAM, 0);
if (sockFd < 0) {
printf("create socket error:%d", errno);
exit(EXIT_FAILURE);
}// SO_REUSEADDR
int yesReuseAddr = 1;
if (::setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR, &yesReuseAddr, sizeof(yesReuseAddr)) == -1) {
std::cout << "setsockopt error:" << errno << std::endl;
return 0;
}if (bind(sockFd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
std::cout << "Unable to bind:" << errno << std::endl;
exit(EXIT_FAILURE);
}if (listen(sockFd, SOMAXCONN) < 0) {
std::cout << "Unable to listen:" << errno << std::endl;
exit(EXIT_FAILURE);
}return sockFd;
}/** @fn init_openssl
* @brief 全局初始化openssl库,只需要调用一次
* @return
*/
void init_openssl() {
SSL_load_error_strings();
// 载入所有SSL 错误消息
OpenSSL_add_all_algorithms();
// 加载所有支持的算法
}/** @fn cleanup_openssl
* @brief 退出前清理openssl
* @return
*/
void cleanup_openssl() {
EVP_cleanup();
}/** @fn create_context
* @brief 创建一个全局SSL_CTX,存储证书等信息
* @return
*/
SSL_CTX *create_context() {
const SSL_METHOD *method;
SSL_CTX *ctx;
/* 以SSL V2 和 V3 标准兼容方式产生一个SSL_CTX ,即SSL Content Text */
/* 也可以用SSLv2_server_method() 或SSLv3_server_method() 单独表示V2 或V3 标准*/
//method = SSLv3_server_method();
method = SSLv23_server_method();
ctx = SSL_CTX_new(method);
if (!ctx) {
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}return ctx;
}/** @fn configure_context
* @brief 设置证书
* @param [in]ctx: SSL上下文
* @param [in]certPath: 证书文件
* @param [in]privateKeyPath: 私钥文件
* @return
*/
void configure_context(SSL_CTX *ctx, std::string certPath, std::string privateKeyPath) {
SSL_CTX_set_ecdh_auto(ctx, 1);
// 载入用户的数字证书, 此证书用来发送给客户端。证书里包含有公钥
if (SSL_CTX_use_certificate_file(ctx, certPath.c_str() /*"cert.pem"*/, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}// 载入用户私钥
if (SSL_CTX_use_PrivateKey_file(ctx, privateKeyPath.c_str()/*"key.pem"*/, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}// 检查用户私钥是否正确
if (!SSL_CTX_check_private_key(ctx)) {
ERR_print_errors_fp(stdout);
exit(1);
}
}/** @fn opensslErrorCheck
* @brief opensslErrorCheck
* @param [in]ssl: SSL实例
* @param [in]retCode: SSL_read/SSL_write返回值
* @param [in]isError: 是否确实发生了错误
* @return
*/
void opensslErrorCheck(SSL *ssl, int retCode, bool &isError) {
// 处理ssl的错误码
int sslErr = SSL_get_error(ssl, retCode);
isError = true;
switch (sslErr) {
case SSL_ERROR_WANT_READ:
std::cout << "SSL_ERROR_WANT_READ" << std::endl;
isError = false;
break;
case SSL_ERROR_WANT_WRITE:
std::cout << "SSL_ERROR_WANT_WRITE" << std::endl;
isError = false;
break;
case SSL_ERROR_NONE: // 没有错误发生,这种情况好像没怎么遇到过
std::cout << "SSL_ERROR_WANT_WRITE" << std::endl;
break;
case SSL_ERROR_ZERO_RETURN:// == 0 ,代表对端关闭了连接
std::cout << "SSL remote close the connection" << std::endl;
break;
case SSL_ERROR_SSL:
std::cout << "SSL error:" << sslErr << std::endl;
break;
default:
std::cout << "SSL unknown error:" << sslErr << std::endl;
break;
}
}/** @fn
* @brief
* @param [in]socketFd: 客户端的socket文件句柄
* @param [in]ctx:全局的上下文,保存有证书信息等
* @return
*/
void onHandleClient(int socketFd, SSL_CTX *ctx) {
std::cout << "new connection coming" << std::endl;
SSL *ssl;
const char reply[] = "test\n";
// 基于ctx 产生一个新的SSL
ssl = SSL_new(ctx);
// 将连接用户的socket 加入到SSL
SSL_set_fd(ssl, socketFd);
auto t1 = std::chrono::steady_clock::now();
// 建立SSL 连接
int ret = SSL_accept(ssl);
if (ret > 0) {
std::cout << "ssl handshake success" << std::endl;
// 发消息给客户端
//SSL_write(ssl, reply, strlen(reply));
auto t2 = std::chrono::steady_clock::now();
auto timeSpan = std::chrono::duration_cast::chrono::duration>(t2 - t1);
std::cout << "SSL_accept cost " << timeSpan.count() * 1000 << " ms." << std::endl;
while (true) {
char tempBuf[512] = {};
int recvLen = SSL_read(ssl, tempBuf, sizeof(tempBuf));
if (recvLen > 0) {
std::cout << "客户端发来数据,len=" << recvLen << ",content=" << tempBuf << std::endl;
// echo
std::cout << "SSL_write " << std::string(tempBuf, recvLen) << std::endl;
ret = SSL_write(ssl, tempBuf, recvLen);
if (ret <= 0) {
std::cout << "SSL_write return <=0,ret=" << recvLen << std::endl;
bool isError = false;
opensslErrorCheck(ssl, recvLen, isError);
if (isError) {
std::cout << "SSL_write error,close" << std::endl;
break;
}
}
} else { // SSL_read <= 0 ,进一步检查openssl 的错误码,判断具体原因
std::cout << "SSL_read return <=0,ret=" << recvLen << std::endl;
bool isError = true;
opensslErrorCheck(ssl, recvLen, isError);
if (isError) {
std::cout << "SSL_read error,close" << std::endl;
break;
}
}/* 这里是TCP处理的流程,针对openssl,还需进一步针对<=0进行判断
* else if (recvLen == 0) {
std::cout << "客户端主动断开连接,退出接收流程" << std::endl;
break;
} else {
std::cout << "发生其他错误,no=" << errno << ",desc=" << strerror(errno) << std::endl;
}*/
}
} else {
int code = SSL_get_error(ssl, ret);
auto reason = ERR_reason_error_string(code);
if (code == SSL_ERROR_SYSCALL) {
std::cout << "ssl handshake error:errno=" << errno << ",reason:" << strerror(errno) << std::endl;
} else {
std::cout << "ssl handshake error:code=" << code << ",reason:" << reason << std::endl;
}ERR_print_errors_fp(stderr);
}std::cout << "cleanup ssl connection" << std::endl;
// 关闭SSL 连接
SSL_shutdown(ssl);
// 释放SSL
SSL_free(ssl);
// 关闭socket
close(socketFd);
}int main() {
int sockFd;
SSL_CTX *ctx;
// 捕获SIG_IGN信号,解决Broken pipe导致进程崩溃问题
signal(SIGPIPE, SIG_IGN);
init_openssl();
ctx = create_context();
configure_context(ctx, "../ssl/google.com.pem", "../ssl/google.com.key");
//configure_context(ctx, "../ssl/zhaogang.com.pem", "../ssl/zhaogang.com.key");
std::cout << "listen at :8433" << std::endl;
sockFd = create_socket(8433);
/* Handle connections */
while (true) {
struct sockaddr_in addr{};
socklen_t len = sizeof(addr);
// 阻塞,直到有新的连接到来
int clientFd = accept(sockFd, (struct sockaddr *) &addr, &len);
if (clientFd < 0) {
perror("Unable to accept\n");
break;
}// 单独起1个线程处理客户端逻辑(错误的用法,这里只是为了演示,实战中需要使用epoll多路复用技术)
std::thread task(onHandleClient, clientFd, ctx);
task.detach();
}// 关闭监听socket文件句柄
close(sockFd);
// 退出前释放全局的上下文
SSL_CTX_free(ctx);
// 清理openssl
cleanup_openssl();
return 0;
}
Client
/** @file main.h
* @brief
* @author teng.qing
* @date 2021/5/13
*/// openssl
#include
#include // socket
#include
#include
#include
#include
#include #include
#include void showCerts(SSL *ssl) {
X509 *cert;
char *line;
cert = SSL_get_peer_certificate(ssl);
if (cert != nullptr) {
std::cout << "数字证书信息:" << std::endl;
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
std::cout << "证书: " << line << std::endl;
free(line);
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
std::cout << "颁发者: " << line << std::endl;
free(line);
X509_free(cert);
} else {
std::cout << "无证书信息!" << std::endl;
}
}/** @fn init_openssl
* @brief 全局初始化openssl库,只需要调用一次
* @return
*/
void init_openssl() {
//SSL_library_init();
SSL_load_error_strings();
// 载入所有SSL 错误消息
OpenSSL_add_all_algorithms();
// 加载所有支持的算法
}/** @fn create_context
* @brief 创建一个全局SSL_CTX,存储证书等信息
* @return
*/
SSL_CTX *create_context() {
const SSL_METHOD *method;
SSL_CTX *ctx;
/* 以SSL V2 和 V3 标准兼容方式产生一个SSL_CTX ,即SSL Content Text */
/* 也可以用SSLv2_server_method() 或SSLv3_server_method() 单独表示V2 或V3 标准*/
//method = SSLv3_server_method();
method = SSLv23_client_method();
ctx = SSL_CTX_new(method);
if (!ctx) {
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}return ctx;
}/** @fn create_socket
* @brief 创建一个监听的socket
* @param [in]listenPort:监听端口
* @return
*/
int create_socket(std::string serverIp, uint16_t serverPort) {
int sockFd = 0;
struct sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(serverPort);
addr.sin_addr.s_addr = inet_addr(serverIp.c_str());
sockFd = socket(PF_INET, SOCK_STREAM, 0);
if (sockFd < 0) {
printf("create socket error:%d", errno);
exit(EXIT_FAILURE);
}int ret = connect(sockFd, (struct sockaddr *) &addr, sizeof(sockaddr_in));
if (ret != 0) {
std::cout << "Connect err:" << errno << std::endl;
exit(errno);
}return sockFd;
}/** @fn opensslErrorCheck
* @brief opensslErrorCheck
* @param [in]ssl: SSL实例
* @param [in]retCode: SSL_read/SSL_write返回值
* @param [in]isError: 是否确实发生了错误
* @return
*/
void opensslErrorCheck(SSL *ssl, int retCode, bool &isError) {
// 处理ssl的错误码
int sslErr = SSL_get_error(ssl, retCode);
isError = true;
switch (sslErr) {
case SSL_ERROR_WANT_READ:
std::cout << "SSL_ERROR_WANT_READ" << std::endl;
isError = false;
break;
case SSL_ERROR_WANT_WRITE:
std::cout << "SSL_ERROR_WANT_WRITE" << std::endl;
isError = false;
break;
case SSL_ERROR_NONE: // 没有错误发生,这种情况好像没怎么遇到过
std::cout << "SSL_ERROR_WANT_WRITE" << std::endl;
break;
case SSL_ERROR_ZERO_RETURN:// == 0 ,代表对端关闭了连接
std::cout << "SSL remote close the connection" << std::endl;
break;
case SSL_ERROR_SSL:
std::cout << "SSL error:" << sslErr << std::endl;
break;
default:
std::cout << "SSL unknown error:" << sslErr << std::endl;
break;
}
}int main() {
SSL_CTX *ctx = nullptr;
// 初始化openssl
init_openssl();
std::cout << "init openssl success" << std::endl;
// 初始化socket,同步连接远端服务器
//int socketFd = create_socket("10.0.72.202", 8433);
int socketFd = create_socket("10.80.0.17", 8000);
std::cout << "tcp connect remote success" << std::endl;
// 创建SSL_CTX上下文
ctx = create_context();
// 绑定socket句柄到SSL实例上
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, socketFd);
// 建立SSL链接,握手
std::cout << "SSL_connect 2s later will connect and do hand shake..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "SSL_connect " << std::endl;
int ret = SSL_connect(ssl);
if (ret <= 0) {
ERR_print_errors_fp(stderr);
return 0;
}
std::cout << "handshake success" << std::endl;
// 显示对方证书信息
std::cout << "Connected with " << SSL_get_cipher(ssl) << " encryption" << std::endl;
showCerts(ssl);
std::cout << "send hello server" << std::endl;
std::string msg = "hello serve";
SSL_write(ssl, msg.c_str(), msg.length());
// wait server response
char tempBuf[256] = {};
ret = SSL_read(ssl, tempBuf, sizeof(tempBuf));
if (ret <= 0) {
std::cout << "SSL_read return <=0,ret=" << ret << std::endl;
bool isError = false;
opensslErrorCheck(ssl, ret, isError);
if (isError) {
std::cout << "SSL_read error,close" << std::endl;
}
}std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "exit ..." << std::endl;
SSL_shutdown(ssl);
// 关闭SSL连接
SSL_free(ssl);
// 释放SSL资源
close(socketFd);
// 关闭socket文件句柄
SSL_CTX_free(ctx);
// 释放SSL_CTX上下文资源return 0;
}
证书生成
生成自签名证书命令如下:
- 生成私钥
$ openssl genrsa -out google.com.key 2048
- 生成CSR(证书签名请求)
$ openssl req -new -out google.com.csr -key google.com.keyCountry Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Shanghai
Locality Name (eg, city) []:Shanghai
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Google Ltd
Organizational Unit Name (eg, section) []:google.com
Common Name (e.g. server FQDN or YOUR name) []:*.google.com #这一项必须和你的域名一致
Email Address []:kefu@google.com
A challenge password []:fG!#tRru
An optional company name []:Google.com
- 生成自签名证书(100年过期)
$ openssl x509 -req -in google.com.csr -out google.com.cer -signkey google.com.key -CAcreateserial -days 36500
- 生成服务器crt格式证书
$ openssl x509 -inform PEM -in google.com.cer -out google.com.crt
- 生成PEM公钥
$ openssl x509 -in google.com.crt -outform PEM -out google.com.pem
最后,google.com.pem 和 google.com.key 是本程序需要的 公钥和私钥
附录:
- 生成IOS客户端p12格式根证书(输入密码fG!#tRru)
$ openssl pkcs12 -export -clcerts -in google.com.cer -inkey google.com.key -out google.com.p12
- 生成Android客户端bks格式证书
# 略
问题记录 获取错误原因
SSL_get_error(),ERR_reason_error_string()
int ret = SSL_accept(ssl);
if (ret <= 0) {
int code = SSL_get_error(ssl, ret);
auto reason = ERR_reason_error_string(code);
std::cout << "ssl handshake error:code=" << code << ",reason:" << reason << std::endl;
ERR_print_errors_fp(stderr);
}
数据发送对方接受不到
当 SSL_read 和 SSL_write 返回值<=0时,需要调用SSL_get_error(ssl, ret)再次判断,如果是SSL_ERROR_WANT_READ 或者 SSL_ERROR_WANT_WRITE,则需要再次调用SSL_read或者SSL_write。
拿我修改的evpp增加openssl代码举例:
// add openssl support
int serrno = 0;
ssize_t n = !ssl_ ?
input_buffer_.ReadFromFD(chan_->fd(), &serrno) :
evpp::ssl::SSL_read(ssl_, &input_buffer_, &serrno);
if (n > 0) {
msg_fn_(shared_from_this(), &input_buffer_);
} else if (ssl_) { // <= 0 , 需要进一步处理SSL错误码
// deal SSL_ERROR_WANT_READ/SSL_ERROR_WANT_WRITE
// 这里如果不这么处理,则导致接收数据不完整,从而出错
switch (serrno) {
case SSL_ERROR_WANT_READ:
chan_->EnableReadEvent();
break;
case SSL_ERROR_WANT_WRITE:
chan_->EnableWriteEvent();
break;
case SSL_ERROR_ZERO_RETURN://SSL has been shutdown,相当于原生write()返回0,代表对端关闭连接(正常情况)
DLOG_TRACE << "SSL has been shutdown(" << serrno << ").";
HandleError();
break;
case SSL_ERROR_SSL:
DLOG_TRACE << "SSL has error(" << serrno << ").";
HandleError();
break;
case SSL_ERROR_NONE: // 0,have none error
DLOG_TRACE << "SSL has error none";
break;
default:
DLOG_TRACE << "SSL Connection has been aborted(" << serrno << ").";
HandleError();
break;
}
} else if (n == 0) { // remote close the connection
if (type() == kOutgoing) {
// This is an outgoisslsssng connection, we own it and it's done. so close it
DLOG_TRACE << "fd=" << fd_ << ". We read 0 bytes and close the socket.";
status_ = kDisconnecting;
HandleClose();
} else {
// Fix the half-closing problem : https://github.com/chenshuo/muduo/pull/117chan_->DisableReadEvent();
if (close_delay_.IsZero()) {
DLOG_TRACE << "channel (fd=" << chan_->fd() << ") DisableReadEvent. delay time "
<< close_delay_.Seconds() << "s. We close this connection immediately";
DelayClose();
} else {
// This is an incoming connection, we need to preserve the
// connection for a while so that we can reply to it.
// And we set a timer to close the connection eventually.
DLOG_TRACE << "channel (fd=" << chan_->fd()
<< ") DisableReadEvent. And set a timer to delay close this TCPConn, delay time "
<< close_delay_.Seconds() << "s";
delay_close_timer_ = loop_->RunAfter(close_delay_, std::bind(&TCPConn::DelayClose,
shared_from_this()));
// TODO leave it to user layer close.
}
}
} else { // n < 0
if (EVUTIL_ERR_RW_RETRIABLE(serrno)) {
DLOG_TRACE << "errno=" << serrno << " " << strerror(serrno);
} else {
DLOG_TRACE << "errno=" << serrno << " " << strerror(serrno)
<< " We are closing this connection now.";
HandleError();
}
}
SSL_connect在服务端异常时阻塞卡住
如果connect()系统调用成功,但是调用SSL_connect的时候卡住,则代表对方没有响应我们的握手请求,可能是出BUG了或者其他原因,此时我们需要一种机制能监测出来。通常有2种思路:
- 通过定时器,超过一定时间调用SSL_shutdown(ssl),SSL_connect()会立即返回。
- 通过非阻塞I/O+select I/O复用增加重试机制达到超时监测的目标。
// no-blocking io
std::random_device rd;
std::default_random_engine gen = std::default_random_engine(rd());
std::uniform_int_distribution dis(5, 10);
// c++11 的随机数
int maxTimes = dis(gen);
// 最大次数,5 - 10 次
int curTimes = 0;
int ret = -1;
// 这里限制超时时间
while (curTimes < maxTimes) { // 100 - 200 ms超时
ret = SSL_connect(ssl);
if (ret == -1) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(sockfd, &fds);
int ret = SSL_get_error(ssl, -1);
switch (ret) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE: {
struct timeval t{};
t.tv_sec = 0;
t.tv_usec = 100 * 1000;
// 100 ms
select(sockfd + 1, &fds, nullptr, nullptr, &t);
curTimes++;
break;
}
default:
break;
}
} else {
break;
}
}if (ret <= 0) {
LOG_WARN << "SSL_connect error:" << ret;
conn_fn_(TCPConnPtr(new TCPConn(loop_, "", sockfd, laddr, remote_addr_, 0)));
return;
}
SSL_shutdown崩溃Broken pipe
问题 写了个性能测试工具,每
20毫秒启动一个协程
,进行TCP连接和SSL握手,然后关闭。【C++葵花宝典|OpenSSL API入门和踩坑大全】服务端运行过程中,程序有时在SSL_shutdown处崩溃,如果降低客户端连接频率(比如1秒),该问题不出现。
static inline void SSL_free_(SSL *&ssl) {
if (ssl) {
::SSL_shutdown(ssl);
// crash
::SSL_free(ssl);
ssl = nullptr;
}
}
解决方式 根据:socket编程—— 服务器遇到Broken Pipe崩溃 ,是因为对一个对端已经关闭的socket调用两次write,第二次将会生成SIGPIPE信号,该信号默认结束进程。
解决方法是:捕获SIG_IGN信号,以避免进程退出
signal(SIGPIPE, SIG_IGN);
这样, 第二次调用write方法时,会返回-1,同时errno置为SIGPIPE,程序便能知道对端已经关闭。
原理 引用socket编程—— 服务器遇到Broken Pipe崩溃:
具体的分析可以结合TCP的"四次握手",TCP是全双工的信道,可以看作两条单工信道,TCP连接两端的两个端点各负责一条。当对端调用close时,虽然本意是关闭整个两条信道,但本端只是收到FIN包。按照TCP协议的语义,表示对端只是关闭了其所负责的那一条单工信道,仍然可以继续接收数据。也就是说,因为TCP协议的限制,一个端点无法获知对端的socket是调用了close还是shutdown。
对一个已经收到FIN包的socket调用read方法,如果接收缓冲已空,则返回0,这就是常说的表示连接关闭。 但第一次对其调用write方法时,如果发送缓冲没问题,会返回正确写入(发送)。但发送的报文会导致对端发送RST报文,因为对端的socket已经调用了close,完全关闭,既不发送,也不接收数据。所以,第二次调用write方法(假设在收到RST之后),会生成SIGPIPE信号,导致进程退出。参考
- OpenSSL官网:Simple TLS Server
- IBM社区:Secure programming with the OpenSSL API(客户端)
- SSL协议原理详解
- SSL原来介绍+OpenSSL API使用教程
- OpenSSL Libraries
- websocket协议实现及基于muduo库的功能扩展 https/ws/wss
- websocket-muduo
推荐阅读
- java|程序员口中常说的API是什么意思(什么是API?)
- 持续集成Jenkins|傻瓜式搭建持续集成Jenkins详细步骤及用法
- vb|VB中利用CopyMemory使用指针
- Web|动态创建表格案例
- Web|Web API 实用案例
- Web|Web APIs 实用案例
- 后端开发|Springboot实现上传文件以及文件实时预览(包括图片预览,视频预览)
- delphi|Delphi ServerSocket,ClientSocket示例
- windowns|SQLServer 错误: 15404,无法获取有关 Windows NT 组/用户