HTTP_实践&go_5_6

写在开头

如无必要,勿增实体”———威廉

这就是著名的奥卡姆剃刀原则, 他说的是, 小就是美, 今天在写处理http的网络库时, 深有感触

http协议位于传输层上方, 但是不一定是最高的层次, 他就像操作系统对于硬件和用户的关系一样, 既要支持用户直接用, 也要支持作为协议的一层

从支持用户直接使用的角度来看, 报文的请求行, 请求头, 请求体要一块进行处理, 但是从协议的一层来看, 请求体要向上传递, 交给另一个协议进行处理, 设计时, 要让 body部分不处理, 交付上层 这就带来了一个问题, 如何区分headersbody, 因为headers可以有很多行, 协议的设计人员使用一个空行来进行分隔 [具体看这里](#### 2.2 http报文格式), 既要用少的规则来区分不同的属性, 又要尽可能的简单

1 HttpRequest

  • 存储了连接的状态, URL以及 headers
class HttpRequest{
public:
    using HttpMap= map<string, string> ;
​
    enum Method{
        kInvalid, kGet, kPost, kHead, kPut, kDelete
    };
​

    enum Version{
        kUnkown, kHttp10 , kHttp11
    };
private:
    Method method_;
    Version version_;    
​

    std::string path_;
    std::string query_;
    Timestamp receiveTime_;
    std::map<string, string> headers_;
​

};

2 HttpContext

2.1 主要的功能:

  • 处理 http报文, 将报文解析出来, 传入成员HttpRequest
class HttpContext{
public:

    enum HttpRequestParseState{
        kExpectRequestLine, // 期望解析请求行
        kExpectHeaders, // 期望解析请求头部
        kExpectBody, // 期望解析请求体
        kGotAll, // 已经解析完整请求
    };
    bool parseRequCest(Buffer* buf, Timestamp receivetime);
private:
    HttpRequestParseState state_;
    HttpRequest request_;
​

};
​

2.2 http报文格式

  • 请求行 / 状态行

    • 方法名
    • URL
    • 协议版本
  • 请求头 / 响应头

  • 请求体 / 响应体

POST /index.html HTTP/1.1 #(回车换行)
Who: Alex                 #(回车换行)
Content-Type: text/plain 
Host: 127.0.0.1:8888
Content-Length: 28
Cookie: JSESSIONID=24DF2688E37EE4F66D9669D2542AC17B #(回车换行)
#(回车换行)
Let's watch a movie together
​

​
​

# 响应报文
HTTP/1.1 200 OK 
Server: Apache-Coyote/1.1
Content-Type: application/json 
Transfer-Encoding: chunked 
Date: Mon, 12 Sep 2011 12: 41: 24 GMT
6f
{"password":"1234","userName":"tom","birthday":null,"salary":0,
"realName": "tomson","userId": "1000","dept":null}
0

其中, 状态行和首部中的每行都是以回车符 (\r,%0d,CR) 和换行符 (\n,%0a,LF) 结束, headersbody中间有一个空行

因此我们也要分为三部分处理, 首先处理请求行, 再处理 header, 最后再处理 请求体

2.3 核心函数bool parseRequCest(Buffer* buf, Timestamp receivetime);

  • 功能: 从buffer中将报文解析出来
bool HttpContext::parseRequCest(Buffer *buf, Timestamp receivetime)
{

    bool ok = true;
    bool has_more = true;
    while(has_more){
        if(state_ ==kExpectRequestLine){
            // 找到第一行结束的位置
            const char *crlf = buf->findCRLF();
​

            if(crlf){
                // 处理 请求行
                ok = processRequestLine(buf-> Peek(), crlf);
​

                if(ok){
                    // 设置时间戳
                    request_.setReceiveTime(receivetime);
                    buf-> Retrieve(crlf + 2 - buf-> Peek());
                    // 处理完请求行, 处理 headers
                    state_ = kExpectHeaders;
                }
                else{// !ok
                    // 遇到错误, 处理失败
                    has_more = false;
                }
            }
            else{// ! crlf
                // 没有找到 crlf, 非法报文
                has_more = false;
            }
        }
​
        // 处理 头部, 本身没有循环, 通过外部循环来**逐行** 处理
        else if (state_ == kExpectHeaders){// state_ != kExpectRequestLine
            const char* crlf = buf->findCRLF();
​
            if(crlf){
                const char * colon = std::find(buf-> Peek(), crlf, ':');
                if(colon != crlf){
                    ++colon;
                    while(*colon == ' '){
                        colon++;
                    }
                    request_.addHeader(string(buf-> Peek(), colon), string(colon, crlf));
                }
                // 有一个空行, 头部处理完毕
                else{
                    state_ = kGotAll;
                    has_more = false;
                }
                buf-> Retrieve(crlf + 2 - buf-> Peek());
            }
            // 没找到回车换行, 非法报文
            else{
                has_more = false;
            }
        }
​
        // body 部分依旧留在 buf中, 
        // 空语句, 在这里不处理, 只是为了逻辑上更顺畅
        else if (state_ == kExpectBody){
​
        }
​
    }
    return ok;
}
​

2.4处理请求行

  • 每个值中间都用空格方法(空格)URL(空格)版本CRLF
bool HttpContext::processRequestLine(const char *begin, const char *end)
{

    // 传进来的数据就是一行, 不用找 crlf
    // 以空格为准
    bool succeed = false;
    const char * start = begin;
    const char * space = std::find(start, end, ' ');
​

    // 设置方法名
    if(space != end && request_.setMethod(string(start, space))){
        start = space +1;
​
        // 继续查找下一个空格
        space = std::find(start, end, ' ');
        if(space != end){
            const char * question = std::find(start, space, '?');
            if(question != space){
                request_.setPath(string(start, question));
                request_.setQuery(string(question, space));
            }
            else{
                request_.setPath(string(start, space));
            }
​
            start = space+1;
            succeed = end - start == 8 && std::equal(start, end-1, "HTTP/1.");
​
            if(succeed ){
                if(*(end-1) == '1'){
                    request_.setVersion(HttpRequest::kHttp11);
                }
                if(*(end-1) == '0'){
                    request_.setVersion(HttpRequest::kHttp10);
                }
                else{
                    succeed = false;
                }
            }
​

        }// space != end
    
        
    }// set method
    return succeed;
}

httpResponse

class HttpResponse{
public:

      enum HttpStatusCode
    {
        kUnknown,
        k200Ok = 200,
        k301MovedPermanently = 301,
        k400BadRequest = 400,
        k404NotFound = 404,
    };
​

    explicit HttpResponse(bool close)
        : status_code_(kUnknown),
        close_connection_(close)
    {
    }
​
        void setStatusCode(HttpStatusCode code)
    { status_code_ = code; }
​

    void setStatusMessage(const string& message)
    { status_message_ = message; }
​
    void setCloseConnection(bool on)
    { close_connection_ = on; }
​
    bool closeConnection() const
    { return close_connection_; }
​
    void setContentType(const string& contentType)
    { addHeader("Content-Type", contentType); }
​
    // FIXME: replace string with StringPiece
    void addHeader(const string& key, const string& value)
    { headers_[key] = value; }
​
    void setBody(const string& body)
    { body_ = body; }
​

    void appendToBuffer(Buffer* output) const;
​
private:
    HttpStatusCode status_code_;
    string body_;
    string status_message_;
    bool close_connection_;
    std::map<string, string> headers_;
};
​

第五节

环境

  • Linux/操作系统
  • Wireshark件
  • Socket 网络编程开发环境

ARP协议

  • 逻辑同网段才能发送ARP
  • ARP请求广播, ARP应答单播

免费 ARP

ARP(地址解析协议)是一种用于将 IP 地址映射到 MAC 地址的协议。它在局域网中起作用,允许网络设备通过发送 ARP 请求来查找目标设备的 MAC 地址。

“免费 ARP”(Free ARP)是指发送 ARP 请求时不指定目标 IP 地址,而是将目标 IP 地址设置为 0.0.0.0。这种类型的 ARP 请求旨在查询网络中是否存在冲突的 IP 地址。当一个设备收到了一个免费 ARP 请求时,如果发现自己的 IP 地址与请求中的源 IP 地址冲突,它将发送一个 ARP 响应来解决冲突。

免费 ARP 的主要用途是检测和解决 IP 地址冲突问题,因为每个设备都应该具有唯一的 IP 地址。通过发送免费 ARP 请求,设备可以确定是否有其他设备使用了相同的 IP 地址,以便及时处理冲突。

NAT

  • ip和端口都转换
  • 提高了 端口的利用率, 毕竟那么多端口, 用不完

NAT(Network Address Translation)是网络地址转换的缩写,是一种在计算机网络中用于将私有网络内部的 IP 地址转换为公网可路由的 IP 地址的技术。

在一个局域网中,通常会使用私有 IP 地址来分配给内部设备,例如以 “192.168.x.x”、”10.x.x.x” 或 “172.16.x.x” 到 “172.31.x.x” 开头的地址。这些私有 IP 地址在公共互联网上是不可路由的,因此无法直接与外部网络进行通信。

NAT 技术允许将多个内部设备的私有 IP 地址映射到一个或多个公共 IP 地址上,以实现与外部网络的通信。NAT 通常由一个网络设备(例如路由器或防火墙)提供支持,它拥有至少两个网络接口,一个连接到内部局域网,一个连接到外部公网。

当内部设备发送数据包到外部网络时,NAT 设备会将源 IP 地址从私有 IP 地址替换为公共 IP 地址,并将该转换信息记录在转换表中。当外部网络返回响应数据包时,NAT 设备会根据转换表将目标 IP 地址从公共 IP 地址转换回原始的私有 IP 地址,并将数据包传递给正确的内部设备。

NAT 技术有以下几个主要作用:

  1. 节省公共 IP 地址:由于私有 IP 地址在内部网络中可以重复使用,NAT 可以大大减少对公共 IP 地址的需求。
  2. 提供网络安全:由于内部设备的私有 IP 地址不直接暴露在公网上,NAT 在一定程度上提供了网络安全保护。
  3. 简化网络配置:通过使用 NAT,整个局域网可以共享一个公共 IP 地址,无需为每个内部设备分配公共 IP 地址。

需要注意的是,使用 NAT 技术的网络中的内部设备一般无法直接从外部网络访问,除非适当配置端口转发或设置 DMZ(Demilitarized Zone)。

TCP

HTTP1.1哪些优化?

  • 长连接
  • 部分传输
  • HOST
  • 缓存

https的加密过程

HTTPS(Hypertext Transfer Protocol Secure)是在 HTTP 基础上加入了安全套接层(SSL/TLS)的协议,用于在客户端和服务器之间进行安全的数据传输。以下是 HTTPS 的加密过程:

  1. 客户端发起连接请求:客户端向服务器发起连接请求,并请求建立加密连接。
  2. 服务器的证书:服务器将自己的数字证书发送给客户端,证书中包含了服务器的公钥和相关信息。
  3. 客户端验证证书:客户端收到服务器的证书后,会对证书进行验证。验证包括检查证书的合法性、颁发机构的可信度以及证书是否过期等。如果证书验证失败,客户端会发出警告或终止连接。
  4. 客户端生成密钥:如果服务器的证书验证通过,客户端会生成一个随机的对称密钥(session key),用于后续的数据加密和解密。
  5. 密钥交换:客户端使用服务器的公钥对生成的对称密钥进行加密,然后将加密后的密钥发送给服务器。
  6. 服务器解密密钥:服务器收到客户端发送的加密后的对称密钥后,利用自己的私钥进行解密,得到对称密钥。
  7. 数据加密:接下来的通信过程中,客户端和服务器使用对称密钥进行数据加密和解密。客户端和服务器的数据通过对称密钥进行加密后传输,保证了数据的机密性。
  8. 数据传输:在建立加密连接后,客户端和服务器可以安全地传输数据。数据在传输过程中使用对称密钥进行加密和解密,确保了数据的机密性和完整性。

通过以上步骤,HTTPS 实现了对通信过程中的数据进行加密保护,防止敏感信息被窃听或篡改。加密过程中,服务器的数字证书起到了验证服务器身份和传递公钥的作用,而对称密钥的使用则提供了高效的加密和解密机制。

网络提速

  • 网络提速-同运营商访问
  • 网络提速-动态API(播放/评论接口)路径优化(DSA)

故障排查

先止损再排查

比如推荐算法坏了-> 先不用推荐, 先随机刷刷刷

客户端排查

  • 客户端访问其他服务没问题吗?
  • 其他客户端访问目标服务没问题吗?

服务端排查

  • 服务端监控/指标都正常吗?
  • 手动访问一下正常吗?
  • 分组件排查

中间链路排查

  • 服务端跟客户端确保都没问题
  • 中间网络设备有没有问题?(交换机/路由器/网关LB)
  • 旁路的DNS有没有问题?

网络故障排查

  • dig查询DNS问题
  • ping/telnet/nmap查询三层/四层连通性
  • Traceroute排查中间链路
  • iptables
  • tcpdump

课后作业1

UDP socket 实现ack, 感知丢包重传提示: 1.学会UDP socket编程2.先从简单的ack学习,客户端等待ack再发包 3.什么时候客户端认为是丢包? 4.重传怎么考虑效率? 5.能不能不阻塞只穿丢掉的中间的段?

课后作业2

三台同网段内的服务器,模拟实现一个路由器

方法一:Linux操作系统配置法提示: 1.了解Linux的路由配置方式2.确保是同网段直连可达的环境.在三台机器上另外配置IP网段和路由. 3.一台机器做客户端,一台机器做路由器,一台机器做服务端. 4.客户端配置到达服务器的下一跳指向路由器,路由器上配置到达服务端的路由

方法二:用户态socket编程实现简易route软件提示: 1.收到指定的包后,做转发2.注意是修改报文的MAC.不是修改IP. 3.实现一个对称路由.这样可以实现TCP交互 4.可以通过ping来验证5.可以支持traceroute吗?

第六节课

域名系统的问题

  • 内网域名的解析也得出公网去获取,效率低下
  • 外部用户看到内网 ip地址,容易被 hacker攻击
  • 云厂商权威DNS容易出故障,影响用户体验

解决方案, 自建 DNS

常见的开源DNS:bind、nsd、knot、 coredns

DNS 解析的一般步骤:

  1. 发起解析请求:当用户在浏览器中输入一个域名时,系统首先会检查本地的 DNS 缓存,如果存在对应的解析结果则直接返回。如果本地缓存没有相关记录,系统会发起 DNS 解析请求。
  2. 查询本地 DNS 服务器:如果本地缓存没有域名解析记录,系统会向本地 DNS 服务器(通常由 ISP 或企业提供)发送解析请求。
  3. 递归查询过程:如果本地 DNS 服务器不知道域名的解析结果,它会向根 DNS 服务器发起递归查询请求。
  4. 根 DNS 服务器响应:根 DNS 服务器是整个 DNS 系统的顶级服务器,它并不直接返回域名解析结果,而是告诉本地 DNS 服务器应该向哪个顶级域名服务器(TLD DNS 服务器)发送查询请求。
  5. TLD DNS 服务器响应:本地 DNS 服务器收到根 DNS 服务器返回的 TLD DNS 服务器地址后,向 TLD DNS 服务器发送解析请求。
  6. 权威 DNS 服务器响应:TLD DNS 服务器会告诉本地 DNS 服务器要查询的域名的权威 DNS 服务器的地址。
  7. 权威 DNS 服务器响应:本地 DNS 服务器向权威 DNS 服务器发起解析请求。
  8. 解析结果返回:权威 DNS 服务器将域名的解析结果发送给本地 DNS 服务器。
  9. 缓存解析结果:本地 DNS 服务器将解析结果保存在缓存中,并将结果返回给用户的设备。同时,将解析结果缓存在本地,以便下次查询时可以直接返回,提高解析速度。
  10. 用户设备使用 IP 地址访问:用户设备收到 IP 地址后,会使用该地址发起与目标服务器之间的通信,实现对域名的访问。

通过以上步骤,DNS 解析过程中的各级 DNS 服务器协同工作,将域名转换为 IP 地址,使用户能够通过域名访问互联网上的各种资源。

  • 动态加速DCDN

    • 针对POST等非静态请求等不能在用户边缘缓存的业务, 基于智能选路技术, 从众多回源线路中择优选择一条线路进行传输.

请区分下列场景使用的加速类型

  • 1、用户首次登录抖音,注册用户名手机号等用户信息

    • 动态加速DCDN
  • 2、抖音用户点开某个特定的短视频加载后观看

    • 静态加速CDN
  • 3、用户打开头条官网进行网页浏览

    • 静态加速CDN+动态加速DCDN

4层负载均衡特点

  • 大部分都是通过dpdk技术实现,技术成熟,大厂都在用
  • 纯用户态协议栈,kernel bypass,消除协议栈瓶颈
  • 无缓存,零拷贝,大页内存(减少 cache miss)
  • 仅针对4层数据包转发,小包转发可达到限速,可承受高cps

什么是 nginx

Nginx(发音为 “engine-x”)是一款高性能的开源 Web 服务器软件,也可以用作反向代理服务器、负载均衡器和 HTTP 缓存。它由俄罗斯的程序员 Igor Sysoev 开发,并于 2004 年首次发布。

Nginx 的主要特点包括:

  1. 高性能:Nginx 采用了异步非阻塞的事件驱动架构,能够高效地处理大量并发连接和高流量的请求。
  2. 轻量级:Nginx 的内存占用较低,适合于资源受限的环境,并且能够有效利用系统资源。
  3. 反向代理:Nginx 可以用作反向代理服务器,接收客户端的请求并将其转发到后端服务器,隐藏后端服务器的真实 IP 地址,提供更好的安全性和负载均衡能力。
  4. 负载均衡:Nginx 可以基于不同的算法(如轮询、IP 哈希、最少连接等)将请求分发到多个后端服务器,实现负载均衡,提高系统的可扩展性和稳定性。
  5. 静态文件服务:Nginx 能够快速地处理静态文件的请求,通过内置的缓存机制提供高效的静态内容传输。
  6. HTTP 缓存:Nginx 支持反向代理缓存和本地 HTTP 缓存,可以减少后端服务器的负载并加快内容传输速度。
  7. HTTPS 支持:Nginx 能够配置 SSL 证书,提供安全的 HTTPS 连接。
  8. 可扩展性:Nginx 具有模块化的架构,可以通过加载不同的模块来扩展其功能。

由于其出色的性能和可靠性,Nginx 被广泛应用于许多大规模的网站、应用程序和服务中,包括一些知名的公司和网站,如 Netflix、GitHub、微博等。

优点

  • 模块化设计,较好的扩展性和可靠性
  • 基于master/worker架构设计
  • 支持热部署;可在线升级不停机更新配置文件、更换日志文件、更新服务器二进制
  • 较低的内存消耗:1万个keep-alive连接模式下的非活动连接仅消耗2.5M内存

dashboard.ngrok.com/get-started…

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYCOGYgy' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片