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 读写检测的更多相关文章

  1. 《java编程思想》读书笔记(一)开篇&第五章(1)

    2017 ---新篇章  今天终于找到阅读<java编程思想>这本书方法了,表示打开了一个新世界. 第一章:对象导论 内容不多但也有20页,主要是对整本书的一个概括.因为已经有过完整JAV ...

  2. 《Django By Example》第五章 中文 翻译 (个人学习,渣翻)

    书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者@ucag注:大家好,我是新来的翻译, ...

  3. 《Introduction to Tornado》中文翻译计划——第五章:异步Web服务

    http://www.pythoner.com/294.html 本文为<Introduction to Tornado>中文翻译,将在https://github.com/alioth3 ...

  4. Java Persistence with MyBatis 3(中国版) 第五章 与Spring集成

    MyBatis-Spring它是MyBatis子模块框.它用来提供流行的依赖注入框架Spring无缝集成. Spring框架是一个基于依赖注入(Dependency Injection)和面向切面编程 ...

  5. JavaScript DOM编程艺术-学习笔记(第五章、第六章)

    第五章: 1.题外话:首先大声疾呼,"js无罪",有罪的是滥用js的那些人.js的father 布兰登-艾克,当初为了应付工作,10天就赶出了这个js,事后还说人家js是c语言和s ...

  6. ArcGIS API for JavaScript 4.2学习笔记[16] 弹窗自定义功能按钮及为要素自定义按钮(第五章完结)

    这节对Popups这一章的最后两个例子进行介绍和解析. 第一个[Popup Actions]介绍了弹窗中如何自定义工具按钮(名为actions),以PopupTemplate+FeatureLayer ...

  7. Gradle 1.12用户指南翻译——第四十五章. 应用程序插件

    本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

  8. ROS机器人程序设计(原书第2版)补充资料 (伍) 第五章 计算机视觉

    ROS机器人程序设计(原书第2版)补充资料 (伍) 第五章 计算机视觉 书中,大部分出现hydro的地方,直接替换为indigo或jade或kinetic,即可在对应版本中使用. 计算机视觉这章分为两 ...

  9. Gradle 1.12 翻译——第十五章. 任务详述

    有关其他已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或访问:http://gradledoc.qiniudn.com ...

随机推荐

  1. 手写一个HTTP框架:两个类实现基本的IoC功能

    jsoncat: 仿 Spring Boot 但不同于 Spring Boot 的一个轻量级的 HTTP 框架 国庆节的时候,我就已经把 jsoncat 的 IoC 功能给写了,具体可以看这篇文章&l ...

  2. Android开发教程之密码框右侧显示小眼睛

    现在都说互联网寒冬,其实只要自身技术能力够强,咱们就不怕!我这边专门针对Android开发工程师整理了一套[Android进阶学习视频].[全套Android面试秘籍].[Android知识点PDF] ...

  3. 你不知道的MySQL,以及MariaDB初体验

    MySQL 是一个跨世纪的伟大产品,它最早诞生于 1979 年,距今已经有 40 多年的历史了,而如今比较主流的 Java 语言也只是 1991 年才诞生的,也就是说 MySQL 要比 Java 的诞 ...

  4. CDH+Kylin三部曲之一:准备工作

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. python- pyqt5 一个存疑问题

    首先 我的问题是 自定义的方法中 无法给窗体中增加控件 我们直接看例子 这是一个图书管理系统的窗口 我们给他加上菜单(menuBar) 加上工具栏(QAction) 程序变成了这样 这个界面是这样的( ...

  6. mock.js 学习

    安装 npm install mockjs 使用 // 引入 import Mock from 'mockjs' Mock.setup({ timeout: '200 - 400' }) const ...

  7. vue生命钩子函数

    vue的生命钩子函数在使用Vue开发中是非常重要的一环,可以说,生命钩子函数使开发变得更加便捷. 下图是Vue的生命周期图: 具体钩子如下: beforeCreate created beforeMo ...

  8. D. Road to Post Office 解析(思維)

    Codeforce 702 D. Road to Post Office 解析(思維) 今天我們來看看CF702D 題目連結 題目 略,請直接看原題. 前言 原本想說會不會也是要列式子解或者二分搜,沒 ...

  9. 用匿名内部类实现 Java 同步回调

    在一个应用系统中,不论使用何种编程语言,模块之间要进行调用,仅存在三种方式:同步调用.异步调用.回调.本文就其中回调方式进行详细解读,并通过匿名内部类的手段,在最后实现一个同步回调的过程. 一.回调的 ...

  10. ArcGIS API for Javascript的Point clustering使用及默认符号无法显示问题

    1.将包含ClusterFeatureLayer.js文件的extras文件夹放在部署的arcgis api目录下,如下图. extras路径 2.使用ClusterFeatureLayer关键代码如 ...