nginx&http 第五章 https non-fd 读写检测
EPOLL的LT/ET 模式下的读写
从一个非阻塞的socket上调用recv/send函数, 返回EAGAIN或者EWOULDBLOCK(注: EAGAIN就是EWOULDBLOCK)
从字面上看, 意思是:EAGAIN: 再试一次,EWOULDBLOCK: 如果这是一个阻塞socket, 操作将被block,error输出: Resource temporarily unavailable,这个错误表示资源暂时不够,能read时,读缓冲区没有数据,或者write时,写缓冲区满了。遇到这种情况,如果是阻塞socket,read/write就要阻塞掉。而如果是非阻塞socket,read/write立即返回-1, 同时errno设置为EAGAIN。所以,对于阻塞socket,read/write返回-1代表网络出错了。但对于非阻塞socket,read/write返回-1不一定网络真的出错了。可能是Resource temporarily unavailable。这时应该继续尝试,直到Resource available。
综上,对于non-blocking的socket,正确的读写操作为:
读:忽略掉errno = EAGAIN的错误,下次继续读
写:忽略掉errno = EAGAIN的错误,下次继续写
epoll 的LT和ET二者的差异在于
level-trigger模式下只要某个socket处于readable/writable状态,无论什么时候进行epoll_wait都会返回该socket;
edge-trigger模式下只有某个socket从unreadable变为readable或从unwritable变为writable时,epoll_wait才会返回该socket。
epoll的ET模式下,正确的读写方式:
读:只要可读,就一直读,直到返回0,或者ret = -1, errno = EAGAIN
写:只要可写,就一直写,直到数据发送完,或者ret = -1, errno = EAGAIN
static int do_raw_read(int fd, void *buf, int *bufsize, void *userdata)
{
ssize_t len = 0, res;
int is_first_recv = 0;
while(len < *bufsize) {
res = recv(fd, buf + len, *bufsize - len, 0);
if (res < 0) {
do_warn("errno:%d, %s\n", cross_sock_errno, strerror(errno));
if (sock_errno == EINTR)
continue;
else if (sock_errno == EAGAIN)
break;
else
return -1;
}
if (res == 0) {
if (is_first_recv == 0)
return 1; //EOF of fd
else
break;
}
len += res;
is_first_recv = 1;
}
*bufsize = len;
return 0;
}
int nwrite, data_size = strlen(buf);
n = data_size;
while (n > 0) {
nwrite = write(fd, buf + data_size - n, n);
if (nwrite < n) {
if(nwrite == -1 && errno != EINTR)
continue;
if (nwrite == -1 && errno != EAGAIN) {
do_debug("errno:%d, %s\n", sock_errno, strerror(errno));
}
do_warn("errno:%d, %s\n", sock_errno, strerror(errno));
break;
}
n -= nwrite;
}
socket的accept,accept 要考虑 2 个问题
(1) 阻塞模式 accept 存在的问题
考虑这种情况:TCP连接被客户端夭折,即在服务器调用accept之前,客户端主动发送RST终止连接,导致刚刚建立的连接从就绪队列中移出,
如果套接口被设置成阻塞模式,服务器就会一直阻塞在accept调用上,直到其他某个客户建立一个新的连接为止。但是在此期间,服务器单
纯地阻塞在accept调用上,就绪队列中的其他描述符都得不到处理。
解决办法是把监听套接口设置为非阻塞,当客户在服务器调用accept之前中止某个连接时,accept调用可以立即返回-1,这时源自Berkeley的
实现会在内核中处理该事件,并不会将该事件通知给epoll,而其他实现把errno设置为ECONNABORTED或者EPROTO错误,我们应该忽略这两个错误。
(2)ET模式下accept存在的问题
考虑这种情况:多个连接同时到达,服务器的TCP就绪队列瞬间积累多个就绪连接,由于是边缘触发模式,epoll只会通知一次,accept只处理
一个连接,导致TCP就绪队列中剩下的连接都得不到处理。
解决办法是用while循环抱住accept调用,处理完TCP就绪队列中的所有连接后再退出循环。如何知道是否处理完就绪队列中的所有连接呢?accept
返回-1并且errno设置为EAGAIN就表示所有连接都处理完。
do {
/*
针对非阻塞I/O执行的系统调用则总是立即返回,而不管事件足否已经发生。如果事件没有眭即发生,这些系统调用就
返回—1.和出错的情况一样。此时我们必须根据errno来区分这两种情况。对accept、send和recv而言,事件未发牛时errno
通常被设置成EAGAIN(意为“再来一次”)或者EWOULDBLOCK(意为“期待阻塞”):对conncct而言,errno则被
设置成EINPROGRESS(意为“在处理中")。
*/
s = accept(lc->fd, (struct sockaddr *) sa, &socklen);
if (s == (socket_t) -1) {
err = socket_errno;
/* 如果要去一次性读取所有的accept信息,当读取完毕后,通过这里返回。所有的accept事件都读取完毕 */
if (err == NGX_EAGAIN) { //如果event{}开启multi_accept,则在accept完该listen ip:port对应的ip和端口连接后,会通过这里返回
return;
} if (err == NGX_EMFILE || err == NGX_ENFILE) {
do_warn("");// Too many descriptors are in use by this process.
return;
}
if (err == NGX_ECONNABORTED || err= EPROTO) {
continue;
}
return;
}
process_new_fd(s);
} while (flag); //一次性读取所有当前的accept,直到accept返回NGX_EAGAIN,然后退出
}
使用Linux epoll模型,水平触发模式;当socket可写时,会不停的触发socket可写的事件,如何处理?????????????????
开始不把socket加入epoll,需要向socket写数据的时候,直接调用write或者send发送数据。如果返回EAGAIN,把socket加入epoll,在epoll的
驱动下写数据,全部数据发送完毕后,再移出epoll。这种方式的优点是:数据不多的时候可以避免epoll的事件处理,提高效率。
int main(){ if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
do_debug("sockfd\n");
exit(1);
}
setnonblocking(listenfd);
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_ANY);;
local.sin_port = htons(PORT);
if( bind(listenfd, (struct sockaddr *) &local, sizeof(local)) < 0) {
do_debug("bind\n");
exit(1);
}
listen(listenfd, 20); epfd = epoll_create(MAX_EVENTS);
if (epfd == -1) {
do_debug("epoll_create");
exit(EXIT_FAILURE);
} ev.events = EPOLLIN;
ev.data.fd = listenfd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) {
do_debug("epoll_ctl: listen_sock");
exit(EXIT_FAILURE);
} for (;;) {
nfds = epoll_wait(epfd, events, MAX_EVENTS, timer); //timer为-1表示无限等待
if (nfds == -1) {
do_debug("epoll_pwait");
if (errno == NGX_EINTR) {
}
exit(EXIT_FAILURE);
}
if (0 == nfds){
}
for (i = 0; i < nfds; ++i) {
fd = events[i].data.fd;
if (fd == listenfd) {
while ((sock_fd = accept(listenfd,(struct sockaddr *) &remote,
(size_t *)&addrlen)) > 0) {
setnonblocking(sock_fd);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = sock_fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, sock_fd,
&ev) == -1) {
do_debug("epoll_ctl: add");
exit(EXIT_FAILURE);
}
}
if (sock_fd == -1) {
if (errno != EAGAIN && errno != ECONNABORTED
&& errno != EPROTO && errno != EINTR)
do_debug("accept");
}
continue;
}
if (events[i].events & (EPOLLERR|EPOLLHUP)) {
process------
}
if (events[i].events & EPOLLIN) {
n = 0;
while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) {
n += nread;
}
if (nread == -1 && errno != EAGAIN) {
do_debug("read error");
}
ev.data.fd = fd;
ev.events = events[i].events | EPOLLOUT;
if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1) { }
}
if (events[i].events & EPOLLOUT) {
sprintf_n(buf, buf_len, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\nHello World");
int nwrite, data_size = strlen(buf);
n = data_size;
while (n > 0) {
nwrite = write(fd, buf + data_size - n, n);
if (nwrite < n) {
if (nwrite == -1 && errno != EAGAIN) {
do_debug("write error");
}
break;
}
n -= nwrite;
}
close(fd);
}
}
} return 0;
}
调用connect方法向上游服务器发起TCP连接,作为非阻塞套接字,connect方法可能立刻返回连接建立成功,也可能告诉用户继续等待上游服务器的响应对connect连接是否建立.
针对非阻塞I/O执行的系统调用则总是立即返回,而不管事件足否已经发生。如果事件没有眭即发生,这些系统调用就返回—1.和出错的情况一样。此时我们必须根据errno来区分这两种情况。对accept、send和recv而言,事件未发牛时errno通常被设置成EAGAIN(意为“再来一次”)或者EWOULDBLOCK(意为“期待阻塞”):对conncct而言,errno则被
设置成EINPROGRESS(意为“在处理中")。connect的时候返回成功后使用的sock就是socket创建的sock,这和服务器端accept成功返回一个新的sock不一样.
rc = connect(s, pc->sockaddr, pc->socklen); //connect返回值可以<linux高性能服务器开发> 9.5节
if (rc == -1) {
err = socket_errno;
if (err != NGX_EINPROGRESS)
{
if (err == NGX_ECONNREFUSED
/*
* Linux returns EAGAIN instead of ECONNREFUSED
* for unix sockets if listen queue is full
*/
|| err == EAGAIN
|| err == ECONNRESET
|| err == ENETDOWN
|| err == ENETUNREACH
|| err == EHOSTDOWN
|| err == EHOSTUNREACH)
{
do_err(); } else {
do_warn();
}
return ;
}
}
static int do_in_progress(xx *clidata)
{ clidata->in_progress = 0; if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == 0
&& error == 0) {
/* success connecting */
do_process_con_server();
} else {
/* failed connecting */
}
return 1; }
connect 链接成功后:fd 应该是可读同时可写。
ssl连接建立--ssl握手
SSL协议是基于TCP、位于应用层、创数层之间,提供数据加密、用户验证和保证数据完整性的一种网络协议;
SSL/TLS 加密方式:
对称加密和非对称加密结合;
加密算法一般分为两种: '对称加密' 和 '非对称加密'。
'对称加密': 也叫'密钥加密',就是指加密和解密使用的是相同的密钥。
'非对称加密': 也叫'公钥加密',就是指加密和解密使用的是不同的密钥。
do_ssl_init( )
{
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms(); } do_ssl_create(ssl_t *ssl)
{
ssl->ctx = SSL_CTX_new(SSLv23_method());
} //server端需要初始化证书与私钥
string cert = "server.pem", key = "server.pem";
r = SSL_CTX_use_certificate_file(g_sslCtx, cert.c_str(), SSL_FILETYPE_PEM);
r = SSL_CTX_use_PrivateKey_file(g_sslCtx, key.c_str(), SSL_FILETYPE_PEM);
r = SSL_CTX_check_private_key(g_sslCtx); // 使用已建立连接的socket初始化ssl
sc->connection = SSL_new(ssl->ctx);
SSL_set_fd(sc->connection, c->fd) if (flags & NGX_SSL_CLIENT) {//客户端
SSL_set_connect_state(sc->connection);
} else {//服务器端
SSL_set_accept_state(sc->connection);
} //epoll_wait后,如果SSL相关的socket有读写事件需要处理则进行SSL握手,直到握手完成
int r = SSL_do_handshake(sc->connection_);
if (r == 1) { // 若返回值为1,则SSL握手已完成
process_add_epoll_fd();
return;
}
int err = SSL_get_error(sc->connection, r););
if (err == SSL_ERROR_WANT_WRITE) { //SSL需要在非阻塞socket可写时写入数据
c->events_ |= EPOLLIN; //等待socket可读
c->events_ &= ~EPOLLOUT; //暂时不关注socket可写状态
c->write->ready = 0;
c->read->handler = ssl_handshake_handler;
c->write->handler = ssl_handshake_handler; if (ngx_handle_read_event(c->read, 0, NGX_FUNC_LINE) != NGX_OK) {
return NGX_ERROR;
} if (ngx_handle_write_event(c->write, 0, NGX_FUNC_LINE) != NGX_OK) {
return NGX_ERROR;
} return NGX_AGAIN; //需要继续握手 } else if (err == SSL_ERROR_WANT_READ) { //SSL需要在非阻塞socket可读时读入数据
c->events_ |= EPOLLOUT;
c->events_ &= ~EPOLLIN;
c->read->ready = 0;
c->read->handler = ssl_handshake_handler;
c->write->handler = ssl_handshake_handler; if (ngx_handle_read_event(c->read, 0, NGX_FUNC_LINE) != NGX_OK) {
return NGX_ERROR;
} if (ngx_handle_write_event(c->write, 0, NGX_FUNC_LINE) != NGX_OK) {
return NGX_ERROR;
}
return NGX_AGAIN; //需要继续握手
} else { //错误
ERR_print_errors(errBio);
} 5. 握手完成后,进行SSL数据的读写
SSL_write(ssl->connection, data, size);
SSL_read(ssl->connection, data, size); 以ngx 为例:
accept new fd 后 :
ngx_http_init_connection 中调用
rev->handler = ngx_http_ssl_handshake;
tcp 数据到服务端---ssl开始:
服务器处理数据---ngx_http_ssl_handshake----> rc = ngx_ssl_handshake(c);;ssl握手
如果握手完成:
//ssl单向认证四次握手完成后执行该handler
c->ssl->handler = ngx_http_ssl_handshake_handler;
如果握手失败:设置fd 回调函数--- c->read->handler = ngx_ssl_handshake_handler; // TLS单向认证 协议握手过程参考http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html
//tls单向认证四次握手过程,都会调用该函数处理,返回NGX_AGAIN表示握手还没有完成,需要再次进行后续握手过程
ngx_int_t
ngx_ssl_handshake(ngx_connection_t *c)
{
int n, sslerr;
ngx_err_t err; ngx_ssl_clear_error(c->log); //这里会试着握手
n = SSL_do_handshake(c->ssl->connection); //改函数内部会调用ngx_http_ssl_alpn_select执行 //0x80:SSLv2 0x16:SSLv3/TLSv1
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); if (n == 1) { //握手完成
if (ngx_handle_read_event(c->read, 0, NGX_FUNC_LINE) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_handle_write_event(c->write, 0, NGX_FUNC_LINE) != NGX_OK) {
return NGX_ERROR;
} return NGX_OK;//握手完成
} sslerr = SSL_get_error(c->ssl->connection, n); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
//这里应该再重新接收一次和NGINX一样,等待下一次循环(epoll)再进行,同时设置读写句柄,以便下次读取的时候直接进行握手
//单向认证四次握手过程还没有完成,需要继续握手
if (sslerr == SSL_ERROR_WANT_READ) { //# define SSL_ERROR_WANT_READ 2
c->read->ready = 0;
c->read->handler = ngx_ssl_handshake_handler;
c->write->handler = ngx_ssl_handshake_handler; if (ngx_handle_read_event(c->read, 0, NGX_FUNC_LINE) != NGX_OK) {
return NGX_ERROR;
} if (ngx_handle_write_event(c->write, 0, NGX_FUNC_LINE) != NGX_OK) {
return NGX_ERROR;
} return NGX_AGAIN;//需要继续握手
} if (sslerr == SSL_ERROR_WANT_WRITE) {
c->write->ready = 0;
c->read->handler = ngx_ssl_handshake_handler;
c->write->handler = ngx_ssl_handshake_handler; if (ngx_handle_read_event(c->read, 0, NGX_FUNC_LINE) != NGX_OK) {
return NGX_ERROR;
} if (ngx_handle_write_event(c->write, 0, NGX_FUNC_LINE) != NGX_OK) {
return NGX_ERROR;
} return NGX_AGAIN; //需要继续握手
} return NGX_ERROR; //握手失败
}
nginx&http 第五章 https non-fd 读写检测的更多相关文章
- 《java编程思想》读书笔记(一)开篇&第五章(1)
2017 ---新篇章 今天终于找到阅读<java编程思想>这本书方法了,表示打开了一个新世界. 第一章:对象导论 内容不多但也有20页,主要是对整本书的一个概括.因为已经有过完整JAV ...
- 《Django By Example》第五章 中文 翻译 (个人学习,渣翻)
书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者@ucag注:大家好,我是新来的翻译, ...
- 《Introduction to Tornado》中文翻译计划——第五章:异步Web服务
http://www.pythoner.com/294.html 本文为<Introduction to Tornado>中文翻译,将在https://github.com/alioth3 ...
- Java Persistence with MyBatis 3(中国版) 第五章 与Spring集成
MyBatis-Spring它是MyBatis子模块框.它用来提供流行的依赖注入框架Spring无缝集成. Spring框架是一个基于依赖注入(Dependency Injection)和面向切面编程 ...
- JavaScript DOM编程艺术-学习笔记(第五章、第六章)
第五章: 1.题外话:首先大声疾呼,"js无罪",有罪的是滥用js的那些人.js的father 布兰登-艾克,当初为了应付工作,10天就赶出了这个js,事后还说人家js是c语言和s ...
- ArcGIS API for JavaScript 4.2学习笔记[16] 弹窗自定义功能按钮及为要素自定义按钮(第五章完结)
这节对Popups这一章的最后两个例子进行介绍和解析. 第一个[Popup Actions]介绍了弹窗中如何自定义工具按钮(名为actions),以PopupTemplate+FeatureLayer ...
- Gradle 1.12用户指南翻译——第四十五章. 应用程序插件
本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
- ROS机器人程序设计(原书第2版)补充资料 (伍) 第五章 计算机视觉
ROS机器人程序设计(原书第2版)补充资料 (伍) 第五章 计算机视觉 书中,大部分出现hydro的地方,直接替换为indigo或jade或kinetic,即可在对应版本中使用. 计算机视觉这章分为两 ...
- Gradle 1.12 翻译——第十五章. 任务详述
有关其他已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或访问:http://gradledoc.qiniudn.com ...
随机推荐
- C++时间函数小结
time time_t time (time_t* timer); 返回的值表示自1970年1月1日0时0分0秒(这个时间名叫 The Unix Epoch)起,到现在过去的时间,这里C/C++标准中 ...
- C语言和单片机C语言为什么会有差异?虽不同但理同!
许多小伙伴在学完C语言后想入门单片机,但学着学着发现明明都是C语言,为什么单片机C语言和我当初学的C语言有差异呢? 今天小编就来梳理我们平时所学的C语言与单片机C语言的有什么样的不同. 单片机c语言比 ...
- 【C语言教程】双向链表学习总结和C语言代码实现!值得学习~
双向链表 定义 我们一开始学习的链表中各节点中都只包含一个指针(游标),且都统一指向直接后继节点,通常称这类链表为单向链表. 虽然使用单向链表能 100% 解决逻辑关系为 "一对一" ...
- 扫描仪扫描文件处理-图像扫描加工到生成PDF步骤简述[JAVA版]
另参见:https://www.cnblogs.com/whycnblogs/p/8034276.html 详细见:https://github.com/barrer/scan-helper 用途: ...
- SQL报表语句;SQL获取今日、本周、本月数据
SQL报表语句 SQL获取今日.本周.本月数据 本日:select * from table where datediff(dd,C_CALLTIME,getdate())=0 --C ...
- C# 微支付退款查询接口 V3.3.6
#region 微支付退款查询 string Nonce = CreateRandomCode(15).ToLower(); //生成15个随机字符string sign1 = "appid ...
- Docker结合.Net Core初步使用
Docker是一项比较流行的容器化技术,可以让开发者将应用以及应用依赖的环境,依赖包一起打包到容器中,然后部署容器到生产环境就可以了,解决了应用程序部署到不同服务器环境带来的问题(很多开发人员都遇到过 ...
- 没事学学KVM(四)虚拟机基础管理
上次学完虚机的创建.开关机.备份配置文件等,今天学学其他几个常用的虚机管理命令: 1.重命名 方法一:virsh domrename old-name new-name virsh # list I ...
- centos7启用EPEL Repository
1,下载库文件 http://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/epel-release-7-11.noarch.rpm 2,安装 r ...
- Messenger实现进程间通信(IPC)
messenger内部也是实现aidl通信,所以可以看做一个轻量级aidl,但相对比较简单.首先开启一个服务并实现一个Handler用来处理消息,在onbind方法中返回IBinder对象,通过Ser ...