【Linux】应用层协议HTTP

一、HTTP 协议基本概念

虽然我们说, 应⽤层协议是我们程序猿自己定的. 但实际上, 已经有⼤佬们定义了⼀些现成的, ⼜⾮常好⽤的应⽤层协议, 供我们直接参考使⽤. HTTP(超⽂本传输协议)就是其中之⼀。
在互联⽹世界中,HTTP(HyperText Transfer Protocol,超⽂本传输协议)是⼀个⾄关重要的协议。它定义了客⼾端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如HTML文档)。
HTTP协议是客⼾端与服务器之间通信的基础。客⼾端通过HTTP协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP协议是⼀个无连接、无状态的协议,即每次请求都需要建立新的连接,且服务器不会保存客⼾端的状态信息。

由于 "无状态" 无法满足登录验证、购物车、用户会话等场景需求,实际开发中会通过以下技术实现 "状态保持":

  1. Cookie

    • 原理:服务器在响应头中通过Set-Cookie字段,向客户端发送一段标识信息(如会话 ID);客户端后续请求时,会自动在Cookie字段中携带该信息,服务器通过解析 Cookie 识别客户端。
    • 示例:登录成功后,服务器返回Set-Cookie: session_id=abc123;客户端下次请求时,自动携带Cookie: session_id=abc123,服务器通过session_id确认 "该客户端已登录"。
  2. Session

    • 原理:服务器为每个客户端创建独立的 "会话对象"(存储登录状态、用户信息等),并生成唯一的session_idsession_id通过 Cookie 发送给客户端,客户端后续请求携带session_id,服务器据此找到对应的会话对象。
    • 特点:状态数据存储在服务器端,比 Cookie 更安全(避免敏感信息暴露在客户端)。
  3. Token

    • 原理:客户端登录成功后,服务器生成一段加密的 "令牌(Token)" 并返回;客户端后续请求时,在请求头(如Authorization: Bearer <Token>)或参数中携带 Token,服务器解密 Token 即可验证身份和状态。
    • 适用场景:跨域请求、移动端 APP(无需依赖 Cookie)。

二、URL 相关

平时我们俗称的 "⽹址" 其实就是说的 URL

  • URL:统一资源定位符,即 "网址",用于标识互联网中的资源位置。
  • urlencode 与 urldecode
    • 转义规则:特殊字符(如/、?、:)转为 16 进制,前加%(如+转义为%2B)。
    • urldecode 是 urlencode 的逆过程,用于解析转义后的字符。
      • 转义的规则如下:
        将需要转码的字符转为16进制,然后从右到左,取4位(不⾜4位直接处理),每2位做⼀位,前⾯加
        上%,编码成%XY格式
        例如:

三、HTTP 请求与响应格式

1. HTTP 请求格式

  • 首行[方法] + [url] + [版本](如GET /index.html HTTP/1.1)。
  • Header :键值对(冒号分隔),每组用\r\n分隔,空行表示 Header 结束(如Host: www.example.com)。
  • Body :空行后的内容,可为空;若存在,Header 中用Content-Length标识长度。
请求是怎么表示自己要请求什么资源的?
一、资源请求的表示:URI

客户端通过 URI(统一资源标识符) 向服务器表示要请求的资源,例如:

  • HTTP 请求行示例:其中/a/b/c.html就是 URI,明确了要请求的资源路径。
  • 浏览器地址栏的完整 URL(如8137.19.140:8080/a/b/c.html),包含服务器地址、端口和 URI 部分,共同定位网络中的资源。
二、服务器端资源的存储与定位

服务器的资源并非存放在 Linux 系统根目录,而是在web 根目录 下。资源的实际路径通过 "web 根目录 + URI" 拼接得到:

  • 若 URI 是/a/b/c.html,则实际文件路径为 ./wwwroot/a/b/c.html
三、首页的自动拼接规则

当客户端请求根路径(URI 为/)时,服务器会自动拼接默认首页文件 (如index.htmlindex.htm):

  • 实际路径为 ./wwwroot/index.html,确保用户访问根路径时能加载到首页。
四、网页资源的类型与存储

网页依赖的所有资源(如 html、css、js、图片、视频等),都需存放在 web 根目录下,通过上述 "web 根目录 + URI" 的规则被客户端请求和加载。

五、代码实现示例(资源路径拼接)

通过 C++ 代码示例,直观展示路径拼接逻辑:

cpp 复制代码
// 定义web根目录和默认首页路径
const std::string webroot = "./wwwroot";
const std::string homepage = "/index.html";

// 拼接实际文件路径并读取内容
std::string filename = webroot + homepage; // 结果为./wwwroot/index.html
bool res = Util::ReadFileContent(filename, &(resp._text));

2. HTTP 响应格式

  • 首行[版本号] + [状态码] + [状态码解释](如HTTP/1.1 200 OK)。
  • Header:同请求格式,键值对描述响应属性。
  • Body :空行后的内容(如 HTML 页面内容),长度由Content-Length标识。

四、HTTP 方法(常用)

方法 用途 特性 示例
GET 请求 URL 指定资源 数据在 URL 中,长度有限,常用于查询 GET /index.html HTTP/1.1
POST 提交表单数据等实体主体 数据在 Body 中,可传输大量数据 POST /submit.cgi HTTP/1.1
HEAD 类似 GET,但仅返回响应头 用于确认 URL 有效性或资源更新时间 HEAD /index.html HTTP/1.1
PUT 传输文件到指定 URL 位置 用于更新资源(较少用) PUT /example.html HTTP/1.1
DELETE 删除指定 URL 资源 PUT 的相反操作(较少用) DELETE /example.html HTTP/1.1
OPTIONS 查询资源支持的方法 返回允许的方法(如 GET、POST) OPTIONS * HTTP/1.1

GET:提交参数的方式通过uri进行提交的。会回显参数。

POST:提交参数的方式通过http request正文提交的。可以传递长数据。不会回显参数。

两者都不安全,都可以通过抓包工具抓取!

五、HTTP 状态码

(一)常见状态码汇总

状态码 含义 应用样例
100 Continue 上传大文件时,服务器告诉客户端可以继续上传
200 OK 访问网站首页,服务器返回网页内容
201 Created 发布新文章,服务器返回文章创建成功的信息
204 No Content 删除文章后,服务器返回 "无内容" 表示操作成功
301 Moved Permanently 网站换域名后,自动跳转到新域名;搜索引擎更新网站链接时使用
302 Found 或 See Other 用户登录成功后,重定向到用户首页
304 Not Modified 浏览器缓存机制,对未修改的资源返回 304 状态码
400 Bad Request 填写表单时,格式不正确导致提交失败
401 Unauthorized 访问需要登录的页面时,未登录或认证失败
403 Forbidden 尝试访问没有权限查看的页面
404 Not Found 访问不存在的网页链接
500 Internal Server Error 服务器崩溃或数据库错误导致页面无法加载
502 Bad Gateway 使用代理服务器时,代理服务器无法从上游服务器获取有效响应
503 Service Unavailable 服务器维护或过载,暂时无法处理请求

(二)重定向相关状态码

状态码 含义 是否为临时重定向 应用样例
301 Moved Permanently 网站换域名后,自动跳转到新域名;搜索引擎更新网站链接时使用
302 Found 或 See Other 用户登录成功后,重定向到用户首页
307 Temporary Redirect 临时重定向资源到新的位置(较少使用)
308 Permanent Redirect 永久重定向资源到新的位置(较少使用)

(三)重定向验证(以 301 为例)

  1. 依赖 Location 选项:HTTP 状态码 301(永久重定向)和 302(临时重定向)均依赖 Location 选项指定资源新位置。

  2. HTTP 状态码 301(永久重定向):

    • 含义:请求的资源已永久移动到新位置。

    • Location 头部作用:服务器在响应中添加 Location 头部,包含新 URL 地址,浏览器自动重定向到该地址。

    • 响应示例:

      复制代码
      HTTP/1.1 301 Moved Permanently\r\n
      Location: https://www.new-url.com\r\n
  3. HTTP 状态码 302(临时重定向):

    • 含义:请求的资源临时移动到新位置。

    • Location 头部作用:服务器添加 Location 头部指定新位置,浏览器暂时用新 URL 进行后续请求,不缓存重定向。

    • 响应示例:

      复制代码
      HTTP/1.1 302 Found\r\n
      Location: https://www.new-url.com\r\n

六、HTTP 常见 Header

(一)核心 Header 说明

字段名 含义 样例
Accept 客户端可接受的响应内容类型 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8
Accept-Encoding 客户端支持的数据压缩格式 Accept-Encoding: gzip, deflate, br
Accept-Language 客户端可接受的语言类型 Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Host 请求的主机名和端口号 Host: www.example.com:8080
User-Agent 客户端的软件环境信息 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
Cookie 客户端发送给服务器的 HTTP cookie 信息 Cookie: session_id=abcdefg12345; user_id=123
Referer 请求的来源 URL Referer: http://www.example.com/previous_page.html
Content-Type 实体主体的媒体类型 Content-Type: application/x-www-form-urlencoded(表单提交)或 Content-Type: application/json(JSON 数据)
Content-Length 实体主体的字节大小 Content-Length: 150
Authorization 认证信息,如用户名和密码 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==(Base64 编码后的用户名:密码)
Cache-Control 缓存控制指令 请求时:Cache-Control: no-cache 或 Cache-Control: max-age=3600;响应时:Cache-Control: public, max-age=3600
Connection 请求完后是关闭还是保持连接 Connection: keep-alive 或 Connection: close
Date 请求或响应的日期和时间 Date: Wed, 21 Oct 2023 07:28:00 GMT
Location 重定向的目标 URL(与 3xx 状态码配合使用) Location: http://www.example.com/new_location.html(与 302 状态码配合使用)
Server 服务器类型 Server: Apache/2.4.41 (Unix)
Last-Modified 资源的最后修改时间 Last-Modified: Wed, 21 Oct 2023 07:20:00 GMT
ETag 资源的唯一标识符,用于缓存 ETag: "3f80f-1b6-5f4e2512a4100"
Expires 响应过期的日期和时间 Expires: Wed, 21 Oct 2023 08:28:00 GMT

(二)Connection 报头补充

  1. 核心作用:控制和管理客户端与服务器之间的连接状态,管理持久连接(长连接)。
  2. 持久连接(长连接):
    • HTTP/1.1:默认使用持久连接,客户端和服务器未明确指定关闭连接时,连接保持打开,供后续请求和响应复用。
    • HTTP/1.0:默认连接是非持久的,需在请求头中显式设置Connection: keep-alive实现持久连接。
  3. 语法格式:
    • Connection: keep-alive:希望保持连接以复用 TCP 连接。
    • Connection: close:请求 / 响应完成后,关闭 TCP 连接。

七、文件读取辅助函数

cpp 复制代码
std::string GetFileContentHelper(const std::string &path)
{
    // 一份简单的读取二进制文件的代码
    std::ifstream in(path, std::ios::binary);
    if (!in.is_open())
        return "";
    in.seekg(0, in.end);
    int filesize = in.tellg();
    in.seekg(0, in.beg);
    std::string content;
    content.resize(filesize);
    in.read((char *)content.c_str(), filesize);
    // std::vector<char> content(filesize);
    // in.read(content.data(), filesize);
    in.close();
    return content;
}

八、网页报错信息(补充)

网页 URL 系统报错信息
http://127.0.0.1/ URL 拼写可能存在错误,请检查
https://www.new-url.com 网页解析失败,可能是不支持的网页类型,请检查网页或稍后重试
http://www.example.com/previous_page.html 网页解析失败,可能是不支持的网页类型,请检查网页或稍后重试
http://www.example.com/new_location.html 网页解析失败,可能是不支持的网页类型,请检查网页或稍后重试

九、HTTP服务器

Http.hpp

cpp 复制代码
#pragma once

#include "Socket.hpp"
#include "TcpServer.hpp"
#include <iostream>
#include <string>
#include <memory>
#include "Util.hpp"
#include <sstream>
#include <unordered_map>
#include "Log.hpp"

using namespace SocketModule;
using namespace LogModule;

const std::string gspace = "  ";
const std::string glinespace = "\r\n";
const std::string glinesep = ": ";

const std::string webroot = "./wwwroot";
const std::string homepage = "index.html";
const std::string page_404 = "/404.html";

// 请求报头
class HttpRequest
{
public:
    HttpRequest() : _is_interact(false)
    {
    }

    std::string Serialize()
    {
        return "";
    }

    void PaseReqLine(std::string &reqline)
    {
        // GET / HTTP/1.1
        // 将请求行字符串按reqline分割,得到三个字段
        std::stringstream ss(reqline);
        // 依次提取三个关键部分
        ss >> _method >> _uri >> _version;
    }

    bool Deserialize(std::string &reqstr)
    {
        // 1. 以换行符作为分隔符,读取请求行
        std::string reqline;
        bool res = Util::ReadOneLine(reqstr, &reqline, glinespace);
        LOG(LogLevel::DEBUG) << "reqline: " << reqline;

        // 2. 对请求行进行反序列化
        // GET / HTTP/1.1
        PaseReqLine(reqline);
        // 得到请求路径
        if (_uri == "/")
            _uri = webroot + _uri + homepage;
        else
            _uri = webroot + _uri;

        if (_method == "POST")
        {
            _is_interact = true;
            // 3. 以换行符作为分隔符,读取每个header
            while (Util::ReadOneLine(reqstr, &reqline, glinespace))
            {
                if (reqline.empty())
                    break;
                size_t pos = reqline.find(glinesep);
                if (pos == std::string::npos)
                {
                    //...
                    LOG(LogLevel::ERROR) << "请求报头格式错误";
                    return false;
                }
                std::string key = reqline.substr(0, pos);
                std::string value = reqline.substr(pos + glinesep.length());
                _headers.insert(std::make_pair(key, value));
            }

            // 4. 读取请求正文
            if (_headers.find("Content-Length") != _headers.end())
            {
                int content_length = std::stoi(_headers["Content-Length"]);
                _text = reqstr.substr(0, content_length);
                reqstr.erase(0, content_length);
            }
        }

        LOG(LogLevel::DEBUG) << "_method: " << _method;
        LOG(LogLevel::DEBUG) << "_uri: " << _uri;
        LOG(LogLevel::DEBUG) << "_version: " << _version;

        // 0.0.0.0:8080/login?username=zhangsan&password=123456
        // 从 URI 中提取查询参数, 并分离出纯资源路径
        const std::string temp = "?";
        auto pos = _uri.find(temp);
        if (pos != std::string::npos)
        {
            _args = _uri.substr(pos + temp.size());
            _uri = _uri.substr(0, pos);
            // 标记该请求包含交互参数
            _is_interact = true;
        }

        return true;
    }

    std::string Uri() { return _uri; }
    bool isInteract() { return _is_interact; }
    std::string Args() { return _args; }
    ~HttpRequest()
    {
    }

private:
    std::string _method;
    std::string _uri;
    std::string _version;

    std::unordered_map<std::string, std::string> _headers;
    std::string _blankline;
    std::string _text;

    std::string _args; // 查询参数
    bool _is_interact; // 是否交互模式
};

// 响应报头
class HttpResponse
{
public:
    HttpResponse() : _blankline(glinespace), _version("HTTP/1.1")
    {
    }

    std::string Serialize()
    {
        std::string status_line = _version + gspace + std::to_string(_code) + gspace + _desc + glinespace; // 状态行
        std::string resp_header;                                                                           // 响应报头
        for (auto &header : _headers)
        {
            std::string line = header.first + glinesep + header.second + glinespace;
            resp_header += line;
        }

        return status_line + resp_header + glinespace + _text; // 状态行 + 响应报头 + 空行 + 响应正文
    }

    bool Deserialize(std::string &reqstr)
    {

        return true;
    }

    void SetTargetFile(const std::string &target)
    {
        _targetfile = target;
    }

    void SetCode(int code)
    {
        _code = code;
        switch (_code)
        {
        case 200:
            _desc = "OK";
            break;
        case 404:
            _desc = "Not Found";
            break;
        case 302:
            _desc = "Found";
            break;
        case 301:
            _desc = "Moved Permanently";
            break;
        default:
            _desc = "Unknown";
            break;
        }
    }

    void SetHeader(const std::string &key, const std::string &value)
    {
        auto iter = _headers.find(key);
        if (iter != _headers.end())
            return;
        _headers.insert(std::make_pair(key, value));
    }

    std::string Uri2Suffix(const std::string &targetfile)
    {
        // 得到文件后缀,根据后缀返回Content-Type
        auto pos = targetfile.rfind('.');
        if (pos == std::string::npos)
        {
            return "text/html";
        }

        std::string suffix = targetfile.substr(pos);
        if (suffix == ".html" || suffix == ".htm")
        {
            return "text/html";
        }
        else if (suffix == ".jpg")
        {
            return "image/jpeg";
        }
        else if (suffix == ".png")
        {
            return "image/png";
        }
        else if (suffix == ".gif")
        {
            return "image/gif";
        }
        else if (suffix == ".css")
        {
            return "text/css";
        }
        else if (suffix == ".js")
        {
            return "application/javascript";
        }
        else if (suffix == ".json")
        {
            return "application/json";
        }
        else if (suffix == ".xml")
        {
            return "text/xml";
        }
        else if (suffix == ".txt")
        {
            return "text/plain";
        }
        else
        {
            return "text/html";
        }
    }

    bool MakeResponse()
    {
        if (_targetfile == "./wwwroot/favicon.ico")
        {
            LOG(LogLevel::DEBUG) << "用户请求: " << _targetfile << "忽略它";
            return false;
        }
        if (_targetfile == "./wwwroot/redir_test") // 重定向测试
        {
            SetCode(302);
            SetHeader("Location", "https://www.baidu.com");
            return true;
        }
        int filesize = 0;
        bool res = Util::ReadFileContent(_targetfile, &_text);
        if (!res)
        {
            // _text = "";
            // SetCode(404);
            // _targetfile = webroot + page_404;
            // filesize = Util::FileSize(_targetfile);
            // Util::ReadFileContent(_targetfile, &_text);
            // std::string suffix = Uri2Suffix(_targetfile);
            // SetHeader("Content-Type", suffix);
            // SetHeader("Content-Length", std::to_string(filesize));
            SetCode(302);
            SetHeader("Location", "https://www.baidu.com");
        }
        else
        {
            LOG(LogLevel::DEBUG) << "读取文件:" << _targetfile;
            SetCode(200);
            filesize = Util::FileSize(_targetfile);
            std::string suffix = Uri2Suffix(_targetfile);
            SetHeader("Content-Type", suffix);
            SetHeader("Content-Length", std::to_string(filesize));
        }
        return true;
    }

    void SetText(const std::string &text)
    {
        _text = text;
    }

    ~HttpResponse() {}

public:
    std::string _version;
    int _code;
    std::string _desc;

    std::unordered_map<std::string, std::string> _headers;
    std::string _blankline;
    std::string _text;

    std::string _targetfile;
};

using http_func_t = std::function<void(HttpRequest &req, HttpResponse &resp)>;

// 处理HTTP请求
class Http
{
public:
    Http(uint16_t port) : tsvrp(std::make_unique<TcpServer>(port))
    {
    }

    void HandlerHttpRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
    {
        // #ifndef DEBUG
        // #define DEBUG
        // 接收HTTP请求
        std::string httpreqstr;
        HttpResponse resp; // 一个应答
        int n = sock->Recv(&httpreqstr);
        if (n > 0)
        {
            HttpRequest req;             // 一个请求
            req.Deserialize(httpreqstr); // 请求反序列化,将uri解析为文件路径
            if (req.isInteract())        // 交互模式
            {
                if (_route.find(req.Uri()) == _route.end())
                {
                }
                else // 有注册的服务
                {
                    _route[req.Uri()](req, resp);
                    std::string response_str = resp.Serialize(); // 应答序列化
                    sock->Send(response_str);                    // 发送应答
                }
            }
            else
            {
                resp.SetTargetFile(req.Uri());
                if (resp.MakeResponse()) // 根据uri生成响应报文
                {
                    std::string response_str = resp.Serialize(); // 应答序列化
                    sock->Send(response_str);                    // 发送应答
                }
            }

            // std::string filename = req.Uri();
            // HttpResponse resp;
            // resp._version = "HTTP/1.1";
            // resp._code = 200;
            // resp._desc = "OK";

            // // LOG(LogLevel::DEBUG) << "用户请求:" << filename;
            // bool res = Util::ReadFileContent(filename, &(resp._text)); // 读取文件内容
            // (void)res;
        }

#ifdef DEBUG
        std::string httpreqstr;
        sock->Recv(&httpreqstr);
        std::cout << httpreqstr << std::endl;

        //
        HttpResponse resp;
        resp._version = "HTTP/1.1";
        resp._code = 200;
        resp._desc = "OK";

        std::string filename = webroot + homepage; // "./wwwroot/index.html" web根目录
        bool res = Util::ReadFileContent(filename, &(resp._text));
        (void)res;

        std::string response_str = resp.Serialize();
        sock->Send(response_str);
#endif
    }

    void Start()
    {
        tsvrp->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client)
                     { this->HandlerHttpRequest(sock, client); });
    }

    void RegisterService(std::string name, http_func_t h)
    {
        name = webroot + name;
        auto iter = _route.find(name);
        if (iter == _route.end())
        {
            _route.insert(std::make_pair(name, h));
        }
    }

    ~Http()
    {
    }

private:
    std::unique_ptr<TcpServer> tsvrp;
    std::unordered_map<std::string, http_func_t> _route;
};

Main.cc

cpp 复制代码
#include "Http.hpp"

void Usage(std::string programName)
{
    std::cout << "Usage: " << programName << " <port>" << std::endl;
}

void Login(HttpRequest &req, HttpResponse &resp)
{
    LOG(LogLevel::INFO) << req.Args() << " login";
    std::string text = "";
    resp.SetCode(200);
    resp.SetHeader("Content-Type", "text/html");
    text += "<html><head><title>Login</title></head><body>";
    text += "<form method='POST' action='/login'><label>Username:</label><input type='text' name='username'><br>";
    text += "<label>Password:</label><input type='password' name='password'><br>";
    text += "<input type='submit' value='Login'></form>";
    text += "</body></html>";
    resp.SetText(text);
}

void Regiter(HttpRequest &req, HttpResponse &resp)
{
    LOG(LogLevel::INFO) << req.Args() << " register";
    std::string text = "";
    resp.SetCode(200);
    resp.SetHeader("Content-Type", "text/html");
    text += "<html><head><title>Register</title></head><body>";
    text += "<form method='POST' action='/register'><label>Username:</label><input type='text' name='username'><br>";
    text += "<label>Password:</label><input type='password' name='password'><br>";
    text += "<label>Confirm Password:</label><input type='password' name='confirm_password'><br>";
    text += "<input type='submit' value='Register'></form>";
    text += "</body></html>";
    resp.SetText(text);
}

void VipCheck(HttpRequest &req, HttpResponse &resp)
{
    LOG(LogLevel::INFO) << req.Args() << " vip_check";
    std::string text = "";
    resp.SetCode(200);
    resp.SetHeader("Content-Type", "text/html");
    text += "<html><head><title>VIP Check</title></head><body>";
    text += "<form method='POST' action='/vip_check'><label>Username:</label><input type='text' name='username'><br>";
    text += "<label>Password:</label><input type='password' name='password'><br>";
    text += "<input type='submit' value='VIP Check'></form>";
    text += "</body></html>";
    resp.SetText(text);
}

int main(int argc, char* argv[])
{
    if (argc!= 2) 
        Usage(argv[0]);

    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<Http> httpsvr = std::make_unique<Http>(port);
    httpsvr->RegisterService("/login", Login);
    httpsvr->RegisterService("/register", Regiter);
    httpsvr->RegisterService("/vip_check", VipCheck);
    // httpsvr->RegisterService("/profile", Login);

    httpsvr->Start();

    return 0;
}
相关推荐
一位搞嵌入式的 genius2 小时前
HTTP与HTTPS深度解析:从明文传输到安全通信
计算机网络·安全·http·https·网络通信
熙客2 小时前
网络访问流程:HTTPS + TCP + IP
网络
Hello,C++!2 小时前
linux下libcurl的https简单例子
linux·数据库·https
white-persist2 小时前
差异功能定位解析:C语言与C++(区别在哪里?)
java·c语言·开发语言·网络·c++·安全·信息可视化
qq_401700413 小时前
Linux 磁盘挂载管理
linux·运维·服务器
AIwenIPgeolocation3 小时前
IP定位精度疑问:有些IP为什么难以达到街道级准确度?
服务器·网络·tcp/ip
liu****3 小时前
20.传输层协议TCP
服务器·网络·数据结构·c++·网络协议·tcp/ip·udp
q***31833 小时前
在Linux系统上使用nmcli命令配置各种网络(有线、无线、vlan、vxlan、路由、网桥等)
linux·服务器·网络
mljy.3 小时前
Linux《Socket编程Tcp》
linux