网络知识 review & 简单梳理

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

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中加入 下面这一行来表示当前页面内容不缓存

 

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 & 简单梳理)