精华内容
下载资源
问答
  • live Server配置之open-with-Live-Server 无法打开浏览器改用Preview on Web Server插件正常打开浏览器Preview on Web Server插件安装配置 插件Preview on Web Server的端口号 Preview on Web Server插件安装 参考:...

    live Server配置之open-with-Live-Server 无法打开浏览器改用Preview on Web Server插件正常打开浏览器

    Preview on Web Server插件安装

    参考: https://blog.csdn.net/zhouwei_doris/article/details/80604604

    配置 插件Preview on Web Server的端口号

    在这里插入图片描述
    添加默认浏览器和设置端口号:
    在这里插入图片描述
    回到Visual Studio Code编辑页面,右击,选择 vscode-preview-server:launch on browser
    在这里插入图片描述
    即可打开浏览器预览界面了。

    展开全文
  • 这里的参照的代码是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()
    
    展开全文
  • 9、ESP8266 Web Server

    2021-07-15 10:54:38
    Web Server 可以实现Esp8266配置,传感器状态展示,遥控小车等功能。 创建Web Server有两种方式: 1)使用Arduino IDE 2)使用NodeMCU固件 使用Arduino IDE创建Web Server 先准备好Arduino IDE开发环境 ...

     

    Web Server 可以实现Esp8266配置,传感器状态展示,遥控小车等功能。

    创建Web Server有两种方式:

    1)使用Arduino IDE

    2)使用NodeMCU固件

    使用Arduino IDE创建Web Server

    先准备好Arduino IDE开发环境

    我们要实现这样的页面展示:带控制按钮和引脚状态展示

     

    准备好零件,如下图所示,将两个 LED 连接到 ESP8266,一个 LED 连接到 GPIO 4 (D2),另一个连接到 GPIO 5 (D1)。电阻可以选220欧姆或者330欧姆都可以。

    展开全文
  • vscode中用preview on web server打开,结果页面显示:Cannot GET /d:/ab/getpost.html 请问是什么原因呢?$.get方法$(function(){$("#send").click(function(){$.get("get1.txt",{username:$("#username").val...

    我在vscode中用preview on web server打开,结果页面显示:Cannot GET /d:/ab/getpost.html 请问是什么原因呢?

    $.get方法

    $(function(){

    $("#send").click(function(){

    $.get("get1.txt",

    {

    username:$("#username").val(),

    content:$("#content").val()

    },

    function(data,textStatus){

    $("#resTest").html(data);

    }

    )

    })

    /*$("#send").click(function(){

    $.get("get2.php",

    {

    username:$("#username").val(),

    content:$("#content").val()

    },function(data,textStatus){

    var username=$(data).find("comment").attr("username");

    var content=$(data).find("comment content")

    .attr("content").text();

    var txtHtml="

    "+username+

    ":

    "+content+"

    ";

    $("#resTest").html(txtHtml);

    })

    })*/

    })

    评论

    姓名:

    内容:

    展开全文
  • 从网上借了一张图,就是Internal Server Error这个问题 问题原因: 下面这个strawberry-perl-5.10.1.0.msi安装后需要配置环境变量 解决: 环境变量 C:\Strawberry\c\bin; C:\Strawberry\perl\bin; C:\Strawberry\...
  • 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 ...
  • 文章目录1、前言2、代码3、硬件连接 —— NodeMcu4、硬件连接 —— ESP015、测试结果 授人以鱼不如授人以渔,目的...建立一个独立的 ESP8266 Web Server 去控制两个输出口(例如两个LED)。 这个 ESP8266(NodeMcu
  • 打开Start HP Web Tour Server,窗口闪现一下就消失了 找到错误原因 找到Start HP Web Tour Server文件所在位置,右击Start HP Web Tour Server =》打开文件所在位置 编辑StartServer.bat,右击=》编辑,最后一...
  • 最近在学d3.js (看的b站清华大学的教学视频),想着把教学代码先运行下,直接用Preview on Web Server插件VS Code里浏览,无法显示 但是用浏览器直接打开就OK 找了好久为啥 一个老哥的答案,说d3的版本有更新,...
  • 用Rust创建一个简单的webserver fn handle_client(stream: TcpStream) { let mut buffer = [0; 512]; stream.read(&mut buffer).unwrap(); let contents = fs::read_to_string("main.html").unwrap(); ...
  • 初试TinyWebServer所遇到的问题 按照作者所写的快速运行的步骤,安装了MYSQL,向新database添加了自己的用户名和密码,然后兴高采烈的对项目进行make后,程序却立马结束,然后打印出一条日志信息: 2021-12-15 18:50...
  • webserver

    2021-08-02 16:49:40
    一些webserver搭建过程中的操作总结 服务器http://weilab.sducat.top/PepBCL/,用的是别人域名的二级域名,采用的是nigix的端口映射 注意tomcat必须是8080端口才可以打开,但是服务器要80端口,所以nigix映射 首先是...
  • VS code内置浏览器需要自己下载安装,下面我们就来看看下载安装使用VS code内置浏览器...3、点击顶部搜索栏输入“Preview on Web Server” 4、 搜索结果第一个就是 点击install下载 5、回到代码部分 ,鼠标右键单击,
  • TinyWebServerLinux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的服务器.使用 线程池 + 非阻塞socket + epoll(ET和LT均实现) + 事件处理(Reactor和模拟Proactor均实现) 的并发模型使用状态机...
  • 手动写一个非常简易的web容器,即服务器端程序。
  • HttpServer是一个轻量级Web服务器,用于嵌入式设备以及客户端环境中提供简单Web服务。HttpServer支持路由映射到匿名委托、WebApi接口、静态文件,以及具有多个接口的...
  • 注意 html 页面里面,有没有写<body></body>标签 只写一个<body>也行,重新ctrl + shift +v
  • 什么是 gzip? GZIP 最早由 Jean-loup Gailly 和 Mark Adle r创建,用于 UNIX 系统的文件压缩,它是网站压缩加速的一种技术,对于开启后可以加快我们网站的打开...压缩需要消耗服务器 cpu,可能会多服务器 web server
  • 1、Apache WebServer 配置为了保证电脑的安全,必须设置用户密码。2、配置服务器:配置服务器的工作: Finder 中创建一个 "Sites" 的文件夹,直接创建 /Users/Kenvin(当前用户名)目录下。修改配置文件中的 "两个...
  • 我们Mac使用XAMPP时遇到无法启动Apache服务 可以通过使用终端运行XAMPP,查看具体的错误...XAMPP: Another web server is already running. 解决办法: sudo apachectl stop // This command killed Apache ...
  • Web Server(网页服务器)这个例子中,你将用Ethernet Shield和Arduino或genuino开发板来创建一个简单的Web服务器。采用以太网库,您的设备能够通过 Ethernet shield回答一个HTTP请求。在打开浏览器并导航到您的...
  • 1 Tinywebserver介绍 Linux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的服务器. 使用 线程池 + 非阻塞socket + epoll(ET和LT均实现) + 事件处理(Reactor和Proactor均实现) 的并发模型 使用...
  • TinyWebServer代码详细讲解(threadpool模块)threadpool.h设计思路代码详解preactor与reactor总结 这里的参照的代码是https://github.com/qinguoyi/TinyWebServer 对于原代码的不足之处,我会之后的文章中给出...
  • WebServer:Apacher 2.4 后端:PHP 7.3.4 OS:Windows 10 创建项目并修改配置文件 1、创建项目 利用cmd访问到D:\Projects\目录下,执行 vue init webpack test 可以此创建名为test的vue项目。 安装axios,用于访问...
  • 语法server.uri()参数无返回值客户端请求行中的请求资源路径信息。(类型:字符串)示例程序关于本函数的具体使用方法,请参考以下代码,尤其是代码中高亮的语句部分。当您将WiFi连接信息修改后并且将本示例程序上传...
  • Web server failed to start. Port 8887 was already in use. 二、解决方式 1、首先打开cmd运行界面,输入netstat -ano,查看端口进程ID。 2、如下图所示,8887端口的进程ID(PID)为16080 3、然后将该ID的进程...
  • 位置导航: ESP8266库 / 本页ESP8266WebServer库用于HTTP协议通讯。...假如您需要了解如何使用ESP8266开发板拉通过ESP8266WebServer库建立网络服务器以实现物联网应用,欢迎您收看太极创客团队制作的...
  • Windows下的WEB服务器,即IIS服务,对于使用Windows系统服务的企业...下面介绍如何Windows Server 2012中搭建WEB服务器。Windows Server 2012中开启服务器管理器,仪表板的界面当中选择点击“添加角色和功能...
  • 文章转载至:http://blog.csdn.net/hongchangfirst/article/details/7722703web项目没有run on server1、首先确保正确安装Tomcat和JDK。(eclipse要确保eclipse是javaee版本的,或者已经安装看wtp插件)Tomcat安装参考...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 533,946
精华内容 213,578
关键字:

webserver在哪里打开