美文网首页
网络知识 review & 简单梳理

网络知识 review & 简单梳理

作者: yanianthe | 来源:发表于2018-04-21 14:30 被阅读0次

    简单回复一下基本的网络知识,希望对大家快速回顾起某个领域的主要知识有所提益

    1. TCP/IP

    TCP/IP 基本知识

    1.1 IP报文格式

    ip 报文协议
    three-way-handshake

    1.2 TCP 延迟确认

    reference:

    1. https://cloud.tencent.com/developer/article/1004356
    2. https://www.villainhr.com/page/2016/07/19/%E5%8A%A0%E5%BF%AB%E7%BD%91%E7%BB%9C%E9%80%9F%E5%BA%A6-TCP%E4%BC%98%E5%8C%96
    3. TCP-IP详解 19章

    1.2.1 why 延迟确认?

    网络请求中可能真实的 ip包中,实际内容很少,比 ip报文少多了。大量小包加剧了网络拥堵,同时大大降低了网络使用率。

    1.2.2 dealay ack 与nagle算法

    1. 通常 tcp接受到数据后并不会立刻返回ack,而是会在下一次数据返回中捎带这个ack确认。若是后面没有数据,则这个ack会有一个时延,超过时延,ack包就会被发送出去,一般时延是 200ms(接收端延迟)
    2. nagle算法: 一个TCP 连接上最多只能有一个没有被确认的ip分组,该分组确认到达之前不能发送其他分组。在此等待期间,会收集数据,在下一次一次发出去。

    (1)如果包长度达到MSS,则允许发送;

    (2)如果该包含有FIN,则允许发送;

    (3)设置了TCP_NODELAY选项,则允许发送;

    (4)未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送;

    (5)上述条件都未满足,但发生了超时(一般为200ms),则立即发送。

    小结:

    1. nagle 算法,发送端限制 大量小包的发送,可以使用 tcp_nodelay 关闭这个延迟
    2. delay ack,接受端会在响应数据的时候捎带 ack确认,当后面没有数据包的时候,时延一到,ack包就会发送出去。
    3. nagel 混合delay ack使用

    1.3. tcp 拥塞控制与流量控制

    流量控制: 滑动窗口协议

    拥塞控制

    • .慢开始:在主机刚刚开始发送报文段时可先将拥塞窗口 cwnd 设置为一个最大报文段 MSS 的数值。在每收到一个对新的报文段的确认后,将拥塞窗口增加至多一个 MSS 的数值。用这样的方法逐步增大发送端的拥塞窗口 cwnd,可以使分组注入到网络的速率更加合理。每经过一个传输轮回,拥塞窗口(发送端)就加倍。*

    • 2.拥塞避免:让拥塞窗口缓慢增大,每经过一个往返时间就加1,而不是加倍,按线性规律缓慢增长。拥塞窗口大于慢开始门限,就执行拥塞避免算法。“乘法减小”:指不论在慢开始还是拥塞避免阶段,只要出现超时重传就把慢开始门限值减半。"加分增大“:指执行拥塞避免算法后,使拥塞窗口缓慢增大,以防止网络过早出现拥塞。合起来叫AIMD算法。*

    • 3.快重传算法:发送方只要一连收到三个重复确认就应当重传对方尚未收到的报文。而不必等到该分组的重传计时器到期。 *

    • 4.快恢复算法:(1)当发送端收到连续三个重复的确认时,就执行“乘法减小”算法,把慢开始门限 ssthresh 减半。但接下去不执行慢开始算法。(2)由于发送方现在认为网络很可能没有发生拥塞,因此现在不执行慢开始算法,即拥塞窗口 cwnd 现在不设置为 1,而是设置为慢开始门限 ssthresh 减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大.*

    image

    1.4 TCP time_wait 与端口耗尽

    1.4.1 为什么会有 time_wait?

    TCP 是双向连接的,所以关闭的时候也需要 发起两次 主动关闭。


    TCP Close

    主动关闭的一方 会进入 time_wait 状态。客户端主动关闭连接,发送完最后一个ack后,会进入time_wait状态。经过2MSL 后才进入closed状态。

    引入 time_wait 的主要目的是:

    1. 当主动关闭的一方 最后发送的ack 没有被 接受方收到时,接收方会再发送一次 fin,假如这时候主动方已经关闭了,这个时候就会报错,connnection reset。为什么需要是 2MSL,因为假设 发送方发出的ack经过一个MSL后,没有收到,接收方再发出一个fin 也接过了一次MSL,所以大概是2MSL 的时长。
    2. 假如主动方 发出最后一个ack后,直接进入了closed 状态,有可能这个 ack 很慢。而 此时相同端口和ip的新连接又建立起来并发出了报文,还提前比上一个ack到达了,这样就造成了混乱。所以引入 2MSL也是为了确保,在建立新的连接的时候,网络上所有旧的分组已经没有了。(time_wait阶段是不允许再进行相同端口的tcp连接。)
    • 旧的TCP连接已经不存在了,系统此时只能返回RST包
    • 新的TCP连接被建立起来了,延迟包可能干扰新的连接

    1.4.2 为什么会端口耗尽?

    注意: 只有主动关闭的一方才会进入 time_wait 阶段。 一般端口耗尽的场景都是一些短连接场景
    比如 A 服务器 请求B 服务器,短期内发起大量的请求,并关闭。如压测场景。
    这时候A机器会有大量的端口处于time_wait阶段,还要等待2MSL 才会被回收。所以这个期间假如再大量发起请求,就会出现端口耗尽
    一般这种时候可以 尝试优化下内核参数:一般有以下几个参数:

    #收缩TIME_WAIT状态socket的回收时间窗口   
    net.ipv4.tcp_tw_timeout=3
    表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
    net.ipv4.tcp_tw_recycle = 0  
    net.ipv4.tcp_tw_reuse = 0    表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
    

    注意是 调整请求发起方的机器

    1. syn flood

    2. HTTP

    2.1. HTTP 报文基本知识

    http request = 请求行 +** 请求头** + 空行 + 请求数据

    http response = 状态行 + 消息报头 + 空行 +返回数据

    小结:

    1. ** 请求和响应中,头部和数据之间都有一个空行**
    2. ** GET 和 POST 区别**
      get 提交,请求的数据会附在url 上,其http请求中 内容为空,最后一行是空行。
      post 提交则不同, 请求的数据 会放在 http 请求中的 内容中。因此 地址栏不会显示
      倘若请求数据太多,有些浏览器和web服务器会对 url的长度做限制,因此长度有时候会报错。
      (cubes 中 下载明细的时候,把请求参数都弄在url中,get请求时backend 机会报一个长度超出限制的异常)
      get 请求 url可能会被浏览器缓存
    3. 响应状态码
    • 100 continue 这个临时响应表明,迄今为止的所有内容都是可行的,客户端应该继续请求,如果已经完成,则忽略它。100 continue: 当curl 中 post数据量超过1K 的时候, curl会分为两步,1. 在请求体中 搞一个 expet "100-continue"的请求头,当服务器返回 100-continue的时候,再把内容 post过去。否则不post内容
    • 200 OK 客户端请求成功
    • 201 Created 客户端请求成功
    • 301 Moved Permanently 该请求已成功,并因此创建了一个新的资源
    • 302 Moved Temporarily 请求临时重定向
    • 304 Not Modified 文件未修改,可以直接使用缓存的文件。
    • 400 Bad Request 由于客户端请求有语法错误,不能被服务器所理解。
    • 401 Unauthorized 请求未经授权。这个状态代码必须和WWW-Authenticate报头域一起使用
    • 403 Forbidden 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因
    • 404 Not Found 请求的资源不存在,例如,输入了错误的URL
    • 406 Not Acceptable 请求的资源的内容特性无法满足请求头中的条件,因而无法生成响应实体。
    • 500 Internal Server Error 服务器发生不可预期的错误,导致无法完成客户端的请求。
    • 503 Service Unavailable 服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常。

    2.2 HTTP 连接管理

    reference: https://juejin.im/entry/5880e3df1b69e60058cb7b71

    2.2.1 串行连接

    每个请求起一个 tcp连接。这样大部分开销都花在了 tcp连接建立的时间上。而且http请求只能一个接着一个来,俗称串行请求

    2.2.2 并行连接

    可以同时开多个tcp连接。并行连接不一定能够加快。因为总的带宽是有限的,太多并行连接会竞争带宽资源。对服务端是一个性能灾难,同时维持这么多个连接,一下就崩了。
    而且 多个tcp连接很耗内存,客户端基于这个考虑也会限制 并行数量。
    一般浏览器限制时4 个,谷歌是6条tcp连接。

    2.2.2 持久连接

    1. http/1.1 后引入 connnection:keep-alive 。1.1 中默认是长连接的
    2. 什么时候 关闭连接?:服务器可以在任何时候关闭该条tcp连接
    3. 关闭时先关闭 输出端,再关闭输入端。tcp半关闭

    2.2.3 管道连接

    3 http 缓存

    3.1 浏览器缓存

    referrence: https://www.cnblogs.com/lyzg/p/5125934.html

    1. 强缓存

    2. cache-control: Cache-Control:max-age=315360000 相对时间,以秒为单位,第一次请求资源后,浏览器会把响应结果跟 header头存起来,下一次再 请求时,根据当前时间与max-age 算出缓存是否还生效。生效就直接从浏览器里获取这个资源。

    3. expire: 也是设置一个失效时间,但是又有一个不同服务器的时间偏差的问题。当cache-control 和expire两个同时存在只会生效一个,cache-control 优先级比较高
      小结:强缓存不会与服务器进行通信。直接从本地浏览器缓存获取。这是与304一个比较大的区别,但强随之会引入一个资源更新的问题。一般的方法是: 1. 利用文件摘要对资源进行命名。2. 非覆盖式的,每次修改新增一个资源文件。3.发布时 全量更新资源文件,再灰度更新 html文件。

    4. 协商缓存
      当强缓存失效后,会发一个请求到服务器,验证协商缓存是否命中。
      主要使用 [last-modified, if-modifyed-since][etag if-none-match] 来管理的

    5. last-modified 和 if-modified-since:
      浏览器在第一次请求资源的时候,服务器返回响应中会返回一个 last-modified 这个值。
      浏览器下一次请求该资源,会 将 if-modified-since 设置为 last-modified 的值,服务器收到请求后,会根据文件的最后修改时间判断,若文件未修改,则返回304,浏览器则从缓存中加载数据,若资源修改了,则服务器返回资源内容,并更新last-modifeid 值。
      但经常会有文件修改了,但是最后修改时间却没有变化的情况。所以又引入了etag 和 if-none-match 来解决这种情况。

    6. ETag 和if-none-match:
      ETag 可以看成文件的摘要信息,只要文件修改,etag就会相应修改。
      浏览器初次请求资源,服务器会返回一个Etag值,
      浏览器下次再请求的时候,会将 if-none-match 设置为etag的值,服务器根据收到请求中的if-none-match和资源生成的etag进行比对,若相同,则文件没有改变,返回304,同时返回 etag值。
      分布式情况下 每台机器生成的etag都不一样,同时需要注意 last-modified要保持一致。
      etag的优先级要高于last-modified

    7. 启发式缓存

    3.2 不使用缓存

    可以通过在head中加入 下面这一行来表示当前页面内容不缓存

     <META HTTP-EQUIV="Pragma"  CONTENT="no-cache">
    

    4 http 访问控制

    refernce: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
    http://www.ruanyifeng.com/blog/2016/04/cors.html
    前端跨域:

    1. 浏览器在头中 增加origin: xxx
    2. 服务器端 会根据浏览器中的 origin 进行判断,域名在允许的列表中时会正常返回,同时在头部中会返回
      access-control-allow-origin,(该值必须要返回,要么为*,要么为固定的几个列表,浏览器才能判断请求是否合法),Access-Control-Allow-Credentials,option,表示是否要发送浏览器的cookie.
      如果要发送cookie,则allow-origin 不能设置为*号
      1. 1.0 和2.0 协议区别
      2. HTTPS 实现?
      3. http限流
      4. cookie
      5. 安全

    3. 网络编程--netty

    3.1 linux 网络IO 模型

    reference:
    https://segmentfault.com/a/1190000003063859#articleHeader12

    1. 阻塞IO 模型:
      [图片上传失败...(image-2ca914-1524292196821)]
      进程读写数据,进入内核空间。系统调用知道数据包达到 被复制到应用进程的缓冲区才会返回,该期间进程一直被阻塞。阻塞不耗费CPU。

    2. 非阻塞IO 模型
      [图片上传失败...(image-547bfc-1524292196821)]
      系统调用的时候,若内核缓冲区没有数据,则马上返回一个错误。应用程序会轮询检查这个状态,看内核数据是否准备完成。

    3. IO 复用模型
      [图片上传失败...(image-f89171-1524292196821)]

    3.2 IO 复用 select poll epoll

    IO 复用 应用在 服务器需要同时处理多个处于监听状态或者多个连接状态的套接字

    3.2.1 select

    reference: https://cloud.tencent.com/developer/article/1005481
    select 过程:

    1. 当用户进程 调用select的时候,会将注册在该select上的 socket 集合拷贝到内核空间上
    2. 然后遍历 socket集合,看是否有可读事件,遍历完后,发现没有可读时间,就会进入睡眠,(有个timeout时间)
    3. 若睡眠事件有可读事件,则继续上面的流程,否则就等timeout后再统一去check一遍。

    小结:

    1. 由于单个进程打开的文件描述符是有限的,一般会有一个设置,1024/2048是默认的。这也就决定二楼select能处理有限的socket。
    2. 虽然可以 调高 这个数量,但是select的时候,内核要把消息通知给用户空间,所以会把整个 socket 集合从 用户空间拷到 内核态,然后再在内核进行一一扫描,所以当socket 集合增大,这个复制时间也是线性增长的,一旦太高可能会出现频繁的超时。
    3. 同时,每次可读事件来了,都不知道哪个socket是可读的,所以还要线性扫描一遍整个集合,也是非常耗时的。

    3.2.2 poll

    poll 是在select 基础上改进的,唯一改进的点就是 公用一个文件描述符集合,使用链表来存储,因此不会有 文件描述符 1024的限制,但是仍然需要数据的拷贝,以及线性扫描整个socket集合。

    3.3.2 epoll

    红黑树 + 链表 +mmap
    epoll 主要解决上面两个问题:

    1. socket 集合数据拷贝问题.
    2. socket 线性扫描问题

    首先,内核要把fd的消息通知给用户空间,那么内存复制就是不可避免的,但是重点的是如何减少不必要的内存复制。
    epoll 通过 区分 注册事件,将fds 集合分散,按需,只有监听的事件来了,才会拷贝到内核空间中。对于高频的事件,则用mmap来减少内存复制。
    那么如何能够快速找到监听的事件?同时进行增删改?
    epoll 引入了 红黑树,来进行查找等操作。

    当使用epoll_ctl 注册号所有要交监听的事件后,红黑树建立完毕。同时每个事件都会在网卡建立一个回调关系,当事件来了之后,就会去回调这个callback函数。
    callback 函数主要做的事情就是去红黑树找到这个事件的节点,然后添加到双向链表中。
    然后双向链表再拷贝到内存中。
    这样就减少了 不必要的内存拷贝。同时也解决了无效的线性扫描问题

    [图片上传失败...(image-59e1f4-1524292196821)]

    1. epoll_wait调用ep_poll,当rdlist为空(无就绪fd)时挂起当前进程,直到rdlist不空时进程才被唤醒。
    2. 文件fd状态改变(buffer由不可读变为可读或由不可写变为可写),导致相应fd上的回调函数ep_poll_callback()被调用。
    3. ep_poll_callback将相应fd对应epitem加入rdlist,导致rdlist不空,进程被唤醒,epoll_wait得以继续执行。
    4. ep_events_transfer函数将rdlist中的epitem拷贝到txlist中,并将rdlist清空。
    5. ep_send_events函数(很关键),它扫描txlist中的每个epitem,调用其关联fd对用的poll方法。此时对poll的调用仅仅是取得fd上较新的events(防止之前events被更新),之后将取得的events和相应的fd发送到用户空间(封装在struct epoll_event,从epoll_wait返回)。

    Epoll 工作模式:
    LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。

    ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

    3.3 java NIO 与 netty

    todo 待续

    相关文章

      网友评论

          本文标题:网络知识 review & 简单梳理

          本文链接:https://www.haomeiwen.com/subject/ebjxlftx.html