精华内容
下载资源
问答
  • webserver
    万次阅读 多人点赞
    2019-06-20 08:56:38

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

    共同学习成长QQ群 622368884,不喜勿加,里面有一大群志同道合的探路人

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

    如果觉得有用,麻烦点赞收藏,您的支持是博主创作的动力。

    更多相关内容
  • 这里的参照的代码是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()
    
    展开全文
  • 因为刚开始 对于很多后台开发的前辈啊 所给出的后端学习的路啊 就比如写一个WebServer 其实这个项目就个人而言 真的肯定是 作为后台开发最好的入手的一个项目了 这个WebServer 不是写一个 就只能支持HTTP协议的...


    Love 6’s C++ High-Performance WebServer(这一路想说的话)


    这个 从零自制高性能多线程的WebServer博客系列呢 刚开始我写之初 其实也就是想记录一下 一个linux后端开发者 以此作为 网络编程的起点 以及多线程编程的起点的博客记录而已

    因为刚开始 对于很多后台开发的前辈啊 所给出的后端学习的路啊 就比如写一个WebServer 其实这个项目就个人而言 真的肯定是 作为后台开发最好的入手的一个项目了 这个WebServer 不是写一个 就只能支持HTTP协议的服务器 而是从零开始写一个能够具有超级高的复用性的网络库 以此的基础上来实现一个高性能的HTTP服务器

    记录之初 只是刚开始学习的时候太迷茫了 不知道从哪里入手 从什么地方开始 从哪里作为起点 都不知道… 在网上搜寻良久 也没有找到解决办法 我相信如果有之后想从事后台开发的初学者的话 又刚好想写一个这样类似的项目的话 看到有一位在这条路已经走过一遍的前者 把自己的学习轨迹记录下来 我相信还是会走更少的弯路的

    本项目 所基于的书籍 最主要是两本
    《Linux高性能服务器编程》----(游双)(现在快绝版了)

    《Linux多线程服务器编程》使用muduo C++网络库 ---- 陈硕

    本项目 所平时参考的网络库的源码 是muduo 网络库 也就是陈硕大佬所写的网络库 耦合性非常低 代码没有一点冗杂 相当精干
    项目的网络库 底层实现逻辑 是基于muduo网络库的实现逻辑 以此为基础上 借鉴实现的网络库

    大概muduo库源码 我应该是会看好几遍的 然后书籍的话 第一本是入门 相当好的入门书籍 没有第一本书籍所做的知识铺垫 模型铺垫 我相信我在阅读第二本书 muduo源码 书籍中的实现模型 相信会是相当相当吃力的

    我发现 网络上面 对于一个从零实现WebServer 那种边学习边记录的博客太少 花时间写下这一系列博客 也算是有些许意义吧

    –2022/3/26 正在写第六篇系列博客时 心中有感而发所写 13:23 笔者留


    写到现在目前第十五篇 目前也就是今天晚上就可以把 这个项目最后一个功能 日志库给完结了 想到现在一路走来 不过也就接近40天的时间 尽管写到现在 也有些许疲倦 写这个项目也没有像上面写第六篇 或者写第一篇 《万丈高楼平地起…》那样有动力 但仔细想想 也属实不易

    这一路走来 遇到了各种形形色色的问题 自己也在其中 不断地巩固自己的一些薄弱的编程基础 也学习到了很多新的实践的工具 自己对于稍微大型一点的项目编程 也有了更多的经验了

    如果把之前的Tiny_OS Tiny_Regex都不算做正式项目的话(Tiny_OS应该还是算的)那这个这个High-Profomance WebServer就算作第一个我的正式项目了
    其中有过开心喜悦 也有过困顿迷惑 有忽然发现解决棘手问题的方法的那种欢呼雀跃 也有因为一个小问题连续苦闷五天的难过时间

    总之还是坚持下来了 可能在编写这段话的时候 再过个几天就要完结这个项目了 我也要进入下一个阶段了 哈哈 希望早点完结吧

    –2022/4/19 正在写第十五篇系列博客 18:41留


    这是最后我对这个项目 也是算是对我启蒙意义最重的一个项目 留下的可能是最后一段话了
    我在这个项目 开始于3/15 今天是4/24 已经过去了45天了
    原本我的计划是在一个月内完成这个项目 显然现在发现是不行的 除非之前就做过类似的项目


    我在这个项目中 收获了太多太多 这个项目我认为 对于后台开发的同学 应该算是必做项目 因为可以收获的太多太多了
    在真的这个项目要结束时 之前总觉得有好多好多话想说 现在却一句都说不出了

    最后给一点意见吧 早点使用Git 在这个项目多去用用一些小工具 多去探索一些平时根本没有用上的Tool 尽量自己尝试去解决问题 尤其是自己编写的东西出现了问题 遇到困难与挫折 永远保持一颗坚持下去的心 不要放弃 保持学习 Keep Learning 当你学的东西越多的时候 你会发现 你不会的东西也越多

    stay hungry stay humble
    祝各位 我们江湖再见~
    还是用这个可爱的表情结束我们的文章吧ヾ( ̄▽ ̄)Bye~Bye~

    2022/4/24 18:48留


    1、全流程实现博客链接


    友情提示:
    博客中有部分代码编译时用的是g++-9.x 到后面的时候才更换回了低版本g++-4.8 对于低版本编译器更友好 但并无大碍 如果在复制代码时 编译出现问题是由于编译器版本过低 无法识别的话 稍加修改代码即可

    从零开始自制实现WebServer(一)---- 万丈高楼平地起 步子得一步一步慢慢走
    从零开始自制实现WebServer(二)---- 勿在浮沙筑高层 摸谈初试进程/线程池与高效并发模型
    从零开始自制实现WebServer(三)---- 华山论剑剑指线程池 大刀阔斧终开始阅读源码
    从零开始自制实现WebServer(四)---- 长望漫漫路觉应先积跬步 不论精致粗糙先砌小砖小瓦
    从零开始自制实现WebServer(五)---- 浅沿芳草鲜花小路静心踱步 拨云见雾终见多线程ThreadPool
    从零开始自制实现WebServer(六)---- 跌跌撞撞奔向HTTP状态机 学习途中拾慧纠正过往细节偏差
    从零开始自制实现WebServer(七)---- 进入首次压力测试开始调优 休整不牢地基开始大整改
    从零开始自制实现WebServer(八)---- 花费两天解决性能瓶颈问题 介绍一路调试历程以及推荐各种好用的工具
    从零开始自制实现WebServer(九)---- 目前总览代码如下 得继续脚步前行
    从零开始自制实现WebServer(十)---- 费时五天研究性能瓶颈 对整个服务器代码大改造 最后发现gcc优化竟是最终问题
    从零开始自制实现WebServer(十一)---- 花费数天完善代码寻找瓶颈 修修改改代码初具规模 罗列目前全部代码
    从零开始自制实现WebServer(十二)---- 剑指定时器小根堆处理 给EventLoop定时任务处理一个温暖的家
    从零开始自制实现WebServer(十三)---- 定时器实现踢掉服务器空闲连接功能 让服务器不再被长连接强行霸占
    从零开始自制实现WebServer(十四)---- 终最后迈向日志库 解决流输出重载前端基本框架搭建 为异步日记库做好准备
    从零开始自制实现WebServer(十五)---- 日志库部分完结啦 实用小件DOUBLE-BUFFERING优化异步写入性能
    从零开始自制实现WebServer(十六)---- 学习新工具CMake自动编写MakeFile 分门别类整理源文件心情愉悦
    从零开始自制实现WebServer(十七)---- 重新阅读Muduo服务器编程书籍 做最后的小改小动 项目终究要迎来终声了
    从零开始自制实现WebServer(十八)---- 对服务器做最后的压力测试 WebBench压测小工具 项目迎来终章
    从零开始自制实现WebServer(十九)---- 正式系统的学习一下Git 捣鼓捣鼓github以及一些其他的小组件
    从零开始自制实现WebServer(二十)---- C++ High-Performance WebServer源码实现(Util核心代码部分)
    从零开始自制实现WebServer(二十)---- C++ High-Performance WebServer源码实现(Base核心代码部分)
    从零开始自制实现WebServer(二十)---- C++ High-Performance WebServer源码实现(Http核心代码部分)
    从零开始自制实现WebServer(二十)---- C++ High-Performance WebServer源码实现(Timer核心代码部分)
    从零开始自制实现WebServer(二十)---- C++ High-Performance WebServer源码实现(Logging核心代码部分)


    2、源码仓库链接


    GITHUB源码仓库链接:Love 6’s Github 源码仓库
    C++ High-Performance WebServer Github链接:Love 6’s C++ High-Performance WebServer

    最后项目代码(博客链接)
    从零开始自制实现WebServer(二十)---- C++ High-Performance WebServer源码实现(Util核心代码部分)
    从零开始自制实现WebServer(二十)---- C++ High-Performance WebServer源码实现(Base核心代码部分)
    从零开始自制实现WebServer(二十)---- C++ High-Performance WebServer源码实现(Http核心代码部分)
    从零开始自制实现WebServer(二十)---- C++ High-Performance WebServer源码实现(Timer核心代码部分)
    从零开始自制实现WebServer(二十)---- C++ High-Performance WebServer源码实现(Logging核心代码部分)

    展开全文
  • c#WebServer简单示例

    热门讨论 2011-06-02 11:01:10
    c#WebServer简单示例 这是我第一次学习webserver时候别人给的觉得非常好用!简单明白!
  • 常用webserver 比较

    万次阅读 2022-03-16 22:14:05
    1)一般Tomcat 是处理JAVA,也就是我们说的JSP语言WEB环境的 2)Tomcat,比较侧重于Servlet引擎,如果以Standalone方式运行,功能上与Apache等效,支持JSP,但对静态网页不太理想 主要用来跑jsp php python等 IIS ...

    市场占有情况

    在这里插入图片描述

    link

    特性比较

    概念特性场景
    NginxNginx是俄罗斯人编写的十分轻量级的HTTP服务器,Nginx,它的发音为“engine X”,是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/POP3/SMTP 代理服务器1)高并发、内存消耗少,成本低
    2)nginx是异步的,多个连接(万级别)可以对应一个进程
    3)非阻塞性异步功能
    nginx,则一般是做静态(html和js),本身不具备动态解析功能,需要配置其他插件或通过其他软件协同才具备动态功能
    ApacheApache HTTP服务器是一个模块化的服务器,可以运行在几乎所有广泛使用的计算机平台上。其属于应用服务器。Apache支持支持模块多,性能稳定,Apache本身是静态解析,适合静态HTML、图片等,但可以通过扩展脚本、模块等支持动态页面等1)Apache是同步多进程模型,一个连接对应一个进程
    2)支持模块多,功能多;运行稳定性强;支持PHP模块,无需安装其他多余的组件就可以实现.php动态页面的解析;地址重写功能(rewrite)强大
    在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。
    TomcatTomcat是应用(Java)服务器,它只是一个Servlet(JSP也翻译成Servlet)容器,可以认为是Apache的扩展,但是可以独立于Apache运行。1)一般Tomcat 是处理JAVA,也就是我们说的JSP语言WEB环境的
    2)Tomcat,比较侧重于Servlet引擎,如果以Standalone方式运行,功能上与Apache等效,支持JSP,但对静态网页不太理想
    主要用来跑jsp php python等
    IISiis是微软开发的web服务器,需要收费,主要用来跑 asp.net asp php,只能在windows下运行。windows下运行,跑asp.netwindows
    展开全文
  • 文章目录目的WebServer基础说明官方WebServer例程演示第三方WebServer库介绍总结 目的 WebServer基础说明 官方WebServer例程演示 MicroPython官方的WebServer例程可以在下面链接中找到: ...下面拿其中一...
  • 文章目录前言Webserver 介绍Webserver基础架构Webserver详细技术总结 前言 【C++后端开发项目入门:TinywebServer】:一个简易轻型的webserver服务器,对于C++后台开发方向作为起始项目是一个不错的选择。关于web...
  • webserver技术总结之一:webserver概念

    万次阅读 多人点赞 2019-06-14 18:03:22
    比如我们在开发项目的过程中,需要调用别的公司提供的数据,这里我们就需要使用到webserver。当前的应用程序开发逐步的呈现了两种迥然不同的倾向:1:基于浏览器的瘦客户端应用程序,2:基于浏览器的富客户端应用...
  • Web Server与App Server

    千次阅读 2018-06-20 15:29:49
    Web Server  常见的Web Server有Apache Server与Nginx。  Apache Http Server是Apache软件基金会下的一个项目,是一款开源的HTTP服务器软件(它也可以作为邮件代理服务器、通用的TCP代理服务器)。  Nginx之前...
  • 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...
  • ESP32开发实例(七),WebServer使用

    千次阅读 2020-08-23 20:41:55
    一、什么是WebServer 二、基本语法 三、路径参数的简单认识 四、用户认证 五、总结 一、什么是WebServer Web Server中文名称叫网页服务器或web服务器。WEB服务器也称为WWW(WORLD WIDE WEB)服务器,主要...
  • 本案例以ESP8266作为服务端,利用Arduino开发环境搭建WebServer和TCPServer。WebServer、TCPServer最大的区别,各位小伙伴可以去回顾一下网络的知识,BS架构与CS架构的区别,在此不做过多的讲解。 一、整体的程序...
  • 什么是WEBserver? 经常使用的WEBserver有哪些?   一、什么是WEBserver  Webserver能够解析HTTP协议。当Webserver接收到一个HTTP请求,会返回一个HTTP响应,比如送回一个HTML页面。为了处理一个请求Webserver能够...
  • tasklist|findstr 端口号 3、结束这个进程 taskkill /f /t /im 进程名 以上就是Web server failed to start. Port XXX was already in use.【完美解决方案】的全部内容 版权声明: 原创博主:牛哄哄的柯南 博主原文...
  • 使用Arduino开发ESP32(09):WebServer使用演示与说明

    万次阅读 多人点赞 2019-04-25 12:42:04
    声明WebServer对象并设置端口号,一般WebServer端口号使用80; 使用on()方法注册链接与回调函数; 使用begin()方法启动服务器进行请求监听; 使用handleClient()处理来自客户端的请求; 可以使用下面代码进行测试:...
  • WebServer详解 使用C++语言,编写了一个基于Linux的HttpServer,能够实现上万的QPS。 项目地址: gitee仓库 github仓库 功能 利用I/O多路复用技术的Epoll与线程池实现【单Reactor、多线程】的高并发服务器模型; ...
  • ● nginx.service - A high performance web server and a reverse proxy server Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled) Active: failed (Result
  • 用Python实现简单的Web Server

    万次阅读 2019-05-02 17:05:43
    Web Server的概念 用Python实现Web Server Python 2中SimpleHTTPServer模块被合并到Python 3的http.server模块。它支持目录浏览,指定端口,指定绑定地址等。 方法一:直接在命令行调用http.server模...
  • vs-code插件 -- Preview on Web Server 插件 使用 及设置默认浏览器
  • VS code内置浏览器需要自己下载安装,下面我们就来看看下载安装使用VS code内置浏览器...3、点击顶部搜索栏输入“Preview on Web Server” 4、 搜索结果第一个就是 点击install下载 5、回到代码部分 ,鼠标右键单击,
  • 10月 30 14:36:12 tedu systemd[1]: Failed to start A high performance web server and a reverse proxy server. 10月 30 14:36:12 tedu systemd[1]: nginx.service: Unit entered failed state. 10月 30 14:36:...
  • 解决Web server failed to start. Port 8080 was already in use.

    千次阅读 多人点赞 2022-03-29 09:08:42
    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. 方法1:关掉...
  • 环境 系统: 阿里云Ubuntu...Failed to start A high performance web server and a reverse proxy server 完整报错信息如下: $ systemctl status nginx * nginx.service - A high performance web server and a rev
  • 1 Tinywebserver介绍 Linux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的服务器. 使用 线程池 + 非阻塞socket + epoll(ET和LT均实现) + 事件处理(Reactor和Proactor均实现) 的并发模型 使用...
  • 203) ~[spring-boot-2.2.1.RELEASE.jar:2.2.1.RELEASE] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:...
  • 2020-11-27 16:53:38.979 ERROR 5408 --- [ main] o.s.boot....org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.b...
  • Nacos Unable to start web server; nested exception is org.springframework.boot.web.server.WebSer
  • 解决:启动springboot项目,出现异常:Unable to start web server; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot....
  • Unable to start web server 错误处理记录

    千次阅读 2020-01-17 12:56:54
    1.提示错误 用到最新的java 为13,Log4j2 error错误。 ERROR SpringApplication ...org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.spri...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,711,810
精华内容 684,724
关键字:

webserver