精华内容
下载资源
问答
  • 这里的参照的代码是https://github.com/qinguoyi/TinyWebServer 对于原代码的不足之处,我会在之后的文章中给出改进代码 在笔者fork的这版中,原代码作者对于代码作出了更细化的分类 细节问题可以参考《APUE》...

    这里的参照的代码是https://github.com/qinguoyi/TinyWebServer

    • 对于原代码的不足之处,我会在之后的文章中给出改进代码
    • 在笔者fork的这版中,原代码作者对于代码作出了更细化的分类

    细节问题可以参考《APUE》《Linux高性能服务器编程》或者我之前的博客

    阅读任何源码一定要先从readme入手,如果没有readme,请从main入口入手。

    config 独立参数模块

    首先映入眼帘的是一个config的头文件,根据标识可以知道这是一个用户自定义头,所以我们先跳进去看看有什么东西。
    我在每个每个条目中都给出了注释。

    这里体现出整个项目的优点:模式切换
    也就是说这是一个复合的ET/LT。作者在Listenfd上和cfd上都建立了两种模式,意味着我们有四种组合方式。

    ET与LT模式

    lfd的ET代表着一次性接受所有连接,笔者认为这里是考虑到网络接入量巨大,瞬间占到Max_size的情况。LT代表一次取一个,当然这是默认的方式也是最常见的方式。

    cfd的两种方式也就是对应了epoll的方式,默认的LT和手动的ET

    config.h代码解读

    #ifndef CONFIG_H
    #define CONFIG_H
    
    #include "webserver.h" //懒得引用一堆头文件了
    
    using namespace std;
    
    class Config
    {
    public:
        Config();
        ~Config(){};
    
        void parse_arg(int argc, char*argv[]);
    
        //端口号
        int PORT;
    
        //日志写入方式
        int LOGWrite;
    
        //触发组合模式
        int TRIGMode;
    
        //listenfd触发模式
        int LISTENTrigmode;
    
        //connfd触发模式
        int CONNTrigmode;
    
        //优雅关闭链接
        int OPT_LINGER;
    
        //数据库连接池数量
        int sql_num;
    
        //线程池内的线程数量
        int thread_num;
    
        //是否关闭日志
        int close_log;
    
        //并发模型选择
        int actor_model;
    };
    
    #endif
    

    config.cpp代码解读

    其实很简单,在构造函数里作出了对于各种初始模式的设定
    并且在这版代码中,对于并发模式的处理,作者给出了reactor和preactor两种方式。(后面会详细讲解)
    原作者的测试环境为4核8线,所以这里给出了池内线程为8
    TRIGMode默认为最低效模式,可以改为1,实现服务器的最高性能,大概实现10wQPS

    #include "config.h"
    
    Config::Config(){
        //端口号,默认9006
        PORT = 9006;
    
        //日志写入方式,默认同步
        LOGWrite = 0;
    
        //触发组合模式,默认listenfd LT + connfd LT
        TRIGMode = 0;
    
        //listenfd触发模式,默认LT
        LISTENTrigmode = 0;
    
        //connfd触发模式,默认LT
        CONNTrigmode = 0;
    
        //优雅关闭链接,默认不使用
        OPT_LINGER = 0;
    
        //数据库连接池数量,默认8
        sql_num = 8;
    
        //线程池内的线程数量,默认8
        thread_num = 8;
    
        //关闭日志,默认不关闭
        close_log = 0;
    
        //并发模型,默认是proactor
        actor_model = 0;
    }
    
    void Config::parse_arg(int argc, char*argv[]){
        int opt;
        const char *str = "p:l:m:o:s:t:c:a:";
        while ((opt = getopt(argc, argv, str)) != -1)
        {
            switch (opt)
            {
            case 'p':
            {
                PORT = atoi(optarg);
                break;
            }
            case 'l':
            {
                LOGWrite = atoi(optarg);
                break;
            }
            case 'm':
            {
                TRIGMode = atoi(optarg);
                break;
            }
            case 'o':
            {
                OPT_LINGER = atoi(optarg);
                break;
            }
            case 's':
            {
                sql_num = atoi(optarg);
                break;
            }
            case 't':
            {
                thread_num = atoi(optarg);
                break;
            }
            case 'c':
            {
                close_log = atoi(optarg);
                break;
            }
            case 'a':
            {
                actor_model = atoi(optarg);
                break;
            }
            default:
                break;
            }
        }
    }
    

    总结: 简单的初始化形式的分割,改动参数的时候我只需要改动config.cpp就行了。

    main 模块

    main模块的主要功能是,驱动Sever。
    WebServer被单独作为一个类实现,并且封装好了调度函数。
    也就是说,main函数相当于一个开关,我打开了服务器的开关让他进入了listen状态,同时也转身打开了数据库的开关。
    当然,这个开关的信息,来自于config

    main.cpp代码解读

    这里的命令行解析是给数据库的运行方式传参,当然你可以什么都不传。
    定义,之后初始化了一个服务器对象。
    首先打开线程池,然后设置运行模式,之后就是启动监听和进入工作循环(事务循环)

    #include "config.h"
    
    int main(int argc, char *argv[])
    {
        //需要修改的数据库信息,登录名,密码,库名
        string user = "root";
        string passwd = "1215";
        string databasename = "Liweb_db";
    
        //命令行解析
        Config config;
        config.parse_arg(argc, argv);
    
        WebServer server;
    
        //初始化
        server.init(config.PORT, user, passwd, databasename, config.LOGWrite, 
                    config.OPT_LINGER, config.TRIGMode,  config.sql_num,  config.thread_num, 
                    config.close_log, config.actor_model);
        
    
        //日志
        server.log_write();
    
        //数据库
        server.sql_pool();
    
        //线程池
        server.thread_pool();
    
        //触发模式
        server.trig_mode();
    
        //监听
        server.eventListen();
    
        //运行
        server.eventLoop();
    
        return 0;
    }
    

    总结: 其实这一版的好处就是,给main瘦身。main本质上就是提供了入口,入口不需要太复杂。

    WebServer模块

    对于这种复杂模块,我会尽量根据调度顺序进行每个函数的分析,对于优点部分,我会重点标出。
    线程池是同步部分,数据库是额外部分这两个后面再讲
    目前是要搞清楚,怎么弄个反应堆打到我可以拿到事务,处理的问题稍后再说。目前只需要知道,我有个处理业务逻辑的池。

    trig_mode函数

    不用解释,对应不同功能

    void WebServer::trig_mode()
    {
        //LT + LT
        if (0 == m_TRIGMode)
        {
            m_LISTENTrigmode = 0;
            m_CONNTrigmode = 0;
        }
        //LT + ET
        else if (1 == m_TRIGMode)
        {
            m_LISTENTrigmode = 0;
            m_CONNTrigmode = 1;
        }
        //ET + LT
        else if (2 == m_TRIGMode)
        {
            m_LISTENTrigmode = 1;
            m_CONNTrigmode = 0;
        }
        //ET + ET
        else if (3 == m_TRIGMode)
        {
            m_LISTENTrigmode = 1;
            m_CONNTrigmode = 1;
        }
    }
    

    在仔细阅读这种复杂功能的模块之前,一定要理解清除调用逻辑。
    首先看main中分别调用了eventListen与eventLoop。根据见名知意的原则,我们可以推测出这是实现了listen部分与事务处理部分。

    eventListen函数

    如果你是初学者,不需要关注什么叫优雅的关闭连接这一部分,具体可以参考我的文章:Linux网络编程:知识点补充
    简单的民工三连调用不需要解释,作者加入了assert,提升了健壮性。
    之后就是调用epoll的三连了,将lfd上树,这里的上树封装为了addfd目的是为了可以更改模式。(cfd需要one_shot而lfd不需要)

    之后就是创建了管道,这里牵扯到进程间通信的问题。这么做的好处就是统一事件源。因为正常情况下,信号处理与IO处理不走一条路。
    这里的信号主要是超时问题
    具体的做法是,信号处理函数使用管道将信号传递给主循环,信号处理函数往管道的写端写入信号值,主循环则从管道的读端读出信号值,使用I/O复用系统调用来监听管道读端的可读事件,这样信号事件与其他文件描述符都可以通过epoll来监测,从而实现统一处理。

    void WebServer::eventListen()
    {
        //网络编程基础步骤
        m_listenfd = socket(PF_INET, SOCK_STREAM, 0);
        //如果它的条件返回错误,则终止程序执行
        assert(m_listenfd >= 0);
    
        //TCP连接断开的时候调用closesocket函数,有优雅的断开和强制断开两种方式
        
        //优雅关闭连接
        if (0 == m_OPT_LINGER)
        {
            //底层会将未发送完的数据发送完成后再释放资源,也就是优雅的退出
            struct linger tmp = {0, 1};
            setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));
        }
        else if (1 == m_OPT_LINGER)
        {
            //这种方式下,在调用closesocket的时候不会立刻返回,内核会延迟一段时间,这个时间就由l_linger得值来决定。
            //如果超时时间到达之前,发送完未发送的数据(包括FIN包)并得到另一端的确认,closesocket会返回正确,socket描述符优雅性退出。
            //否则,closesocket会直接返回 错误值,未发送数据丢失,socket描述符被强制性退出。需要注意的时,如果socket描述符被设置为非堵塞型,则closesocket会直接返回值。
            struct linger tmp = {1, 1};
            setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));
        }
    
        int ret = 0;
        struct sockaddr_in address;
        // bzero() 会将内存块(字符串)的前n个字节清零;
        // s为内存(字符串)指针,n 为需要清零的字节数。
        // 在网络编程中会经常用到。
        bzero(&address, sizeof(address));
        address.sin_family = AF_INET;
        address.sin_addr.s_addr = htonl(INADDR_ANY);
        address.sin_port = htons(m_port);
    
        int flag = 1;
        //允许重用本地地址和端口
        setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
        //传统绑定步骤
        ret = bind(m_listenfd, (struct sockaddr *)&address, sizeof(address));
        //>=0的设定 因为只有小于0才是错误情况
        assert(ret >= 0);
        //传统监听步骤
        ret = listen(m_listenfd, 5);
        assert(ret >= 0);
    
        utils.init(TIMESLOT);
    
        //epoll创建内核事件表
        epoll_event events[MAX_EVENT_NUMBER];
        m_epollfd = epoll_create(6);
        assert(m_epollfd != -1);
    
        //将lfd上树
        utils.addfd(m_epollfd, m_listenfd, false, m_LISTENTrigmode);
        http_conn::m_epollfd = m_epollfd;
    
        //创建管道套接字
        ret = socketpair(PF_UNIX, SOCK_STREAM, 0, m_pipefd);
        assert(ret != -1);
        //设置管道写端为非阻塞,为什么写端要非阻塞?
        //send是将信息发送给套接字缓冲区,如果缓冲区满了,则会阻塞,
        //这时候会进一步增加信号处理函数的执行时间,为此,将其修改为非阻塞。
        utils.setnonblocking(m_pipefd[1]);
    
        //设置管道读端为ET非阻塞 统一事件源
        utils.addfd(m_epollfd, m_pipefd[0], false, 0);
    
        utils.addsig(SIGPIPE, SIG_IGN);
    
        //传递给主循环的信号值,这里只关注SIGALRM和SIGTERM
        utils.addsig(SIGALRM, utils.sig_handler, false);
        utils.addsig(SIGTERM, utils.sig_handler, false);
    
        //每隔TIMESLOT时间触发SIGALRM信号
        alarm(TIMESLOT);
    
        //工具类,信号和描述符基础操作
        Utils::u_pipefd = m_pipefd;
        Utils::u_epollfd = m_epollfd;
    }
    

    总结: 完成了设置lfd与统一事件源,并且创建了带有一个节点的epoll树,同时完成了超时设定

    eventLoop函数

    这个函数可以说是始终伴随着程序始终。只要服务器不关,我就一直不退出,因为我退出了,main也退出了。
    明显看出,这一函数的逻辑就是不断的处理产生事件的节点(思考一下这里为什么这么叫)。
    而在epoll_wait返回后,我们主要处理三种事件:io事件,信号,新的连接
    也就是在for循环中的三次判断,并且每次处理完一组后,我们会刷新定时。

    void WebServer::eventLoop()
    {
        bool timeout = false;
        bool stop_server = false;
    
        while (!stop_server)
        {
            //监测发生事件的文件描述符
            int number = epoll_wait(m_epollfd, events, MAX_EVENT_NUMBER, -1);
            if (number < 0 && errno != EINTR)
            {
                LOG_ERROR("%s", "epoll failure");
                break;
            }
    
            //轮询有事件产生的文件描述符
            for (int i = 0; i < number; i++)
            {
                int sockfd = events[i].data.fd;
    
                //处理新到的客户连接
                if (sockfd == m_listenfd)
                {
                    bool flag = dealclinetdata();
                    if (false == flag)
                        continue;
                }
                else if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR))
                {
                    //服务器端关闭连接,移除对应的定时器
                    util_timer *timer = users_timer[sockfd].timer;
                    deal_timer(timer, sockfd);
                }
                //处理信号
                //管道读端对应文件描述符发生读事件
                //因为统一了事件源,信号处理当成读事件来处理
                //怎么统一?就是信号回调函数哪里不立即处理而是写到:pipe的写端
                else if ((sockfd == m_pipefd[0]) && (events[i].events & EPOLLIN))
                {
                    bool flag = dealwithsignal(timeout, stop_server);
                    if (false == flag)
                        LOG_ERROR("%s", "dealclientdata failure");
                }
                //处理客户连接上接收到的数据
                else if (events[i].events & EPOLLIN)
                {
                    dealwithread(sockfd);
                }
                else if (events[i].events & EPOLLOUT)
                {
                    dealwithwrite(sockfd);
                }
            }
            if (timeout)
            {
                utils.timer_handler();
    
                LOG_INFO("%s", "timer tick");
    
                timeout = false;
            }
        }
    }
    

    dealclinetdata函数

    其实笔者认为不加data更加符合情境,因为当前只是建立连接。
    就像刚才的模式介绍一样,lfd的两种模式。其实ET的存在就是应对存在服务器应付不过来连接请求,来提高效率的办法。
    建议LT使用。
    其实LT的额外循环是在epollwait部分,并且延迟体验是在用户端,用户可能傻傻的觉得自己卡了,问题不大

    bool WebServer::dealclinetdata()
    {
        struct sockaddr_in client_address;
        socklen_t client_addrlength = sizeof(client_address);
        //LT
        if (0 == m_LISTENTrigmode)
        {
            int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength);
            if (connfd < 0)
            {
                LOG_ERROR("%s:errno is:%d", "accept error", errno);
                return false;
            }
            if (http_conn::m_user_count >= MAX_FD)
            {
                utils.show_error(connfd, "Internal server busy");
                LOG_ERROR("%s", "Internal server busy");
                return false;
            }
            timer(connfd, client_address);
        }
    
        else
        {
            //ET
            while (1)
            {
                int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength);
                if (connfd < 0)
                {
                    LOG_ERROR("%s:errno is:%d", "accept error", errno);
                    break;
                }
                if (http_conn::m_user_count >= MAX_FD)
                {
                    utils.show_error(connfd, "Internal server busy");
                    LOG_ERROR("%s", "Internal server busy");
                    break;
                }
                timer(connfd, client_address);
            }
            return false;
        }
        return true;
    }
    

    dealwithread函数

    按照之前的思想,对于整个并发模式的思路,存在两个模式的切换:reactor与preactor(同步io模拟出)。它们的区别是对于数据的读取者是谁,对于reactor是同步线程来完成,整个读就绪放在请求列表上;而对于preactor则是由主线程,也就是当前的WebServer进行一次调用,读取后将读完成纳入请求队列上。

    同样,对于当前的fd我们要对他进行时间片的调整。同样的,当时间到期时,在定时器对象中,会有对应的下树操作。

    void WebServer::dealwithread(int sockfd)
    {
        util_timer *timer = users_timer[sockfd].timer;
    
        //reactor
        if (1 == m_actormodel)
        {
            if (timer)
            {
                adjust_timer(timer);
            }
    
            //若监测到读事件,将该事件放入请求队列
            m_pool->append(users + sockfd, 0);
    
            while (true)
            {
                if (1 == users[sockfd].improv)
                {
                    if (1 == users[sockfd].timer_flag)
                    {
                        deal_timer(timer, sockfd);
                        users[sockfd].timer_flag = 0;
                    }
                    users[sockfd].improv = 0;
                    break;
                }
            }
        }
        else
        {
            //proactor
            if (users[sockfd].read_once())
            {
                LOG_INFO("deal with the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr));
    
                //读完成事件,将该事件放入请求队列
                m_pool->append_p(users + sockfd);
    
                if (timer)
                {
                    adjust_timer(timer);
                }
            }
            else
            {
                deal_timer(timer, sockfd);
            }
        }
    }
    

    dealwithwrite函数

    逻辑与模式大致相同

    void WebServer::dealwithwrite(int sockfd)
    {
        util_timer *timer = users_timer[sockfd].timer;
        //reactor
        if (1 == m_actormodel)
        {
            if (timer)
            {
                adjust_timer(timer);
            }
    
            m_pool->append(users + sockfd, 1);
    
            while (true)
            {
                if (1 == users[sockfd].improv)
                {
                    if (1 == users[sockfd].timer_flag)
                    {
                        deal_timer(timer, sockfd);
                        users[sockfd].timer_flag = 0;
                    }
                    users[sockfd].improv = 0;
                    break;
                }
            }
        }
        else
        {
            //proactor
            if (users[sockfd].write())
            {
                LOG_INFO("send data to the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr));
    
                if (timer)
                {
                    adjust_timer(timer);
                }
            }
            else
            {
                deal_timer(timer, sockfd);
            }
        }
    }
    

    dealwithsignal函数

    与读写不同的是,这里的signal是处理函数,它不需要上队列。这里是通过管道的方式来告知WebServer。管道由epoll监控

    bool WebServer::dealwithsignal(bool &timeout, bool &stop_server)
    {
        int ret = 0;
        int sig;
        char signals[1024];
    
        //从管道读端读出信号值,成功返回字节数,失败返回-1
        //正常情况下,这里的ret返回值总是1,只有14和15两个ASCII码对应的字符
        ret = recv(m_pipefd[0], signals, sizeof(signals), 0);
        if (ret == -1)
        {
            return false;
        }
        else if (ret == 0)
        {
            return false;
        }
        else
        {
            //处理信号值对应的逻辑
            for (int i = 0; i < ret; ++i)
            {
                //这里面明明是字符
                switch (signals[i])
                {
                //这里是整型
                case SIGALRM:
                {
                    timeout = true;
                    break;
                }
                case SIGTERM:
                {
                    stop_server = true;
                    break;
                }
                }
            }
        }
        return true;
    }
    

    总结:目前我们只了解append是一个加入请求队列的函数,不去探究具体实现,当然在编程过程中我们应该也是这种思想,按照模块平行编程。而不是我想到什么功能就一定要先实现出来。递归编程容易把自己搞乱

    time函数集

    同样,我们把与时间片相关的调用,放在WebServer里,但是里面的细节,通过time这个类来实现。

    timer函数

    首先搞清楚,timer在什么时候调用?答案是在accept得到cfd的时候。这时候通过timer函数不只是初始化了cfd的时间,而且整体初始化。
    也就是说,当前服务器已经认可了这一连接,完成了三次握手,并且得到了用户标识,允许传输数据。
    这里为了提升性能,给到了3倍阈值的超时。

    void WebServer::timer(int connfd, struct sockaddr_in client_address)
    {
        users[connfd].init(connfd, client_address, m_root, m_CONNTrigmode, m_close_log, m_user, m_passWord, m_databaseName);
    
        //初始化client_data数据
        //创建定时器,设置回调函数和超时时间,绑定用户数据,将定时器添加到链表中
        users_timer[connfd].address = client_address;
        users_timer[connfd].sockfd = connfd;
        util_timer *timer = new util_timer;
        timer->user_data = &users_timer[connfd];
        timer->cb_func = cb_func;
        time_t cur = time(NULL);
        timer->expire = cur + 3 * TIMESLOT;
        users_timer[connfd].timer = timer;
        utils.m_timer_lst.add_timer(timer);
    }
    

    adjust_timer与deal_timer

    也是区分清楚什么时候调用。
    adjust可以看到是在产生事件之后,我为了还能传输数据,再给你刷新一下你的时间或者给你延长;而deal则是对于坏连接的一个处理。
    如果你对此不理解,可以跳过,你只需要知道what it is。至于how and why我会在time部分详细解析。

    void WebServer::adjust_timer(util_timer *timer)
    {
        time_t cur = time(NULL);
        timer->expire = cur + 3 * TIMESLOT;
        utils.m_timer_lst.adjust_timer(timer);
    
        LOG_INFO("%s", "adjust timer once");
    }
    void WebServer::deal_timer(util_timer *timer, int sockfd)
    {
        timer->cb_func(&users_timer[sockfd]);
        if (timer)
        {
            utils.m_timer_lst.del_timer(timer);
        }
        LOG_INFO("close fd %d", users_timer[sockfd].sockfd);
    }
    

    总结

    WebServer可以说是全功能的一个大集合,也就是说我们构建出了一个领导角色,左手epoll右手线程池。我尽量按照调用顺序来讲解函数,方便读者阅读代码。

    WebServer.h 头文件

    #ifndef WEBSERVER_H
    #define WEBSERVER_H
    
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <cassert>
    #include <sys/epoll.h>
    
    #include "./threadpool/threadpool.h"
    #include "./http/http_conn.h"
    
    const int MAX_FD = 65536;           //最大文件描述符
    const int MAX_EVENT_NUMBER = 10000; //最大事件数
    const int TIMESLOT = 5;             //最小超时单位
    
    class WebServer
    {
    public:
        WebServer();
        ~WebServer();
    
        void init(int port , string user, string passWord, string databaseName,
                  int log_write , int opt_linger, int trigmode, int sql_num,
                  int thread_num, int close_log, int actor_model);
        //线程池函数
        void thread_pool(); 
        //数据库池函数
        void sql_pool();    
        void log_write(); 
        //更改模式  
        void trig_mode();  
        //创建lfd 
        void eventListen(); 
        //当服务器非关闭状态 用于处理事件
        void eventLoop();   
        
        //定时器的操作
        void timer(int connfd, struct sockaddr_in client_address); 
        void adjust_timer(util_timer *timer);
        void deal_timer(util_timer *timer, int sockfd); 
    
        //处理用户数据 这里原作者存在拼写错误
        bool dealclinetdata(); 
        //信号
        bool dealwithsignal(bool& timeout, bool& stop_server); 
        //读事件
        void dealwithread(int sockfd);
        //写事件
        void dealwithwrite(int sockfd);
    
    public:
        //基础
        //监听端口
        int m_port;
        char *m_root;
        //日志
        int m_log_write;
        int m_close_log;
        //触发模式
        int m_actormodel;
    
        //进程通信模块
        int m_pipefd[2];
        //epoll根
        int m_epollfd;
        
        //用于接受用户连接
        http_conn *users;
    
        //数据库相关
        connection_pool *m_connPool;
        string m_user;         //登陆数据库用户名
        string m_passWord;     //登陆数据库密码
        string m_databaseName; //使用数据库名
        int m_sql_num;
    
        //http线程池
        threadpool<http_conn> *m_pool;
        int m_thread_num;
    
        //epoll_event 注册节点事件  
        epoll_event events[MAX_EVENT_NUMBER];
    
        int m_listenfd; //监听fd 申请一次
        int m_OPT_LINGER;
        int m_TRIGMode; //触发模式 ET+LT LT+LT LT+ET  ET+ET 
        int m_LISTENTrigmode; // 监听 ET/LT
        int m_CONNTrigmode;   // 连接 ET/LT
    
        //定时器相关
        client_data *users_timer;
        Utils utils;
    };
    #endif
    

    WebServer.cpp 完整代码

    #include "webserver.h"
    
    WebServer::WebServer()
    {
        //用来调用指定fd的所需功能模块
        users = new http_conn[MAX_FD];
    
        //root文件夹路径
        char server_path[200];
        getcwd(server_path, 200);
        char root[6] = "/root";
        m_root = (char *)malloc(strlen(server_path) + strlen(root) + 1);
        strcpy(m_root, server_path);
        strcat(m_root, root);
    
        //定时器
        users_timer = new client_data[MAX_FD];
    }
    
    WebServer::~WebServer()
    {
        close(m_epollfd);
        close(m_listenfd);
        close(m_pipefd[1]);
        close(m_pipefd[0]);
        delete[] users;
        delete[] users_timer;
        delete m_pool;
    }
    
    void WebServer::init(int port, string user, string passWord, string databaseName, int log_write, 
                         int opt_linger, int trigmode, int sql_num, int thread_num, int close_log, int actor_model)
    {
        m_port = port;
        m_user = user;
        m_passWord = passWord;
        m_databaseName = databaseName;
        m_sql_num = sql_num;
        m_thread_num = thread_num;
        m_log_write = log_write;
        m_OPT_LINGER = opt_linger;
        m_TRIGMode = trigmode;
        m_close_log = close_log;
        m_actormodel = actor_model;
    }
    
    void WebServer::trig_mode()
    {
        //LT + LT
        if (0 == m_TRIGMode)
        {
            m_LISTENTrigmode = 0;
            m_CONNTrigmode = 0;
        }
        //LT + ET
        else if (1 == m_TRIGMode)
        {
            m_LISTENTrigmode = 0;
            m_CONNTrigmode = 1;
        }
        //ET + LT
        else if (2 == m_TRIGMode)
        {
            m_LISTENTrigmode = 1;
            m_CONNTrigmode = 0;
        }
        //ET + ET
        else if (3 == m_TRIGMode)
        {
            m_LISTENTrigmode = 1;
            m_CONNTrigmode = 1;
        }
    }
    
    void WebServer::log_write()
    {
        if (0 == m_close_log)
        {
            //初始化日志
            if (1 == m_log_write)
                Log::get_instance()->init("./ServerLog", m_close_log, 2000, 800000, 800);
            else
                Log::get_instance()->init("./ServerLog", m_close_log, 2000, 800000, 0);
        }
    }
    
    void WebServer::sql_pool()
    {
        //初始化数据库连接池
        m_connPool = connection_pool::GetInstance();
        m_connPool->init("localhost", m_user, m_passWord, m_databaseName, 3306, m_sql_num, m_close_log);
    
        //初始化数据库读取表
        users->initmysql_result(m_connPool);
    }
    
    void WebServer::thread_pool()
    {
        //线程池
        m_pool = new threadpool<http_conn>(m_actormodel, m_connPool, m_thread_num);
    }
    
    void WebServer::eventListen()
    {
        //网络编程基础步骤
        m_listenfd = socket(PF_INET, SOCK_STREAM, 0);
        //如果它的条件返回错误,则终止程序执行
        assert(m_listenfd >= 0);
    
        //TCP连接断开的时候调用closesocket函数,有优雅的断开和强制断开两种方式
        
        //优雅关闭连接
        if (0 == m_OPT_LINGER)
        {
            //底层会将未发送完的数据发送完成后再释放资源,也就是优雅的退出
            struct linger tmp = {0, 1};
            setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));
        }
        else if (1 == m_OPT_LINGER)
        {
            //这种方式下,在调用closesocket的时候不会立刻返回,内核会延迟一段时间,这个时间就由l_linger得值来决定。
            //如果超时时间到达之前,发送完未发送的数据(包括FIN包)并得到另一端的确认,closesocket会返回正确,socket描述符优雅性退出。
            //否则,closesocket会直接返回 错误值,未发送数据丢失,socket描述符被强制性退出。需要注意的时,如果socket描述符被设置为非堵塞型,则closesocket会直接返回值。
            struct linger tmp = {1, 1};
            setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));
        }
    
        int ret = 0;
        struct sockaddr_in address;
        // bzero() 会将内存块(字符串)的前n个字节清零;
        // s为内存(字符串)指针,n 为需要清零的字节数。
        // 在网络编程中会经常用到。
        bzero(&address, sizeof(address));
        address.sin_family = AF_INET;
        address.sin_addr.s_addr = htonl(INADDR_ANY);
        address.sin_port = htons(m_port);
    
        int flag = 1;
        //允许重用本地地址和端口
        setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
        //传统绑定步骤
        ret = bind(m_listenfd, (struct sockaddr *)&address, sizeof(address));
        //>=0的设定 因为只有小于0才是错误情况
        assert(ret >= 0);
        //传统监听步骤
        ret = listen(m_listenfd, 5);
        assert(ret >= 0);
    
        utils.init(TIMESLOT);
    
        //epoll创建内核事件表
        epoll_event events[MAX_EVENT_NUMBER];
        m_epollfd = epoll_create(6);
        assert(m_epollfd != -1);
    
        //将lfd上树
        utils.addfd(m_epollfd, m_listenfd, false, m_LISTENTrigmode);
        http_conn::m_epollfd = m_epollfd;
    
        //创建管道套接字
        ret = socketpair(PF_UNIX, SOCK_STREAM, 0, m_pipefd);
        assert(ret != -1);
        //设置管道写端为非阻塞,为什么写端要非阻塞?
        //send是将信息发送给套接字缓冲区,如果缓冲区满了,则会阻塞,
        //这时候会进一步增加信号处理函数的执行时间,为此,将其修改为非阻塞。
        utils.setnonblocking(m_pipefd[1]);
    
        //设置管道读端为ET非阻塞 统一事件源
        utils.addfd(m_epollfd, m_pipefd[0], false, 0);
    
        utils.addsig(SIGPIPE, SIG_IGN);
    
        //传递给主循环的信号值,这里只关注SIGALRM和SIGTERM
        utils.addsig(SIGALRM, utils.sig_handler, false);
        utils.addsig(SIGTERM, utils.sig_handler, false);
    
        //每隔TIMESLOT时间触发SIGALRM信号
        alarm(TIMESLOT);
    
        //工具类,信号和描述符基础操作
        Utils::u_pipefd = m_pipefd;
        Utils::u_epollfd = m_epollfd;
    }
    
    void WebServer::timer(int connfd, struct sockaddr_in client_address)
    {
        users[connfd].init(connfd, client_address, m_root, m_CONNTrigmode, m_close_log, m_user, m_passWord, m_databaseName);
    
        //初始化client_data数据
        //创建定时器,设置回调函数和超时时间,绑定用户数据,将定时器添加到链表中
        users_timer[connfd].address = client_address;
        users_timer[connfd].sockfd = connfd;
        util_timer *timer = new util_timer;
        timer->user_data = &users_timer[connfd];
        //时间到了踢出树
        timer->cb_func = cb_func;
        time_t cur = time(NULL);
        timer->expire = cur + 3 * TIMESLOT;
        users_timer[connfd].timer = timer;
        utils.m_timer_lst.add_timer(timer);
    }
    
    //若有数据传输,则将定时器往后延迟3个单位
    //并对新的定时器在链表上的位置进行调整
    void WebServer::adjust_timer(util_timer *timer)
    {
        time_t cur = time(NULL);
        timer->expire = cur + 3 * TIMESLOT;
        utils.m_timer_lst.adjust_timer(timer);
    
        LOG_INFO("%s", "adjust timer once");
    }
    
    void WebServer::deal_timer(util_timer *timer, int sockfd)
    {
        timer->cb_func(&users_timer[sockfd]);
        if (timer)
        {
            utils.m_timer_lst.del_timer(timer);
        }
    
        LOG_INFO("close fd %d", users_timer[sockfd].sockfd);
    }
    
    
    bool WebServer::dealclinetdata()
    {
        struct sockaddr_in client_address;
        socklen_t client_addrlength = sizeof(client_address);
        //LT
        if (0 == m_LISTENTrigmode)
        {
            int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength);
            if (connfd < 0)
            {
                LOG_ERROR("%s:errno is:%d", "accept error", errno);
                return false;
            }
            if (http_conn::m_user_count >= MAX_FD)
            {
                utils.show_error(connfd, "Internal server busy");
                LOG_ERROR("%s", "Internal server busy");
                return false;
            }
            timer(connfd, client_address);
        }
    
        else
        {
            //ET
            while (1)
            {
                int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength);
                if (connfd < 0)
                {
                    LOG_ERROR("%s:errno is:%d", "accept error", errno);
                    break;
                }
                if (http_conn::m_user_count >= MAX_FD)
                {
                    utils.show_error(connfd, "Internal server busy");
                    LOG_ERROR("%s", "Internal server busy");
                    break;
                }
                timer(connfd, client_address);
            }
            return false;
        }
        return true;
    }
    
    bool WebServer::dealwithsignal(bool &timeout, bool &stop_server)
    {
        int ret = 0;
        int sig;
        char signals[1024];
    
        //从管道读端读出信号值,成功返回字节数,失败返回-1
        //正常情况下,这里的ret返回值总是1,只有14和15两个ASCII码对应的字符
        ret = recv(m_pipefd[0], signals, sizeof(signals), 0);
        if (ret == -1)
        {
            return false;
        }
        else if (ret == 0)
        {
            return false;
        }
        else
        {
            //处理信号值对应的逻辑
            for (int i = 0; i < ret; ++i)
            {
                //这里面明明是字符
                switch (signals[i])
                {
                //这里是整型
                case SIGALRM:
                {
                    timeout = true;
                    break;
                }
                case SIGTERM:
                {
                    stop_server = true;
                    break;
                }
                }
            }
        }
        return true;
    }
    
    void WebServer::dealwithread(int sockfd)
    {
        util_timer *timer = users_timer[sockfd].timer;
    
        //reactor
        if (1 == m_actormodel)
        {
            if (timer)
            {
                adjust_timer(timer);
            }
    
            //若监测到读事件,将该事件放入请求队列
            m_pool->append(users + sockfd, 0);
    
            while (true)
            {
                if (1 == users[sockfd].improv)
                {
                    if (1 == users[sockfd].timer_flag)
                    {
                        deal_timer(timer, sockfd);
                        users[sockfd].timer_flag = 0;
                    }
                    users[sockfd].improv = 0;
                    break;
                }
            }
        }
        else
        {
            //proactor
            if (users[sockfd].read_once())
            {
                LOG_INFO("deal with the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr));
    
                //读完成事件,将该事件放入请求队列
                m_pool->append_p(users + sockfd);
    
                if (timer)
                {
                    adjust_timer(timer);
                }
            }
            else
            {
                deal_timer(timer, sockfd);
            }
        }
    }
    
    void WebServer::dealwithwrite(int sockfd)
    {
        util_timer *timer = users_timer[sockfd].timer;
        //reactor
        if (1 == m_actormodel)
        {
            if (timer)
            {
                adjust_timer(timer);
            }
    
            m_pool->append(users + sockfd, 1);
    
            while (true)
            {
                if (1 == users[sockfd].improv)
                {
                    if (1 == users[sockfd].timer_flag)
                    {
                        deal_timer(timer, sockfd);
                        users[sockfd].timer_flag = 0;
                    }
                    users[sockfd].improv = 0;
                    break;
                }
            }
        }
        else
        {
            //proactor
            if (users[sockfd].write())
            {
                LOG_INFO("send data to the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr));
    
                if (timer)
                {
                    adjust_timer(timer);
                }
            }
            else
            {
                deal_timer(timer, sockfd);
            }
        }
    }
    
    void WebServer::eventLoop()
    
    展开全文
  • Web server is the container which deploy and run web application, so that all the user can get access to some content through Web browser. 选用web服务器 我们要选和JSP/servlet兼容的服务器。 web server...

    Web server is the container which deploy and run web application, so that all the users can get access to some content through Web browser.
    选用web服务器 我们要选和JSP/servlet兼容的服务器。

    Web server(就是HTTP服务器): only support http protocol, html, url. the main function of it is to deliver page so that web browser can parse and render. in short, it used to handle http requests. [Apache, nginx, Tomcat(轻量级应用服务器)]
    application server: used for providing function for client to use. so any server which have the application to explain and execute server-side code.(JBoss, WebLogic)

    总结:
    web serverhttp serverhandle http request
    application server is bigger, used to explain and execute server-side code (business logic)

    展开全文
  • 文章目录1、前言2、代码3、硬件连接 —— NodeMcu4、硬件连接 —— ESP015、测试结果 授人以鱼不如授人以渔,目的...建立一个独立的 ESP8266 Web Server 去控制两个输出口(例如两个LED)。 这个 ESP8266(NodeMcu

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力。希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石。。。

    快速导航
    单片机菜鸟的博客快速索引(快速找到你要的)

    重点说一下,麻烦三连点赞,你的点赞是博主创作的前进动力

    1、前言

    建立一个独立的 ESP8266 Web Server 去控制两个输出口(例如两个LED)。

    这个 ESP8266(NodeMcu) Web Server会在手机上访问浏览器,并且是局域网有效

    展开全文
  • 文章目录1、前言2、Installing the DHT Library for ESP82663、代码4、测试结果 授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力。希望大家分享给你周边需要的朋友或者同学,说不定大神...

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力。希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石。。。

    快速导航
    单片机菜鸟的博客快速索引(快速找到你要的)

    重点说一下,麻烦三连点赞,你的点赞是博主创作的前进动力

    1、前言

    到Github去下载zip包

    解压放入到Arduino lib目录

    建立一个 ESP8266 Web Server 去显示DHT11或者DHT22的温湿度。

    展开全文
  • 文章目录1、前言2、Asynchronous Web Server2.1 Asynchronous Web Server优势2.2 电路原理2.3 工作原理3、代码4、测试结果 授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力。希望大家...
  • ESP8266WebServer库 文章目录 ESP8266WebServer库 1.webserver管理方法 1.1 ESP8266WebServer() —— 创建web server 1.2 begin() —— 启动web server 1.3 close() —— 关闭web server 1.4 ...
  • 此博客记录对于TinyWebServer项目的学习,并根据自己的理解做出些许更改。 原项目地址:https://github.com/qinguoyi/TinyWebServer 服务器基本规范 服务器框架 服务器程序虽然种类繁多,但是基本框架都是一样的。 ...
  • TinyWebServer代码详细讲解(http模块)http模块设计思路http_conn.cppread业务函数集process_read函数parse_request_line函数总结 这里的参照的代码是https://github.com/qinguoyi/TinyWebServer 对于原代码的不足...
  • 8、简单Web Server程序的设计与实现

    千次阅读 2021-07-13 15:43:24
    8、简单Web Server程序的设计与实现 二、设计内容 实现一个简单的 Web Server,能够响应客户端的请求将指定目录下的 HTML 或 text 件通过指定的TCP 端发送给客户端。具体编程要求是: (1)服务器启动时可指定服务...
  • tasklist|findstr 端口号 3、结束这个进程 taskkill /f /t /im 进程名 以上就是Web server failed to start. Port XXX was already in use.【完美解决方案】的全部内容 版权声明: 原创博主:牛哄哄的柯南 博主原文...
  • background 通过一个项目实现c++后台的基本知识点...在实现之前,我需要对一些基本服务器开发有一个概念,它是实现了什么? content 需求介绍 reference 如何真正从0 开始一个后台项目:here 服务器设计指南 ...
  • ESP8266WebServer.h> const char *ssid = “PDCN”; const char *passwd = “1234567890”; ESP8266WebServer esp8266_server(80); void setup() { // put your setup code here, to run once: Serial.begin(9600...
  • Easy File Sharing Web Server(漏洞)

    千次阅读 2021-11-15 10:31:09
    在靶机打开Easy File Sharing Web Server。 利用kali的nmap扫描靶机,发现提供Easy File Sharing Web Server服务。 用searchsploit Easy File Sharing Web Server指令后,可以看到很多脚本,这里我们使用python ...
  • Node JS启动webserver服务

    2021-01-04 21:37:50
    在项目根目录创建server.js文件,内容如下: //引入http模块 var http = require("http"); //设置主机名 var hostName = '127.0.0.1'; //设置端口 var port = 8080; //创建服务 var server = http.createServer...
  • Unable to connect to web server ‘IIS Express’ If you get an error message that says Unable to connect to web server ‘IIS Express’, close Visual Studio and then open it by using the Run as ...
  • VS code内置浏览器需要自己下载安装,下面我们就来看看下载安装使用VS code内置浏览器...3、点击顶部搜索栏输入“Preview on Web Server” 4、 搜索结果第一个就是 点击install下载 5、回到代码部分 ,鼠标右键单击,
  • 原因是因为JDK版本原因,虽然看到nacos-server.jar就是用1.8.0-131编译的,但内置的tomcat的依赖和jdk冲突了。 将JDK改为1.8.0-221后问题解决。(当然也不一定是221,要看为什么jar冲突了) ...
  • 本案例以ESP8266作为服务端,利用Arduino开发环境搭建WebServer和TCPServer。WebServer、TCPServer最大的区别,各位小伙伴可以去回顾一下网络的知识,BS架构与CS架构的区别,在此不做过多的讲解。 一、整体的程序...
  • org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat 问题...
  • Web server failed to start. Port 8887 was already in use. 二、解决方式 1、首先打开cmd运行界面,输入netstat -ano,查看端口进程ID。 2、如下图所示,8887端口的进程ID(PID)为16080 3、然后将该ID的进程...
  • 1 Tinywebserver介绍 Linux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的服务器. 使用 线程池 + 非阻塞socket + epoll(ET和LT均实现) + 事件处理(Reactor和Proactor均实现) 的并发模型 使用...
  • org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat at org...
  • Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that's listening on port 8080 or configure this application to listen on another port. 报错原因: 是...
  • 导读:使用IntelliJ Idea启动项目时,报了这个错误,意思就是端口被占用; ##解决方式 1、找到占用这个端口的进程,结束掉这个进程 然后获取pid为3588的进程详细信息,并删除这个进程: 结束进程成功的样子,这是...
  • 基于Arduino+ESP8266实现的WebServer,实现在局域网,通过其他设备来控制连接在ESP8266上的LED灯带,实现了智能配网,Winform上位机等功能。
  • full nginx-common sudo apt-get install nginx 成功解决:ubuntu安装nginx时报错Failed to start A high performance web server and a reverse proxy server.的问题 欢迎小伙伴讨论,文章内容如有错误请在评论区...
  • live Server配置之open-with-Live-Server 无法打开浏览器改用Preview on Web Server插件正常打开浏览器Preview on Web Server插件安装配置 插件Preview on Web Server的端口号 Preview on Web Server插件安装 参考:...
  • 获取pid为10788的进程详细信息,并删除的方式: C:\Windows\system32>tasklist|findstr "10788" ApplicationWebServer.exe 10788 Services 0 10,688 K # taskkill的详细命令可以输入: taskkill /? 查看 C:\Windows...
  • Web server failed to start. Port 8001 was already in use. Action: Identify and stop the process that’s listening on port 8001 or configure this application to listen on another port. Disconnected ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,501,787
精华内容 600,714
关键字:

webserver是什么