Lighttpd1.4.20源码分析 笔记 状态机之错误处理和连接关闭

这里所说的错误有两种:

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的调度中,状态机将会关闭连接,清理所有资源。

至此,连接正式关闭。

关于状态机的简单解析就到此为止~

时间: 2024-12-13 12:37:55

Lighttpd1.4.20源码分析 笔记 状态机之错误处理和连接关闭的相关文章

Lighttpd1.4.20源码分析 笔记 状态机之response

在CON_STATE_RESPONSE_START状态中,服务器开始准备给客户端的response: case CON_STATE_RESPONSE_START: /* * the decision is done * - create the HTTP-Response-Header * */ if (srv->srvconf.log_state_handling) { log_error_write(srv, __FILE__, __LINE__, "sds", "

wifidog认证自带http服务器Lighttpd1.4.20源码分析之状态机返回response

前一篇介绍完了请求的处理,先面lighttpd将会把处理的结果返回给客户端.状态机进入CON_STATE_RESPONST_START.在这个状态中,服务器主要的工作在函数connection_handle_write_prepare.这个函数不算复杂,主要是根据客户端请求的method来设置response的headers,其实就是设置“Content-Length”的值.下面是函数代码,做了一些删减: static int connection_handle_write_prepare(se

Lighttpd1.4.20源代码分析 笔记 状态机之错误处理和连接关闭

这里所说的错误有两种: 1.http协议规定的错误,如404错误. 2.server执行过程中的错误.如write错误. 对于http协议规定的错误,这里的"错误"是针对client的. lighttpd返回相应的错误提示文件之后,相当于顺利的完毕了一次请求,仅仅是结果和client想要的不一样而已. 对于server执行中的错误,状态机进入CON_STATE_ERROR状态.常见的错误原因:client提前断开连接. 比方你不停的刷新页面.在你刷新的时候,前一次的连接没有完毕,但被浏

wifidog源码分析Lighttpd1.4.20源码分析之fdevent系统(1)---fdevents结构体和fdevent系统对外接口

前面讲了lighttpd的插件系统,这一篇将看一看lighttpd中的fdevent系统.fdevent系统主要是处理各种IO事件,在web服务器中,主要就是向socket写数据和从socket读数据.通常,web服务器是IO密集型程序,这就要求在数据的读写上,web服务器必须能够具有很好的性能,不会因为某个socket的阻塞而致使其他socket也被阻塞,否则会大大降低服务器的性能.因此,大部分的web服务器都采用非阻塞IO进行数据的读写.lighttpd通过fdevent系统,采用类似OO中

wifidog源码分析Lighttpd1.4.20源码分析之插件系统(3)---PLUGIN_TO_SLOT宏

前面讲了lighttpd插件系统的加载和初始化,这一篇中,将介绍一下plugin.c中的宏PLUGIN_TO_SLOT.在将PLUGIN_TO_SLOT宏之前,我们先来看看lighttpd中插件系统的对外接口.这个接口所对的“外”指的是lighttpd服务器.前面已经提到,在运行的过程中,lighttpd不知道所加载的插件都是干什么用的,只知道这些插件所实现的接口,也就是在plugin结构体中那些函数指针有哪些对于某个插件是NULL,哪些是具体的函数地址.既然lighttpd只知道这些,那么它又

wifidog源码分析Lighttpd1.4.20源码分析之fdevent系统(3) -----使用

接着上文介绍的函数fdevent_linux_sysepoll_event_add 讲解,首先看函数的第三个参数events,他是一个整型,其没以为对应一种IO事件.上面fdevent_event_add()函数的额第三个参数是FDEVENT_IN,这是一个宏 /* * 用于标记文件描述符的状态 */ #define FDEVENT_IN BV(0) //文件描述符是否可写 #define FDEVENT_PRI BV(1) //不阻塞的可读高优先级的数据 poll #define FDEVEN

wifidog 认证Lighttpd1.4.20源码分析之bitset.c(h) -------位集合的使用

使用一个比特位来表示一个事件的两种状态,即节省内存,又可以提高运行速度.在Lighttpd中,提供了一个bitset数据结构,用来管理使用一个比特位集合. 在bitset.h中,比特位集合的数据结构定义如下: typedef struct { size_t *bits; size_t nbits; } bitset; bits指向一个size_t类型的数组,存放bit集合.size_t类型通常被定义成一个无符号的整型(int或long),其长度和具体的机器有关,这个读者可以查阅相关的资料.nbi

zeromq源码分析笔记之线程间收发命令(2)

在zeromq源码分析笔记之架构说到了zmq的整体架构,可以看到线程间通信包括两类,一类是用于收发命令,告知对象该调用什么方法去做什么事情,命令的结构由command_t结构体确定:另一类是socket_base_t实例与session的消息通信,消息的结构由msg_t确定.命令的发送与存储是通过mailbox_t实现的,消息的发送和存储是通过pipe_t实现的,这两个结构都会详细说到,今天先说一下线程间的收发命令. zeromq的线程可分为两类,一类是io线程,像reaper_t.io_thr

GNU GRUB 2.00 源码分析笔记,持续更新

前言 很多运维类书籍或文章仅从系统管理者的角度讲解了 grub 的安装以及使用, 本篇博文则从 gnu grub 2.00 的源码入手,从开发者,以及系统底层运行机制的角度,分析 grub 是如何作为跨平台的"全面统一的引导加载程序",来引导操作系统,加载 Linux 内核的过程等等, 部分内容参考了<深度探索 Linux 操作系统>一书中相关的内容(ISBN 978-7-11143901-1 )以及 gnu grub 项目官方站点的文档,并且加入自己分析源码时的笔记. (