最新资讯

  • 【仿Mudou库one thread per loop式并发服务器实现】HTTP协议模块实现

【仿Mudou库one thread per loop式并发服务器实现】HTTP协议模块实现

2026-01-28 16:52:58 栏目:最新资讯 4 阅读

HTTP协议模块实现

  • 1. Util模块
  • 2. HttpRequest模块
  • 3. HttpResponse模块
  • 4. HttpContext模块
  • 5. HttpServer模块

1. Util模块

这个模块是一个工具模块,主要提供HTTP协议模块所用到的一些工具函数,比如url编解码,文件读写…等。

#include "server.hpp"
#include 
#include 
#include 

static std::unordered_map<int,std::string> _status_msg = {
    {100,  "Continue"},
    {101,  "Switching Protocol"},
    {102,  "Processing"},
    {103,  "Early Hints"},
    {200,  "OK"},
    {201,  "Created"},
    {202,  "Accepted"},
    {203,  "Non-Authoritative Information"},
    {204,  "No Content"},
    {205,  "Reset Content"},
    {206,  "Partial Content"},
    {207,  "Multi-Status"},
    {208,  "Already Reported"},
    {226,  "IM Used"},
    {300,  "Multiple Choice"},
    {301,  "Moved Permanently"},
    {302,  "Found"},
    {303,  "See Other"},
    {304,  "Not Modified"},
    {305,  "Use Proxy"},
    {306,  "unused"},
    {307,  "Temporary Redirect"},
    {308,  "Permanent Redirect"},
    {400,  "Bad Request"},
    {401,  "Unauthorized"},
    {402,  "Payment Required"},
    {403,  "Forbidden"},
    {404,  "Not Found"},
    {405,  "Method Not Allowed"},
    {406,  "Not Acceptable"},
    {407,  "Proxy Authentication Required"},
    {408,  "Request Timeout"},
    {409,  "Conflict"},
    {410,  "Gone"},
    {411,  "Length Required"},
    {412,  "Precondition Failed"},
    {413,  "Payload Too Large"},
    {414,  "URI Too Long"},
    {415,  "Unsupported Media Type"},
    {416,  "Range Not Satisfiable"},
    {417,  "Expectation Failed"},
    {418,  "I'm a teapot"},
    {421,  "Misdirected Request"},
    {422,  "Unprocessable Entity"},
    {423,  "Locked"},
    {424,  "Failed Dependency"},
    {425,  "Too Early"},
    {426,  "Upgrade Required"},
    {428,  "Precondition Required"},
    {429,  "Too Many Requests"},
    {431,  "Request Header Fields Too Large"},
    {451,  "Unavailable For Legal Reasons"},
    {501,  "Not Implemented"},
    {502,  "Bad Gateway"},
    {503,  "Service Unavailable"},
    {504,  "Gateway Timeout"},
    {505,  "HTTP Version Not Supported"},
    {506,  "Variant Also Negotiates"},
    {507,  "Insufficient Storage"},
    {508,  "Loop Detected"},
    {510,  "Not Extended"},
    {511,  "Network Authentication Required"}
};


static std::unordered_map<std::string,std::string> _mine_msg = {
    {".aac",        "audio/aac"},
    {".abw",        "application/x-abiword"},
    {".arc",        "application/x-freearc"},
    {".avi",        "video/x-msvideo"},
    {".azw",        "application/vnd.amazon.ebook"},
    {".bin",        "application/octet-stream"},
    {".bmp",        "image/bmp"},
    {".bz",         "application/x-bzip"},
    {".bz2",        "application/x-bzip2"},
    {".csh",        "application/x-csh"},
    {".css",        "text/css"},
    {".csv",        "text/csv"},
    {".doc",        "application/msword"},
    {".docx",       "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
    {".eot",        "application/vnd.ms-fontobject"},
    {".epub",       "application/epub+zip"},
    {".gif",        "image/gif"},
    {".htm",        "text/html"},
    {".html",       "text/html"},
    {".ico",        "image/vnd.microsoft.icon"},
    {".ics",        "text/calendar"},
    {".jar",        "application/java-archive"},
    {".jpeg",       "image/jpeg"},
    {".jpg",        "image/jpeg"},
    {".js",         "text/javascript"},
    {".json",       "application/json"},
    {".jsonld",     "application/ld+json"},
    {".mid",        "audio/midi"},
    {".midi",       "audio/x-midi"},
    {".mjs",        "text/javascript"},
    {".mp3",        "audio/mpeg"},
    {".mpeg",       "video/mpeg"},
    {".mpkg",       "application/vnd.apple.installer+xml"},
    {".odp",        "application/vnd.oasis.opendocument.presentation"},
    {".ods",        "application/vnd.oasis.opendocument.spreadsheet"},
    {".odt",        "application/vnd.oasis.opendocument.text"},
    {".oga",        "audio/ogg"},
    {".ogv",        "video/ogg"},
    {".ogx",        "application/ogg"},
    {".otf",        "font/otf"},
    {".png",        "image/png"},
    {".pdf",        "application/pdf"},
    {".ppt",        "application/vnd.ms-powerpoint"},
    {".pptx",       "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
    {".rar",        "application/x-rar-compressed"},
    {".rtf",        "application/rtf"},
    {".sh",         "application/x-sh"},
    {".svg",        "image/svg+xml"},
    {".swf",        "application/x-shockwave-flash"},
    {".tar",        "application/x-tar"},
    {".tif",        "image/tiff"},
    {".tiff",       "image/tiff"},
    {".ttf",        "font/ttf"},
    {".txt",        "text/plain"},
    {".vsd",        "application/vnd.visio"},
    {".wav",        "audio/wav"},
    {".weba",       "audio/webm"},
    {".webm",       "video/webm"},
    {".webp",       "image/webp"},
    {".woff",       "font/woff"},
    {".woff2",      "font/woff2"},
    {".xhtml",      "application/xhtml+xml"},
    {".xls",        "application/vnd.ms-excel"},
    {".xlsx",       "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
    {".xml",        "application/xml"},
    {".xul",        "application/vnd.mozilla.xul+xml"},
    {".zip",        "application/zip"},
    {".3gp",        "video/3gpp"},
    {".3g2",        "video/3gpp2"},
    {".7z",         "application/x-7z-compressed"},
};


class Until
{
    public:
        //字符串分割函数,将src字符串按照sep字符进⾏分割,得到的各个字串放到arry中,最终返回字串的数量
        static int Split(const std::string& src,const std::string& sep,std::vector<std::string>* array)
        {
            size_t offset = 0;
            // 有10个字符,offset是查找的起始位置,范围应该是0~9,offset==10就代表已经越界了
            while(offset < src.size())
            {
                size_t pos = src.find(sep,offset);
                if(pos == std::string::npos)//没有找到特定的字符
                {
                    //将剩余的部分当作⼀个字串,放⼊arry中
                    array->push_back(src.substr(offset));
                    return array->size();
                }
                
                //abc....de
                if(pos == offset)
                {
                    offset = pos + sep.size();
                    continue;//当前字串是⼀个空的,没有内容
                }
                array->push_back(src.substr(offset,pos - offset));
                offset = pos + sep.size();
            }
            return array->size();
        }

        //读取⽂件的所有内容,将读取的内容放到⼀个Buffer中
        static bool ReadFile(const std::string& filename,std::string* buf)
        {
            std::ifstream ifs(filename,std::ios::binary);
            if(ifs.is_open() == false)
            {
                LOG_FATAL("OPEN %s FILE FAILED",filename.c_str());
                return false;
            }
            size_t fsize = 0;
            ifs.seekg(0,ifs.end);//跳转读写位置到末尾
            fsize = ifs.tellg();//获取当前读写位置相对于起始位置的偏移量,从末尾偏移刚好就是⽂件大小
            ifs.seekg(0,ifs.beg);//跳转到起始位置
            buf->resize(fsize);//开辟文件大小的空间
            ifs.read(&(*buf)[0],fsize);
            if(ifs.good() == false)
            {
                LOG_FATAL("READ %s FILE FAILED",filename.c_str());
                ifs.close();
                return false;
            }
            ifs.close();
            return true;
        }

        //向文件写入数据
        static bool WriteFile(const std::string filename,const std::string& in)
        {
            std::ofstream ofs(filename,std::ios::binary | std::ios::trunc);
            if(ofs.is_open() == false)
            {
                LOG_FATAL("OPEN %s FILE FAILED",filename.c_str());
                return false;
            }

            ofs.write(in.c_str(),in.size());
            if(ofs.good() == false)
            {
                LOG_FATAL("Write %s FILE FAILED",filename.c_str());
                ofs.close();
                return false;
            }
            ofs.close();
            return true;
        }

        //URL编码,避免URL中资源路径与查询字符串中的特殊字符与HTTP请求中特殊字符产⽣歧义
        //编码格式:将特殊字符的ascii值,转换为两个16进制字符,前缀%, C++ -> C%2B%2B
        //不编码的特殊字符: RFC3986⽂档规定 . - _ ~ 字⺟,数字属于绝对不编码字符
        //RFC3986⽂档规定,编码格式 %HH 
        //W3C标准中规定,查询字符串中的空格,需要编码为+, 解码则是+转空格
        static std::string UrlEncode(const std::string& url,bool convert_space_to_plus)
        {
            std::string res;
            for(auto& c : url)
            {
                if(c == '.' || c == '-' || c == '_' || c == '~' || isalnum(c))
                {
                    res += 'c';
                    continue;
                }

                if(c == ' ' && convert_space_to_plus)
                {
                    res += '+';
                    continue;
                }
                //剩下的字符都是需要编码成为 %HH 格式
                char tmp[4] = {0};
                //snprintf 与 printf⽐较类似,都是格式化字符串,只不过⼀个是打印,⼀个是放到⼀块空间中
                snprintf(tmp,4,"%%%02X",c);
                res += tmp;
            }
            return res;
        }

        static char HEXTOI(char c)
        {
            if(c > '0' && c < '9')
                return c - '0';
            if(c > 'A' && c < 'Z')
                return c - 'A' + 10;
            if(c > 'a' && c < 'z')
                return c - 'a' + 10;
            return -1;
        }


        //URL解码
        static std::string UrlDecode(const std::string& url,bool convert_space_to_plus)
        {
            //遇到了%,则将紧随其后的2个字符,转换为数字,第⼀个数字左移4位,然后加上第二个数字 + -> 2b %2b->2 << 4 + 11
            std::string res;
            for(int i = 0; i < url.size(); ++i)
            {
                if(url[i] == '+' && convert_space_to_plus)
                {
                    res += ' ';
                    continue;
                }

                if(url[i] == '%' && i + 2 < url.size())
                {
                    //字符是以整数形成存储的
                    char v1 = HEXTOI(url[i + 1]) << 4;
                    char v2 = HEXTOI(url[i + 2]);
                    char c = v1 + v2;
                    res += c;
                    i += 2;
                    continue;
                }
                res += url[i];
            }
            return res;
        }

        //获取响应状态码的描述信息
        static std::string StatusDesc(int statu)
        {
            auto it = _status_msg.find(statu);
            if(it != _status_msg.end())
            {
                return it->second;
            }
            return "Unknow";
        }

        //根据文件后缀名获取文件mime
        static std::string ExMime(const std::string& filename)
        {   
            // a.b.txt 先获取⽂件扩展名
            size_t pos = filename.rfind('.');
            if(pos == std::string::npos)
            {
                return "application/octet-stream";
            }
            //根据扩展名,获取mim
            std::string ext = filename.substr(pos);
            auto it = _mine_msg.find(ext);
            if(it != _mine_msg.end())
            {
                return it->second;
            }
            return "application/octet-stream";
        }

        //判断一个文件是否是一个目录
        static bool IsDirectory(const std::string& filename)
        {
            struct stat st;
            int ret = stat(filename.c_str(),&st);
            if(ret < 0)
            {
                return false;
            }
            return S_ISDIR(st.st_mode);
        }

        //判断一个文件是否是一个普通文件
        static bool IsRegular(const std::string& filename)
        {
            struct stat st;
            int ret = stat(filename.c_str(),&st);
            if(ret < 0)
            {
                return false;
            }
            return S_ISREG(st.st_mode);
        }

        //http请求的资源路径有效性判断
        // /index.html --- 前边的/叫做相对根目录 映射的是某个服务器上的⼦目录
        // 想表达的意思就是,客⼾端只能请求相对根⽬录中的资源,其他地⽅的资源都不予理会
        // /../login, 这个路径中的..会让路径的查找跑到相对根⽬录之外,这是不合理的,不安全的
        static bool ValidPath(const std::string& path)
        {
            //思想:按照/进⾏路径分割,根据有多少⼦目录,计算目录深度,有多少层,深度不能⼩于0
            std::vector<std::string> res;
            Split(path,"/",&res);
            int level = 0;
            for(auto& s : res)
            {
                if(s == "..")
                {
                    --level;
                    //任意⼀层⾛出相对根目录,就认为有问题
                    if(level < 0)
                    {
                        return false;
                    }
                    continue;
                }
                ++level;
            }
            return true;
        }
};

2. HttpRequest模块

这个模块是HTTP请求数据模块,用于保存HTTP请求数据被解析后的各项请求元素信息。

HttpRequest模块:

http请求信息模块:存储HTTP请求信息要素,提供简单的功能性接口

请求信息要素:
请求行:请求方法,URL,协议版本
URL:资源路径,查询字符串
GET /search/1234?word=C++&en=utf8 HTTP/1.1
请求头部:key: value key: value …
Content-Length: 0
正文

要素:请求方法,资源路径,查询字符串,头部字段,正文,协议版本
std:smatch保存首行使用regex正则进行解析后,所提取的数据,比如提取资源路径中的数字…

功能性接口:

  1. 将成员变量设置为公有成员,便于直接访问
  2. 提供查询字符串,以及头部字段的单个查询和获取,插入功能
  3. 获取正文长度
  4. 判断长连接&短链接Connection:close/keep-alive
//HttpRequest模块,存储Http请求信息要素,提供简单的功能性接口
class HttpRequest
{
    public:
        std::string _method;//请求方法
        std::string _path;//资源路径
        std::string _version;//协议版本
        std::string _body;//请求正文
        std::smatch _matches;//资源路径的正则提取数据
        std::unordered_map<std::string,std::string> _headers;//头部字段
        std::unordered_map<std::string,std::string> _params;//查询字符串

    public:
        HttpRequest():_version("Http/1.1"){}
        void ReSet()
        {
            _method.clear();
            _path.clear();
            _version = "Http/1.1";
            _body.clear();
            std::smatch newmatches;
            _matches.swap(newmatches);
            _headers.clear();
            _params.clear();
        }
        //插入头部字段
        void SetHeader(const std::string& key,const std::string& val)
        {
            _headers.insert({key,val});
        }
        //判断是否存在指定头部字段
        bool HasHeader(const std::string& key) const
        {
            auto it = _headers.find(key);
            if(it == _headers.end())
            {
                return false;
            }
            return true;
        }
        //获取指定头部字段的值
        std::string GetHeader(const std::string& key) const
        {
            auto it = _headers.find(key);
            if(it == _headers.end())
            {
                return "";
            }
            return it->second;
        }
        //插入查询字符串
        void SetParam(const std::string& key,const std::string& val)
        {
            _params.insert({key,val});
        }
        //判断是否有某个指定的查询字符串
        bool HasParam(const std::string& key)
        {
            auto it = _params.find(key);
            if(it == _params.end())
            {
                return false;
            }
            return true;
        }
        //获取指定的查询字符串
        std::string GetParamr(const std::string& key)
        {
            auto it = _params.find(key);
            if(it == _params.end())
            {
                return "";
            }
            return it->second;
        }
        //获取正文长度
        size_t ContentLength()
        {
            // Content-Length: 1234

            bool ret = HasHeader("Content-Length");
            if(ret == false)
            {
                return 0;
            }
            return std::stol(GetHeader("Content-Length"));
        }
        //判断是否是短连接
        bool Close() const
        {
            // 没有Connection字段,或者有Connection但是值是close,则都是短链接,否则就是长连接
            if(HasHeader("Connection") == true && GetHeader("Connection") == "keep-alive")
            {
                return false;
            }
            return true;

        }
};

3. HttpResponse模块

这个模块是HTTP响应数据模块,用于业务处理后设置并保存HTTP响应数据的的各项元素信息,最终会被按照HTTP协议响应格式组织成为响应信息发送给客户端。

HttpResponse模块:

功能:存储HTTP响应信息要素,提供简单的功能性接口

响应信息要素:

  1. 响应状态码
  2. 头部字段
  3. 响应正文
  4. 重定向信息(是否进行了重定向的标志,重定向的路径)

功能性接口:

  1. 为了便于成员的访问,因此将成员设置为公有成员
  2. 头部字段的新增,查询,获取
  3. 正文的设置
  4. 重定向的设置
  5. 长短连接的判断
//HttpResponse模块,存储Http响应信息要素,提供简单的功能性接口
class HttpResponse
{
    public:
        int _status;//响应码
        bool _redirect_flag;//是否重定向
        std::string _body;//正文
        std::string _redirect_url;//重定向url
        std::unordered_map<std::string,std::string> _headers;//头部字段
    public:
        HttpResponse():_status(200),_redirect_flag(false){}
        HttpResponse(int status):_status(status),_redirect_flag(false){}
        void ReSet() 
        {
            _status = 200;
            _redirect_flag = false;
            _body.clear();
            _redirect_url.clear();
            _headers.clear();
        }
        //插入头部字段
        void SetHeader(const std::string& key,const std::string& val)
        {
            _headers.insert({key,val});
        }
        //判断是否存在指定头部字段
        bool HasHeader(const std::string& key)
        {
            auto it = _headers.find(key);
            if(it == _headers.end())
            {
                return false;
            }
            return true;
        }
        //获取指定头部字段的值
        std::string GetHeader(const std::string& key)
        {
            auto it = _headers.find(key);
            if(it == _headers.end())
            {
                return "";
            }
            return it->second;
        }
        //设置正文以及正文类型
        void SetContent(const std::string& body,const std::string& type = "text/html")
        {
            _body = body;
            SetHeader("Content-Type",type);
        }
        //设置重定向以及重定向状态码
        void SetRedirect(const std::string& url,int status = 302)
        {
            _redirect_flag = true;
            _redirect_url = url;
            _status = status;
        }
        //判断是否是短连接
        bool Close()
        {
            // 没有Connection字段,或者有Connection但是值是close,则都是短链接,否则就是长连接
            if(HasHeader("Connection") == true && GetHeader("Connection") == "keep-alive")
            {
                return false;
            }
            return true;
        }
};

4. HttpContext模块

这个模块是一个HTTP请求接收的上下文模块,主要是为了防支在一次接收的数据中,不是一个完整的HTTP请求,则解析过程并未完成,无法进行完整的请求处理,需要在下次接收到新数据后继续根据上下文进行解析,最终得到一个HttpRequest请求信息对象,因此在请求数据的接收以及解析部分需要一个上下文来进行控制接收和处理节奏。

typedef enum{
    RECV_HTTP_ERROR,
    RECV_HTTP_LINE,
    RECV_HTTP_HEAD,
    RECV_HTTP_BODY,
    RECV_HTTP_OVER
}HttpRecvStatus;

#define MAX_BYTE 8 * 1024

//HttpContext请求接收上下文模块,记录HTTP请求的接收以及处理进度
class HttpContext
{
    private:
        int _resp_status;//响应状态码,解析请求出错时设置
        HttpRecvStatus _recv_status;//当前接收及解析的阶段状态
        HttpRequest _request;//已经解析得到的请求信息
    private:
        //接收请求行
        bool RecvHttpLine(Buffer* buf)
        {
            if (_recv_status != RECV_HTTP_LINE) return false;
            //1. 获取一行数据,带有末尾的换行 
 

            std::string line = buf->GetLineAndPop();
            //2. 需要考虑的⼀些要素:缓冲区中的数据不足一行, 获取的一行数据超大
            if(line.size() == 0)
            {
                //缓冲区中的数据不足一行,则需要判断缓冲区的可读数据⻓度,如果很长了都不足一行,这是有问题的
                if(buf->ReadAbleSize() > MAX_BYTE)
                {
                    _recv_status = RECV_HTTP_ERROR;
                    _resp_status = 414 ;//"URI Too Long"
                    return false;
                }
                //缓冲区中数据不足一行,但是也不多,就等等新数据的到来
                return true;
            }
            if(line.size() > MAX_BYTE)
            {
                if(buf->ReadAbleSize() > MAX_BYTE)
                {
                    _recv_status = RECV_HTTP_ERROR;
                    _resp_status = 414 ;//"URI Too Long"
                    return false;
                }
            }
            bool ret = ParseHttpLine(line);
            if(ret == false)
            {
                return false;
            }
            //首行处理完毕,进⼊头部获取阶段
            _recv_status = RECV_HTTP_HEAD;
            return true;
        }

        //解析请求行
        bool ParseHttpLine(const std::string& line)
        {
            std::smatch matches;
            //std::regex::icase忽略大小写
            std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:?(.*))? (HTTP/1.[01])(?:
|
)?", std::regex::icase);
            bool ret = std::regex_match(line, matches, e);
            if (ret == false) {
                _recv_status = RECV_HTTP_ERROR;
                _resp_status = 400 ;//"Bad Request"
                return false;
            }
            //0 : GET /bitejiuyeke/login?user=xiaoming&pass=123123 HTTP/1.1
            //1 : GET
            //2 : /bitejiuyeke/login
            //3 : user=xiaoming&pass=123123
            //4 : HTTP/1.1

            //请求⽅法的获取
            _request._method = matches[1];
            //小写转成大写,为了下面HttpServer模块中用大写的请求方法进行判断时不会因为大小写出现不匹配的情况
            std::transform(_request._method.begin(), _request._method.end(), _request._method.begin(), ::toupper);
            //资源路径的获取,需要进⾏URL解码操作,但是不需要+转空格
            _request._path = Until::UrlDecode(matches[2],false);
            //协议版本的获取
            _request._version = matches[4];
            //查询字符串的获取与处理
            std::vector<std::string> query_string_arry;
            std::string query_string = matches[3];
            //查询字符串的格式 key=val&key=val....., 先以 & 符号进⾏分割,得到各个字串
            Until::Split(query_string,"&",&query_string_arry);
            //针对各个字串,以 = 符号进⾏分割,得到key 和val, 得到之后也需要进⾏URL解码
            for(auto& str : query_string_arry)
            {
                size_t pos = str.find("=");
                if(pos == std::string::npos)
                {
                    _recv_status = RECV_HTTP_ERROR;
                    _resp_status = 400 ;//"Bad Request"
                    return false;
                }
                std::string key = Until::UrlDecode(str.substr(0,pos),true);
                std::string val = Until::UrlDecode(str.substr(pos + 1),true);
                _request._params.insert({key,val});
            }
            return true;
        }

        //接收请求头部
        bool RecvHttpHead(Buffer* buf)
        {
            if(_recv_status != RECV_HTTP_HEAD) return false;
            //1. 一⾏一行取出数据,直到遇到空行为⽌, 头部的格式 key: val
key: val


            while(1)
            {
                std::string line = buf->GetLineAndPop();
                //2. 需要考虑的⼀些要素:缓冲区中的数据不足一行, 获取的一行数据超大
                if(line.size() == 0)
                {
                    //缓冲区中的数据不足一行,则需要判断缓冲区的可读数据⻓度,如果很长了都不足一行,这是有问题的
                    if(buf->ReadAbleSize() > MAX_BYTE)
                    {
                        _recv_status = RECV_HTTP_ERROR;
                        _resp_status = 414;//"URI Too Long"
                        return false;
                    }
                    //缓冲区中数据不足一行,但是也不多,就等等新数据的到来
                    return true;
                }
                if(line.size() > MAX_BYTE)
                {
                    _recv_status = RECV_HTTP_ERROR;
                    _resp_status = 414;
                    return false;
                }
                //遇到空行头部提取结束
                if(line == "
" || line == "
")
                {
                    break;
                }
                bool ret = ParseHttpHead(line);
                if(ret == false)
                {
                    return false;
                }
            }
            //头部处理完毕,进入正文获取阶段
            _recv_status = RECV_HTTP_BODY;
            return true;
        }

        //解析请求头部
        bool ParseHttpHead(std::string& line)
        {
            //key: val

            //末尾是换行则去掉换行字符
            if(line.back() == '
') line.pop_back();
            //末尾是回⻋则去掉回⻋字符
            if(line.back() == '
') line.pop_back();
            size_t pos = line.find(": ");
            if(pos == std::string::npos)
            {
                _recv_status = RECV_HTTP_ERROR;
                _resp_status = 400;
                return false;
            }
            std::string key = line.substr(0,pos);
            std::string val = line.substr(pos + 2);
            _request.SetHeader(key,val);
            return true;
        }
        //接收请求正文
        bool RecvHttpBody(Buffer* buf)
        {
            if(_recv_status != RECV_HTTP_BODY) return false;
            //1. 获取正文长度
            size_t content_length = _request.ContentLength();
            if(content_length == 0)
            {
                //没有正⽂,则请求接收解析完毕
                _recv_status = RECV_HTTP_OVER;
                return true;
            }
            //2. 当前已经接收了多少正文,其实就是往 _request._body 中放了多少数据了
            size_t real_len = content_length - _request._body.size();//实际还需要接收的正⽂长度
            //3. 接收正文放到body中,但是也要考虑当前缓冲区中的数据,是否是全部的正⽂
            // 3.1 缓冲区中数据,包含了当前请求的所有正文,则取出所需的数据
            if(buf->ReadAbleSize() >= real_len)
            {
                _request._body.append(buf->ReadPosition(),real_len);
                buf->MoveReadOffset(real_len);
                _recv_status = RECV_HTTP_OVER;
                return true;
            }
            // 3.2 缓冲区中数据,⽆法满⾜当前正文的需要,数据不足,取出数据,后续等待新数据到来
            _request._body.append(buf->ReadPosition(),buf->ReadAbleSize());
            buf->MoveReadOffset(buf->ReadAbleSize());
            return true;
        }
    public:
        HttpContext():_recv_status(RECV_HTTP_LINE),_resp_status(200){}
        void ReSet()
        {
            _recv_status = RECV_HTTP_LINE;
            _resp_status = 200;
            _request.ReSet();
        }
        //接收解析遇到错误时返回对应的错误码
        int RespStatus()
        {
            return _resp_status;
        }
        //当前请求接收解析到那个阶段
        HttpRecvStatus RecvStatus()
        {
            return _recv_status;
        }
        //返回请求解析后的HttpRequest对象
        HttpRequest &Request() 
        { 
            return _request; 
        }
        //接收并解析HTTP请求
        void RecvHttpRequest(Buffer* buf)
        {
            //不同的状态,做不同的事情,但是这⾥不要break, 因为处理完请求⾏后,应该⽴即处理头部,⽽不是退出等新数据
            switch(_recv_status)
            {
                case RECV_HTTP_LINE: RecvHttpLine(buf);
                case RECV_HTTP_HEAD: RecvHttpHead(buf);
                case RECV_HTTP_BODY: RecvHttpBody(buf);
            }
            return;
        }
    
};

5. HttpServer模块

这个模块是最终给组件使用者提供的HTTP服务器模块了,用于以简单的接口实现HTTP服务器的搭建。

HttpServer模块内部包含有一个TcpServer对象:TcpServer对象实现服务器的搭建
HttpServer模块内部包含有两个提供给TcpServer对象的接口:连接建立成功设置上下文接口,数据处理接口。

HttpServer模块内部包含有一个hash-map表存储请求与处理函数的映射表:组件使用者向HttpServer设置哪些请求应该使用哪些函数进行处理,等TcpServer收到对应的请求就会使用对应的函数进行处理。

HttpServer模块:用于实现Http服务器的搭建

首先要给不同的请求方法的请求路径设置对应的回调函数。也就是设计一张请求路由表。


设计一张请求路由表:

表中记录了针对哪个请求,应该使用哪个函数来进行业务处理的映射关系。
当服务器收到了一个请求,就在请求路由表中,查找有没有对应请求的处理函数,如果有,则执行对应的处理函数即可。说白了,什么请求,怎么处理,由用户来设定,服务器收到了请求只需要执行函数即可。

这样做的好处:用户只需要实现业务处理函数,然后将请求与处理函数的映射关系,添加到服务器中。

而服务器只需要接收数据,解析数据,查找路由表映射关系,执行业务处理函数。
要实现简便的搭建HTTP服务器,所需要的要素和提供的功能。

要素:

  1. GET请求的路由映射表
  2. POST请求的路由映射表
  3. PUT请求的路由映射表
  4. DELETE请求的路由映射表—路由映射表记录对应请求方法的请求资源路径与对应业务处理函数映射关系–更多是功能性请求的处理
  5. 静态资源相对根目录-实现静态资源请求的处理
  6. 高性能TCP服务器—进行连接的IO操作

公有接口:

  1. 添加请求—处理函数映射信息(GET/POST/PUT/DELETE)
  2. 设置静态资源根目录
  3. 设置是否启动超时连接关闭
  4. 设置线程池中线程数量
  5. 启动服务器

私有接口:

  1. OnConnected—用于给TcpServer设置协议上下文
  2. OnMessage----用于进行缓冲区数据解析处理
  3. 请求的路由查找
  4. 静态资源请求查找和处理
  5. 功能性请求的查找和处理
  6. 组织响应进行回复

服务器处理流程:

  1. 从socket接收数据,放到接收缓冲区
  2. 调用OnMessage回调函数进行业务处理
  3. 对请求进行解析,得到了一个HttpRequest结构,包含了所有的请求要素
  4. 进行请求的路由查找-找到对应请求的处理方法
    a. 静态资源请求–一些实体文件资源的请求,html,image.…
    将静态资源文件的数据读取出来,填充到HttpResponse结构中
    b. 功能性请求—在请求路由映射表中查找处理函数,找到了则执行函数
    具体的业务处理,并进行HttpResponse结构的数据填充
  5. 对静态资源请求/功能性请求进行处理完毕后,得到了一个填充了响应信息的HttpResponse对象,组织http格式响应,进行发送。
//HttpServer模块,用于实现HttpServer服务器的搭建
const std::string html_404 = "/404.html";
const std::string home_page = "index.html";
#define DEFALT_TIMEOUT 10
class HttpServer
{
    private:
        //请求路由表
        using Handler = std::function<void(const HttpRequest&,HttpResponse*)>;
        //请求资源路径我们用正则表达式
        //比如 /number/1、/number/2、/number/3 这样的资源路径
        //对应的业务处理函数都是一样的。如果一个路径给没有必要。
        //因此请求路径用正则表达式,比如number/d+  这个匹配上面三个
        //正则表达式可以用来判断字符串中有没有某个子串
        using Handlers = std::vector<std::pair<std::regex,Handler>>;
        Handlers _get_route;
        Handlers _post_route;
        Handlers _put_route;
        Handlers _delete_route;
        std::string _basedir;//静态资源根⽬录
        TcpServer _server;
    private:
        void ErrHandler(const HttpRequest& req,HttpResponse* rsp)
        {
            //1. 组织⼀个错误展示页⾯
            std::string body;
            body += "";
            body += "";
            body += "";
            body += "";
            body += "";
            body += "

"; body += std::to_string(rsp->_status); body += " "; body += Until::StatusDesc(rsp->_status); body += "

"
; body += ""; body += ""; //2. 将页⾯数据,当作响应正⽂,放⼊rsp中 rsp->SetContent(body, "text/html"); } //将HttpResponse中的要素按照http协议格式进行组织,发送 void WriteResponse(const PtrConnection& conn,const HttpRequest& req,HttpResponse* rsp) { //1. 先完善头部字段 if(req.Close() == true) { rsp->SetHeader("Connection","close"); } else { rsp->SetHeader("Connection","keep-alive"); } if(!rsp->_body.empty() && rsp->HasHeader("Content-Length") == false) { rsp->SetHeader("Content-Length",std::to_string(rsp->_body.size())); } if(!rsp->_body.empty() && rsp->HasHeader("Content-Typy") == false) { rsp->SetHeader("Content-Type","application/octet-stream"); } if(rsp->_redirect_flag == true) { rsp->SetHeader("Location",rsp->_redirect_url); } //2. 将rsp中的要素,按照http协议格式进⾏组织 std::stringstream rsp_string; rsp_string << req._version << " " << std::to_string(rsp->_status) << " " << Until::StatusDesc(rsp->_status) << " "; for(auto& header : rsp->_headers) { rsp_string << header.first << ": " << header.second << " "; } rsp_string << " "; rsp_string << rsp->_body; //3. 发送数据 conn->Send(rsp_string.str().c_str(),rsp_string.str().size()); } bool IsFileHandler(const HttpRequest& req) { // 1. 必须设置了静态资源根目录 if(_basedir.empty()) { return false; } // 2. 请求⽅法,必须是GET / HEAD请求⽅法 if(req._method != "GET" && req._method != "HEAD") { return false; } // 3. 请求的资源路径必须是一个合法路径 if(Until::ValidPath(req._path) == false) { return false; } // 4. 请求的资源必须存在,且是⼀个普通⽂件 // 有⼀种请求⽐较特殊 -- ⽬录:/, 这种情况给后边默认追加⼀个index.html // /index.html /image/a.png // 不要忘了前缀的相对根⽬录,也就是将请求路径转换为实际存在的路径 /image/a.png -> ./wwwroot/image/a.png std::string req_path = _basedir + req._path;//为了避免直接修改请求的资源路径,因此定义⼀个临时对象 if(req._path.back() == '/') { //默认首页 req_path += home_page; } if (Until::IsRegular(req_path) == false) { return false; } return true; } //静态资源的请求处理 --- 将静态资源⽂件的数据读取出来,放到rsp的_body中, 并设置mime void FileHandler(const HttpRequest& req,HttpResponse* rsp) { std::string req_path = _basedir + req._path; if(req._path.back() == '/') { //默认首页 req_path += home_page; } bool ret = Until::ReadFile(req_path,&rsp->_body); if(ret == false)//进来这里已经判断过资源肯定是存在的,否则就不会进入静态资源的处理 { return; } std::string mime = Until::ExMime(req_path); rsp->SetHeader("Content-Type",mime); return; } //功能性请求的的分类处理 void Dispatcher(HttpRequest& req,HttpResponse* rsp,const Handlers& handlers) { //在对应请求⽅法的路由表中,查找是否含有对应资源的对应请求的处理函数,有则调⽤,没有则返回404 //思想:路由表存储的时键值对 -- 正则表达式 & 处理函数 //使⽤正则表达式,对请求的资源路径进⾏正则匹配,匹配成功就使⽤对应函数进⾏处理 // /numbers/(d+) /numbers/12345 for(auto& handler : handlers) { const std::regex& re = handler.first; const Handler& functor = handler.second; bool ret = std::regex_match(req._path,req._matches,re); if(ret == false) { continue; } //传⼊请求信息,和空的rsp,执⾏处理函数 return functor(req,rsp); } //返回错误码404页面 rsp->_status = 404; // std::string path = _basedir + html_404; // bool ret = Until::ReadFile(path,&rsp->_body); // if(ret == false)//错误路径是我们自己设置肯定是存在的 // { // return; // } // return rsp->SetHeader("Content-Type","text/html"); } //路由选择 void Route(HttpRequest& req,HttpResponse* rsp) { //1. 对请求进⾏分辨,是⼀个静态资源请求,还是⼀个功能性请求 // 静态资源请求,则进⾏静态资源的处理 // 功能性请求,则需要通过⼏个请求路由表来确定是否有处理函数 // 既不是静态资源请求,也没有设置对应的功能性请求处理函数,就返回405 if(IsFileHandler(req)) { //是⼀个静态资源请求, 则进⾏静态资源请求的处理 return FileHandler(req,rsp); } if(req._method == "GET" || req._method == "HEAD") { return Dispatcher(req,rsp,_get_route); } else if(req._method == "POST") { return Dispatcher(req,rsp,_post_route); } else if(req._method == "PUT") { return Dispatcher(req,rsp,_put_route); } else if(req._method == "DELETE") { return Dispatcher(req,rsp,_delete_route); } rsp->_status = 405;// Method Not Allowed return; } //连接建立后给对应Connection对象设置一个协议上下文 void OnConnected(const PtrConnection& conn) { conn->SetContent(HttpContext()); } //缓存区数据解析+处理 void OnMessage(const PtrConnection& conn,Buffer* buf) { while(buf->ReadAbleSize() > 0) { //1. 获取上下⽂ //获取在连接建立好就给每个Connection设置HttpContext上下文 HttpContext* context = conn->GetContent()->Get<HttpContext>(); //2. 通过上下⽂对缓冲区数据进⾏解析,得到HttpRequest对象 // 2.1 如果缓冲区的数据解析出错,就直接回复出错响应 // 2.2 如果解析正常,且请求已经获取完毕,才开始去进⾏处理 context->RecvHttpRequest(buf); HttpRequest& rep = context->Request(); HttpResponse rsp(context->RespStatus()); //if(context->RecvStatus() == RECV_HTTP_ERROR) if (context->RespStatus() >= 400) { //进⾏错误响应,关闭连接 //填充⼀个错误显⽰页⾯数据到rsp中 ErrHandler(rep,&rsp); //组织响应发送给客户端 WriteResponse(conn,rep,&rsp); //一定要做下面两步,不然出错了,关闭连接时,接收缓存区还有数据关闭连接的时候先去先处理接收缓存区数据 //但是当前上下文状态一直是RECV_HTTP_ERROR,因此每次去接收缓存区根本拿不到数据,所有在这里死循环 //造成内存资源不足,服务器奔溃退出 //因此在这里把上下文状态重置RECV_HTTP_LINE可以每次都从接收缓存区拿到数据 //直到最后接收缓存区数据不足一行,从下面退出,然后真正的去关闭连接 context->ReSet(); //这里也可以,出错了就把接收缓冲区数据清空,也就不会在多次调用了 buf->MoveReadOffset(buf->ReadAbleSize()); //关闭连接 conn->Shutdown(); return; } //当前请求还没有接收完整,则退出,等新数据到来再重新继续处理 if(context->RecvStatus() != RECV_HTTP_OVER) { return; } //3. 请求路由 + 业务处理 Route(rep,&rsp); //4. 对HttpResponse进⾏组织发送 WriteResponse(conn,rep,&rsp); //5. 重置上下⽂,避免影响下次解析 context->ReSet(); //6. 根据⻓短连接判断是否关闭连接或者继续处理 if(rsp.Close() == true) { //短链接则直接关闭 return conn->Shutdown(); } } return; } public: HttpServer(uint16_t port,int timeout = DEFALT_TIMEOUT):_server(port) { _server.EnableInactiveRelease(timeout); _server.SetConnectedCallback(std::bind(&HttpServer::OnConnected,this,std::placeholders::_1)); _server.SetMessageCallback(std::bind(&HttpServer::OnMessage,this,std::placeholders::_1,std::placeholders::_2)); } void SetBaseDir(const std::string& path) { assert(Until::IsDirectory(path) == true); _basedir = path; } void Get(const std::string& parttern,const Handler& handler) { _get_route.push_back(std::make_pair(std::regex(parttern),handler)); } void Post(const std::string& parttern,const Handler& handler) { _post_route.push_back(std::make_pair(std::regex(parttern),handler)); } void Put(const std::string& parttern,const Handler& handler) { _put_route.push_back(std::make_pair(std::regex(parttern),handler)); } void Delete(const std::string& parttern,const Handler& handler) { _delete_route.push_back(std::make_pair(std::regex(parttern),handler)); } void SetThreadCount(int count) { _server.SetThreadCount(count); } void Start() { _server.Start(); } };

本文地址:https://www.yitenyun.com/482.html

搜索文章

Tags

#ios面试 #ios弱网 #断点续传 #ios开发 #objective-c #ios #ios缓存 #服务器 #python #pip #conda #远程工作 香港站群服务器 多IP服务器 香港站群 站群服务器 #kubernetes #笔记 #平面 #容器 #linux #学习方法 #运维 #进程控制 #开发语言 #云原生 #iventoy #VmWare #OpenEuler #fastapi #html #css #docker #后端 #数据库 #人工智能 #node.js #cpolar #MobaXterm #ubuntu #Conda # 私有索引 # 包管理 #Trae #IDE #AI 原生集成开发环境 #Trae AI #低代码 #爬虫 #音视频 #学习 #物联网 #websocket #内网穿透 #网络 #RTP over RTSP #RTP over TCP #RTSP服务器 #RTP #TCP发送RTP #算法 #大数据 #vscode #mobaxterm #深度学习 #计算机视觉 #开源 #android #腾讯云 #c# #云计算 #windows #web安全 #安全 #kylin #unity #游戏引擎 #数信院生信服务器 #Rstudio #生信入门 #生信云服务器 #nginx #tcp/ip #gemini #gemini国内访问 #gemini api #gemini中转搭建 #Cloudflare #多个客户端访问 #IO多路复用 #回显服务器 #TCP相关API #我的世界 #todesk #Dell #PowerEdge620 #内存 #硬盘 #RAID5 #架构 #面试 #华为 #ModelEngine #mvp #个人开发 #设计模式 #java #1024程序员节 #金融 #大模型 #mcp #金融投资Agent #Agent #github #git #DisM++ # GLM-4.6V # 系统维护 #n8n #本地部署 #vue.js #前端 #国产化 #hadoop #hbase #hive #zookeeper #spark #kafka #flink #ssh #http #c++ #性能优化 #qt #C++ #oracle #我的世界服务器搭建 #minecraft #jmeter #功能测试 #软件测试 #自动化测试 #职场和发展 #udp #c语言 #网络协议 #ide #jenkins #jar #claude #cpp #项目 #高并发 #搜索引擎 #debian #AI编程 #stm32 #macos #mysql #C2000 #TI #实时控制MCU #AI服务器电源 #vue #阿里云 #screen 命令 #JumpServer #堡垒机 #振镜 #振镜焊接 #蓝湖 #Axure原型发布 #单元测试 #集成测试 #嵌入式硬件 #pycharm #京东云 #jvm #SAP #ebs #metaerp #oracle ebs #gpu算力 #编辑器 #SRS #流媒体 #直播 #DeepSeek #MCP #蓝耘智算 #AIGC #ida #Anaconda配置云虚拟环境 #守护进程 #复用 #screen #openHiTLS #TLCP #DTLCP #密码学 #商用密码算法 #unity3d #游戏 #服务器框架 #Fantasy #umeditor粘贴word #ueditor粘贴word #ueditor复制word #ueditor上传word图片 #YOLOFuse # Base64编码 # 多模态检测 #缓存 #ollama #ai #llm #麒麟OS #swagger #凤希AI伴侣 #mamba #Android #Bluedroid #PyTorch # Triton # 高并发部署 #智能手机 #epoll #压力测试 #科技 #自然语言处理 #神经网络 #libosinfo #openlayers #bmap #tile #server #自动化 #maven #gitlab #centos #chatgpt #codex #高级IO #select #计算机网络 #单片机 #需求分析 #scala #测试用例 #测试工具 #微信小程序 #小程序 #微信 #健身房预约系统 #健身房管理系统 #健身管理系统 #mcu #react.js #asp.net #sqlserver #MCP服务器 #apache #北京百思可瑞教育 #百思可瑞教育 #北京百思教育 #spring boot #部署 #企业开发 #ERP #项目实践 #.NET开发 #C#编程 #编程与数学 #NPU #CANN #vllm #Streamlit #Qwen #AI聊天机器人 #智能路由器 #ui #5G #vnstat #监控 #golang #redis #php #运维开发 #pytorch #攻防演练 #Java web #漏洞 #红队 #Llama-Factory # 树莓派 # ARM架构 #支付 #银河麒麟 #系统升级 #信创 #远程桌面 #远程控制 #东方仙盟 #银河麒麟操作系统 #openssh #华为交换机 #信创终端 #驱动开发 #处理器 #黑群晖 #虚拟机 #无U盘 #纯小白 #bash #mariadb #Gunicorn #WSGI #Flask #并发模型 #容器化 #Python #性能调优 #智能体来了 #智能体对传统行业冲击 #行业转型 #AI赋能 #管道Pipe #system V #RAG #AI #LLM #chat #muduo #TcpServer #accept #高并发服务器 #Miniconda #SSH #远程开发 #muduo库 #uv #uvx #uv pip #npx #Ruff #pytest #web server #请求处理流程 #数据结构 #milvus #课程设计 #毕业设计 #springboot #知识库 #语音识别 #910B #昇腾 #个人博客 #vivado license #分布式 #tomcat #uni-app #H5 #手机h5网页浏览器 #安卓app #苹果ios APP #手机电脑开启摄像头并排查 #rocketmq #selenium #svn #可信计算技术 #网络安全 #系统架构 #aws #华为云 #测评 #CCE #Dify-LLM #Flexus #Nacos #web #微服务 #chrome #毕设 #RAID #RAID技术 #磁盘 #存储 #嵌入式编译 #ccache #distcc #cursor #adb #rustdesk #p2p #elasticsearch #源码 #闲置物品交易系统 #仙盟创梦IDE #POC #问答 #交付 #进程 #操作系统 #进程创建与终止 #shell #javascript #SPA #单页应用 #django #flask #web3.py #spring cloud #spring #YOLOv8 # 目标检测 # Docker镜像 #文件管理 #NAS #文件服务器 #jetty #信息与通信 #信号处理 #tcpdump #RustDesk #IndexTTS 2.0 #本地化部署 #车辆排放 #SA-PEKS # 关键词猜测攻击 # 盲签名 # 限速机制 #harmonyos #小艺 #鸿蒙 #搜索 #ms-swift # 大模型 # 模型训练 #Java #Spring AI #STDIO协议 #Streamable-HTTP #McpTool注解 #服务器能力 #wsl #时序数据库 #电脑 #transformer #银河麒麟高级服务器操作系统安装 #银河麒麟高级服务器V11配置 #设置基础软件仓库时出错 #银河麒高级服务器系统的实操教程 #生产级部署银河麒麟服务系统教程 #Linux系统的快速上手教程 #工具集 #sqlite #Ansible #Playbook #AI服务器 #大模型学习 #电气工程 #C# #PLC #https #wordpress #雨云 #java-ee #sql #intellij-idea #json #rdp #负载均衡 #大模型部署 #mindie #大模型推理 #Dify #ARM架构 #鲲鹏 #Tracker 服务器 #响应最快 #torrent 下载 #2026年 #Aria2 可用 #迅雷可用 #BT工具通用 #langchain #大模型开发 #程序员 #ssl #TCP #客户端 #嵌入式 #DIY机器人工房 #eBPF #Puppet # IndexTTS2 # TTS #SSH反向隧道 # Miniconda # Jupyter远程访问 #EMC存储 #存储维护 #NetApp存储 #说话人验证 #声纹识别 #CAM++ #C语言 #yum #windows11 #microsoft #系统修复 #信令服务器 #Janus #MediaSoup #PTP_1588 #gPTP #Termux #Samba #Linux #三维 #3D #三维重建 #Windows #fpga开发 #CVE-2025-61686 #路径遍历高危漏洞 #gitea #rust #ping通服务器 #读不了内网数据库 #bug菌问答团队 #GPU #AutoDL ##租显卡 #进程等待 #wait #waitpid #万悟 #联通元景 #智能体 #镜像 #大模型教程 #AI大模型 #结构体 #制造 #webrtc #idm #YOLO # GPU租赁 # 自建服务器 #web服务器 #CTF #deepseek #postgresql #SSE # AI翻译机 # 实时翻译 #VMware #VMWare Tool #聊天小程序 #鸭科夫 #逃离鸭科夫 #鸭科夫联机 #鸭科夫异地联机 #开服 #risc-v #交互 #SSH公钥认证 # PyTorch # 安全加固 #arm开发 #GPU服务器 #8U #硬件架构 #语言模型 #昇腾300I DUO #fiddler #idea #intellij idea #serverless #内存治理 #opencv #数据挖掘 #cosmic #googlecloud #Qwen3-14B # 大模型部署 # 私有化AI #数据分析 #VibeVoice # 高温监控 # 语音合成 #AI 推理 #NV #文心一言 #AI智能体 #vp9 #leetcode #HeyGem # WebUI # 网络延迟 #anaconda #虚拟环境 #ranger #MySQL8.0 #SSH跳板机 # Python3.11 #LVDS #高速ADC #DDR #API限流 # 频率限制 # 令牌桶算法 #UDP的API使用 #TTS私有化 # IndexTTS # 音色克隆 #ESP32 # OTA升级 # 黄山派 #iBMC #UltraISO #screen命令 #notepad++ #排序算法 #teamviewer #Emby #视频 #经验分享 #rsync # 数据同步 #lua #llama # CUDA #门禁 #梯控 #智能一卡通 #门禁一卡通 #消费一卡通 #智能梯控 #一卡通 #源代码管理 #Socket网络编程 #超时设置 #客户端/服务器 #网络编程 #挖矿 #Linux病毒 #sql注入 #ai编程 #机器人 #azure #YOLO26 #目标检测 #状态模式 #AI-native #dba #国产化OS #react native #机器学习 #工程设计 #预混 #扩散 #燃烧知识 #层流 #湍流 #html5 #weston #x11 #x11显示服务器 #WinSCP 下载安装教程 #SFTP #FTP工具 #服务器文件传输 #计算几何 #斜率 #方向归一化 #叉积 # CosyVoice3 # 批量部署 # 批量管理 #RSO #机器人操作系统 #ASR #SenseVoice #星图GPU #媒体 #中间件 #远程连接 #MQTT协议 #交通物流 #CVE-2025-68143 #CVE-2025-68144 #CVE-2025-68145 #laravel #node #政务 #prometheus #grafana #证书 #scrapy #fabric #winscp #AI部署 # ms-swift #ONLYOFFICE #MCP 服务器 #CPU # 数字人系统 # 远程部署 # 双因素认证 # TensorFlow #服务器繁忙 #蓝牙 #LE Audio #BAP #Clawdbot #个人助理 #数字员工 #Docker #链表 #Node.js # child_process #puppeteer #CUDA #KMS #slmgr #连接数据库报错 #安全威胁分析 #硬件工程 #智能家居 #动态规划 #pyqt #xlwings #Excel #DNS #Discord机器人 #云部署 #程序那些事 # 自动化运维 #dlms #dlms协议 #逻辑设备 #逻辑设置间权限 #mybatis #企业微信 #ipmitool #BMC #翻译 #C #STDIO传输 #SSE传输 #WebMVC #WebFlux #bootstrap #nfs #iscsi #okhttp #IndexTTS2 # 阿里云安骑士 # 木马查杀 #visual studio code #前端框架 #计算机外设 #prompt #树莓派4b安装系统 #scanf #printf #getchar #putchar #cin #cout #paddleocr #企业级存储 #网络设备 #word #pdf #pve #LangGraph #CLI #JavaScript #langgraph.json #ddos #zotero #WebDAV #同步失败 #代理模式 #Beidou #北斗 #SSR #大模型应用 #API调用 #PyInstaller打包运行 #服务端部署 #KMS激活 #大语言模型 #jdk #排序 #数据仓库 #openEuler #欧拉 #numpy #CSDN #aiohttp #asyncio #异步 #信息安全 #信息收集 #.netcore # IndexTTS 2.0 #pjsip #LoRA # lora-scripts # 模型微调 #openresty #LobeChat #vLLM #GPU加速 #开源工具 #ansible #SSH保活 #数字化转型 #实体经济 #商业模式 #软件开发 #数智红包 #商业变革 #创业干货 #能源 #人脸识别sdk #视频编解码 #人脸识别 #AI生成 # outputs目录 # 自动化 #海外服务器安装宝塔面板 #创业创新 #业界资讯 #Zabbix #CosyVoice3 #语音合成 #简单数论 #埃氏筛法 #内存接口 # 澜起科技 # 服务器主板 #模拟退火算法 #集成学习 #文件传输 #电脑文件传输 #电脑传输文件 #电脑怎么传输文件到另一台电脑 #电脑传输文件到另一台电脑 #eureka #mongodb #Harbor #x86_64 #数字人系统 #uvicorn #uvloop #asgi #event #大模型入门 #其他 #rtsp #转发 #TensorRT # 推理优化 #unix #SSH别名 #CS2 #debian13 #RXT4090显卡 #RTX4090 #深度学习服务器 #硬件选型 #视频去字幕 #SQL注入主机 #neo4j #NoSQL #SQL #Android16 #音频性能实战 #音频进阶 #LangFlow # 智能运维 # 性能瓶颈分析 #推荐算法 #devops #戴尔服务器 #戴尔730 #装系统 #汽车 #渗透测试 #黑客技术 #计算机 #文件上传漏洞 #flutter #数码相机 #ThingsBoard MCP #代理 # 服务器IP访问 # 端口映射 #A2A #GenAI #遛狗 #bug #算力一体机 #ai算力服务器 #FHSS #心理健康服务平台 #心理健康系统 #心理服务平台 #心理健康小程序 #arm64 #无人机 #Deepoc #具身模型 #开发板 #未来 #插件 #开源软件 #nodejs #NFC #智能公交 #服务器计费 #FP-增长 #数据安全 #注入漏洞 #tdengine #涛思数据 #串口服务器 #Modbus #MOXA #DAG # 一锤定音 # 大模型微调 #ffmpeg #Triton #SSH密钥 #练习 #基础练习 #数组 #循环 #九九乘法表 #计算机实现 #论文笔记 #dynadot #域名 #ETL管道 #向量存储 #数据预处理 #DocumentReader #esb接口 #走处理类报异常 #dify #网路编程 #百万并发 #PowerBI #企业 #银河麒麟部署 #银河麒麟部署文档 #银河麒麟linux #银河麒麟linux部署教程 #fs7TF # 远程访问 # 服务器IP #c++20 #Buck #NVIDIA #算力 #交错并联 #DGX #tensorflow #ROS # 局域网访问 # 批量处理 #IFix # 远程连接 #gerrit #opc ua #opc #npu #memcache # 环境迁移 #大剑师 #nodejs面试题 #ServBay #matplotlib #安全架构 #跨域 #发布上线后跨域报错 #请求接口跨域问题解决 #跨域请求代理配置 #request浏览器跨域 #指针 #GB28181 #SIP信令 #SpringBoot #视频监控 #远程软件 #WT-2026-0001 #QVD-2026-4572 #smartermail #游戏机 # GLM-TTS # 数据安全 #xshell #host key #内网 # 跳板机 #ansys #ansys问题解决办法 #Modbus-TCP #blender #设计师 #图像处理 #游戏美术 #技术美术 # ARM服务器 # 大模型推理 #编程助手 # Connection refused #系统管理 #服务 #代理服务器 #ip #雨云服务器 #Minecraft服务器 #教程 #MCSM面板 #Apple AI #Apple 人工智能 #FoundationModel #Summarize #SwiftUI #ceph #ambari #arm #多线程 #VMware Workstation16 #服务器操作系统 #claudeCode #content7 #elk #跳槽 #工作 #turn #网安应急响应 #odoo #excel #微PE # GLM # 服务连通性 #哈希算法 # 高并发 # 串口服务器 # NPort5630 #appche #数据恢复 #视频恢复 #视频修复 #RAID5恢复 #流媒体服务器恢复 #Ubuntu #TTS #go # GPU集群 #Gateway #认证服务器集成详解 #服务器开启 TLS v1.2 #IISCrypto 使用教程 #TLS 协议配置 #IIS 安全设置 #服务器运维工具 #ftp #sftp #uniapp #合法域名校验出错 #服务器域名配置不生效 #request域名配置 #已经配置好了但还是报错 #uniapp微信小程序 #框架搭建 #YOLO识别 #YOLO环境搭建Windows #YOLO环境搭建Ubuntu # 轻量化镜像 # 边缘计算 #Tokio #华为od #华为机试 #OpenHarmony #Python办公自动化 #Python办公 #版本控制 #Git入门 #开发工具 #代码托管 #SSH跳转 #Socket #套接字 #I/O多路复用 #字节序 #jupyter #量子计算 #研发管理 #禅道 #禅道云端部署 #samba #硬盘克隆 #DiskGenius # TTS服务器 # 键鼠锁定 #glibc #opc模拟服务器 #cpu #服务器线程 # SSL通信 # 动态结构体 #RWK35xx #语音流 #实时传输 #超算中心 #PBS #lsf #报表制作 #职场 #数据可视化 #信息可视化 #用数据讲故事 #zabbix #深度优先 #DFS #语音生成 #IO #AI写作 #PN 结 #后端框架 #ArkUI #ArkTS #鸿蒙开发 #.net #JNI #pxe #lvs #adobe #STUN # TURN # NAT穿透 #MCP服务器注解 #异步支持 #方法筛选 #声明式编程 #自动筛选机制 #数据迁移 #powerbi #麦克风权限 #访问麦克风并录制音频 #麦克风录制音频后在线播放 #用户拒绝访问麦克风权限怎么办 #uniapp 安卓 苹果ios #将音频保存本地或上传服务器 #express #cherry studio #gmssh #宝塔 #1panel #free #vmstat #sar #宝塔面板部署RustDesk #RustDesk远程控制手机 #手机远程控制 #系统安装 #铁路桥梁 #DIC技术 #箱梁试验 #裂纹监测 #四点弯曲 #可再生能源 #绿色算力 #风电 #系统安全 #scikit-learn #随机森林 #spine #若依 #TRO #TRO侵权 #TRO和解 #运维工具 #GLM-4.6V-Flash-WEB # AI视觉 # 本地部署 #IPv6 #网络攻击模型 #AI应用编程 #r语言 #3d #服务器IO模型 #非阻塞轮询模型 #多任务并发模型 #异步信号模型 #多路复用模型 #Minecraft #PaperMC #我的世界服务器 # 黑屏模式 #前端开发 #EN4FE #领域驱动 #自由表达演说平台 #演说 #程序员创富 #程序人生 #移动端h5网页 #调用浏览器摄像头并拍照 #开启摄像头权限 #拍照后查看与上传服务器端 #摄像头黑屏打不开问题 #文件IO #输入输出流 #流程图 #论文阅读 #图论 #国产开源制品管理工具 #Hadess #一文上手 #蓝桥杯 #工业级串口服务器 #串口转以太网 #串口设备联网通讯模块 #串口服务器选型 #embedding #kong #Kong Audio #Kong Audio3 #KongAudio3 #空音3 #空音 #中国民乐 #范式 #入侵 #日志排查 #kmeans #聚类 #Karalon #AI Test #人大金仓 #Kingbase #Spring AOP #健康医疗 #多进程 #python技巧 #高考 #iot #软件工程 #多模态 #微调 #超参 #LLamafactory #生信 #Smokeping #工程实践 #策略模式 #租显卡 #训练推理 #AI应用 #CMake #Make #C/C++ #图像识别 #bigtop #hdp #hue #kerberos #pencil #pencil.dev #设计 #Java程序员 #Java面试 #后端开发 #Spring源码 #Spring #win11 #vps #轻量化 #低配服务器 #Anything-LLM #IDC服务器 #私有化部署 #国产操作系统 #麒麟 #V11 #kylinos #raid #raid阵列 #rabbitmq #gpt #API #taro #java大文件上传 #java大文件秒传 #java大文件上传下载 #java文件传输解决方案 #wps #Linux多线程 #PyCharm # 远程调试 # YOLOFuse #simulink #matlab #journalctl #docker安装seata #Langchain-Chatchat # 国产化服务器 # 信创 #软件 #本地生活 #电商系统 #商城 #poll #生产服务器问题查询 #日志过滤 #Autodl私有云 #深度服务器配置 # 水冷服务器 # 风冷服务器 #VoxCPM-1.5-TTS # 云端GPU # PyCharm宕机 #webpack #database #儿童AI #图像生成 #实时音视频 #AI论文写作工具 #学术写作辅助 #论文创作效率提升 #AI写论文实测 #传统行业 #Syslog #系统日志 #日志分析 #日志监控 #warp # GLM-4.6V-Flash-WEB # AI部署 #everything #材料工程 #智能电视 #AB包 #VMware创建虚拟机 #远程更新 #缓存更新 #多指令适配 #物料关联计划 #挖漏洞 #攻击溯源 #编程 #stl #漏洞修复 #IIS Crypto #HistoryServer #Spark #YARN #jobhistory #二值化 #Canny边缘检测 #轮廓检测 #透视变换 #FASTMCP #DooTask #ZooKeeper #ZooKeeper面试题 #面试宝典 #深入解析 #ComfyUI # 推理服务器 #防毒面罩 #防尘面罩 #n8n解惑 #Go并发 #高并发架构 #Goroutine #系统设计 #net core #kestrel #web-server #asp.net-core #m3u8 #HLS #移动端H5网页 #APP安卓苹果ios #监控画面 直播视频流 #Prometheus #esp32 arduino #决策树 #Hadoop #交换机 #三层交换机 #vuejs #postman #产品运营 # 显卡驱动备份 #联机教程 #局域网联机 #局域网联机教程 #局域网游戏 #计算机毕业设计 #程序定制 #毕设代做 #课设 #开关电源 #热敏电阻 #PTC热敏电阻 #身体实验室 #健康认知重构 #系统思维 #微行动 #NEAT效应 #亚健康自救 #ICT人 #云开发 #云服务器 #个人电脑 #KMS 激活 #wireshark #广播 #组播 #并发服务器 #nacos #银河麒麟aarch64 #MC #MC群组服务器 # 服务器迁移 # 回滚方案 #homelab #Lattepanda #Jellyfin #Plex #Kodi #yolov12 #研究生life #es安装 #云计算运维 #asp.net大文件上传 #asp.net大文件上传下载 #asp.net大文件上传源码 #ASP.NET断点续传 #asp.net上传文件夹 #asp.net上传大文件 #gpu #nvcc #cuda #nvidia #漏洞挖掘 #c++高并发 #BoringSSL #企业存储 #RustFS #对象存储 #高可用 #log4j #Jetty # 嵌入式服务器 #模块 # 公钥认证 # 权限修复 #ICE #信创国产化 #达梦数据库 #群晖 #音乐 # 鲲鹏 #IntelliJ IDEA #Spring Boot #FTP服务器 #http头信息 #uip #Coturn #TURN #ci/cd #k8s #网站 #截图工具 #批量处理图片 #图片格式转换 #图片裁剪 #echarts #鸿蒙PC #树莓派 #温湿度监控 #WhatsApp通知 #IoT #MySQL # 端口7860 # HiChatBox # 离线AI #建筑缺陷 #红外 #数据集 #TCP服务器 #开发实战 #SMARC #ARM #全文检索 #银河麒麟服务器系统 # 代理转发 #Reactor #Kylin-Server #服务器安装 #短剧 #短剧小程序 #短剧系统 #微剧 #空间计算 #原型模式 #hibernate #nosql # 云服务器 #SMTP # 内容安全 # Qwen3Guard #junit #新人首发 #X11转发 #可撤销IBE #服务器辅助 #私钥更新 #安全性证明 #双线性Diffie-Hellman #MinIO服务器启动与配置详解 #clickhouse #平板 #零售 #智能硬件 #数据访问 #vncdotool #链接VNC服务器 #如何隐藏光标 #H5网页 #网页白屏 #H5页面空白 #资源加载问题 #打包部署后网页打不开 #HBuilderX #I/O模型 #并发 #水平触发、边缘触发 #多路复用 #r-tree #eclipse #servlet #CNAS #CMA #程序文件 #SSH复用 # 远程开发 #磁盘配额 #存储管理 #形考作业 #国家开放大学 #系统运维 #自动化运维 #DHCP #网络安全大赛 #C++ UA Server #SDK #跨平台开发 #agent #ai大模型 #服务器解析漏洞 #云服务器选购 #Saas #线程 #散列表 #机器视觉 #6D位姿 #UOS #海光K100 #统信 #outlook #错误代码2603 #无网络连接 #2603 #mssql #算力建设 #wpf #实时检测 #卷积神经网络 #GATT服务器 #蓝牙低功耗 #lucene # RTX 3090 #b树 #具身智能 #飞牛NAS #NVR #EasyNVR # ControlMaster #硬件 #Fun-ASR # 语音识别 #HarmonyOS APP #密码 #firefox #safari #windbg分析蓝屏教程 #AI电商客服 #le audio #低功耗音频 #通信 #连接 #spring ai #oauth2 #nmodbus4类库使用教程 #docker-compose #目标跟踪 #rtmp #飞牛nas #fnos #分布式数据库 #集中式数据库 #业务需求 #选型误 #HarmonyOS # 服务器配置 # GPU #copilot #mtgsig #美团医药 #美团医药mtgsig #美团医药mtgsig1.2 #反向代理 #参数估计 #矩估计 #概率论 #MinIO #Exchange #sentinel #静脉曲张 #腿部健康 #运动 #AI Agent #开发者工具 #边缘AI # Kontron # SMARC-sAMX8 #remote-ssh #ET模式 #非阻塞 #OpenAI #故障 #优化 #产品经理 #就业 #全链路优化 #实战教程 #sglang #SSH Agent Forwarding # 容器化 #高斯溅射 #UEFI #BIOS #Legacy BIOS #性能 #RAM #AI智能棋盘 #Rock Pi S #边缘计算 #改行学it #gateway #Comate #Proxmox VE #虚拟化 #memory mcp #Cursor #smtp #smtp服务器 #PHP #声源定位 #MUSIC