状态机之错误处理和连接关闭

最后更新于:2022-04-01 15:57:21

这里所说的错误有两种: 1.http协议规定的错误,如404错误; 2.服务器运行过程中的错误,如write错误。 对于http协议规定的错误,这里的“错误”是针对客户端的。lighttpd返回相应的错误提示文件之后,相当于顺利的完成了一次请求,只是结果和客户端想要的不一样而已。 对于服务器运行中的错误,状态机进入CON_STATE_ERROR状态。常见的错误原因:客户端提前断开连接。比如你不停的刷新页面,在你刷新的时候,前一次的连接没有完成,但被浏览器强行断开。对于服务器而言,刷新前后的两个连接是不相干的,服务器在接收后一个连接的时候仍然会继续处理前一次的连接。而前一次的连接已断开,这就产生了连接错误。 进入CON_STATE_ERROR状态后,如果前面的请求处理已经得到了结果。也就是http_status不为空。那么调用plugins_call_handle_request_done告诉插件请求处理结束: ~~~ /* even if the connection was drop we still have to write it to the access log */ if (con->http_status) { plugins_call_handle_request_done(srv, con); } ~~~ 如果使用了ssl,关闭ssl连接: ~~~ #ifdef USE_OPENSSL if (srv_sock->is_ssl) { /* 关闭ssl连接 */ } ERR_clear_error(); #endif ~~~ 接着: ~~~ switch(con->mode) { case DIRECT: #if 0 log_error_write(srv, __FILE__, __LINE__, "sd", "emergency exit: direct", con->fd); #endif break; default: switch(r = plugins_call_handle_connection_close(srv, con)) { case HANDLER_GO_ON: case HANDLER_FINISHED: break; default: log_error_write(srv, __FILE__, __LINE__, ""); break; } break; } connection_reset(srv, con); ~~~ 如果连接模式不是DIRECT,调用plugins_call_handle_connection_close告诉插件连接已经关闭。 如果设置了keep_alive,此时可能是服务器首先关闭连接的。调用shutdown关闭连接的读和写。如果关闭没有出错,状态机进入CON_STATE_CLOSE状态。如果没有设置keep_alive或者shutdown调用失败,那么直接关闭连接,结束状态机的运行。 ~~~ /* close the connection */ if ((con->keep_alive == 1) && (0 == shutdown(con->fd, SHUT_WR))) { con->close_timeout_ts = srv->cur_ts; connection_set_state(srv, con, CON_STATE_CLOSE); if (srv->srvconf.log_state_handling) { log_error_write(srv, __FILE__, __LINE__, "sd", "shutdown for fd", con->fd); } } else { connection_close(srv, con); } con->keep_alive = 0; srv->con_closed++; ~~~ 注意到,这里服务器主动关闭连接的时候用的是shutdown而不是close: 1.close使用引用计数,在计数为0时才关闭套接字;shutdown不管引用计数,直接激发TCP的正常连接终止序列; 2.close终止读和写两个方向的数据传送;shutdown可以指定只关闭连接的读,或只关闭连接的写,或两者均关闭。 以上lighttpd是关闭了连接的写这一半,对于TCP套接字来说,这叫做半关闭:当前留在套接字发送缓冲区的数据仍然可以发送,但是进程不能再对其调用写函数(由于读端没有关闭,所以服务器仍然可以读数据),当数据发送完毕之后,TCP连接终止。 另外,注意一下:con->close_timeout_ts = srv->cur_ts;将close_timeout_ts的值设置为当前时间,在下面会用到。 在CON_STATE_CLOSE阶段: ~~~ case CON_STATE_CLOSE: if (srv->srvconf.log_state_handling) { log_error_write(srv, __FILE__, __LINE__, "sds", "state for fd", con->fd, connection_get_state(con->state)); } if (con->keep_alive) { if (ioctl(con->fd, FIONREAD, &b)) { log_error_write(srv, __FILE__, __LINE__, "ss", "ioctl() failed", strerror(errno)); } if (b > 0) { char buf[1024]; log_error_write(srv, __FILE__, __LINE__, "sdd", "CLOSE-read()", con->fd, b); /* */ read(con->fd, buf, sizeof(buf)); } else { /* nothing to read */ con->close_timeout_ts = 0; } } else { con->close_timeout_ts = 0; } if (srv->cur_ts - con->close_timeout_ts > 1) { connection_close(srv, con); if (srv->srvconf.log_state_handling) { log_error_write(srv, __FILE__, __LINE__, "sd", "connection closed for fd", con->fd); } } break; ~~~ 如果缓冲区中还有数据,服务器会把数据读出来(然后丢弃),以腾出内存空间。 如果没有数据可读,那么设置close_timeout_ts=0,关闭连接。 如果有数据可读,读取数据之后,连接依然处在CON_STATE_CLOSE状态中(在出了CON_STATE_ERROR后,进入CON_STATE_CLOSE,这段时间cur_ts是没有改变的。如果有数据可读,此时const_time_ts是等于cur_ts的,因此连接并未被关闭),连接对应的fd被加入到fdevent系统中监听读事件。如果缓冲区中还有数据,那么在connection_handle_fdevent 函数中,也有上面这段代码,再次运行之,直到数据读完。随着close_timeout_ts被设置为0,在下次joblist的调度中,状态机将会关闭连接,清理所有资源。 至此,连接正式关闭。 关于状态机的简单解析就到此为止~
';