网络

网络协议概述

网络每层的头部基本上就是添加上本层的地址,还有一些校验和控制位

运输层的 MAC 地址是点到点的,没传递一次就会把发送者和接受者用 ARP 转换,替换为经过的路由器的 MAC 地址。而 IP 层的地址和端口号是端到端,从发送到结束始终不变。除非经过上层协议改变了地址和端口号,比如代理服务器或者 NAPT 路由器。

交换机没有任何地址,只有端口的概念。内部有一个转发表,记录了物理端口和 MAC 的对应关系,通过自主学习来建立。每台主机上都有自己的高速 ARP 地址缓存和路由表。可以通过 ip nip r 命令查看。

VLAN 可以理解为逻辑上将一台交换机分割成数台虚拟交换机,且这些虚拟交换机互不相通。Vlan 是广播域,而通常两个广播域之间由路由器相连接,广播域之间来往的数据帧通过路由器中继。因此 Vlan 间的通信也需要路由器(或者三层交换机)提供中继服务,即“Vlan 间路由”。

在阿里云上为内网VPC搭建NAT出口服务器

对于大多数的内部服务来说,我们是不希望他们暴露在公网上的;而且服务之间通过公网通信效率也比较低。阿里云提供了虚拟私网的服务,我们可以把服务都部署在内网。但是与此同时如何让内网的服务器能够上网也就成了问题,毕竟还是经常需要apt-get 一下。

首先,不可能每个服务器都绑定一个弹性IP,贵且不说,这样和又把内部服务暴露在了公网。

其次,阿里云提供了专用的NAT服务器,但是太贵了!!!

其实 NAT 服务器也很简单啦,就是一个路由转发而已,利用 iptables 可以轻松实现。下面以一个例子来讲解一下。

首先说一下 NAT 的两种术语:SNAT 和 DNAT。SNAT的意思就是 source NAT,也就是我们访问其他网站,作为 TCP 链接的来源。而 DNAT 就是 destination NAT,也就是我们作为服务器,作为 TCP 链接的重点。在这里我们要实现的是内网上网,而不是内网提供服务,所以我们只需要 SNAT 就好了。

假设我们有三台服务器,在一个内网中,分别是:10.1.1.1, 10.1.1.2, 10.1.1.3。其中 10.1.1.1 绑定了外网IP可以上网。这里要说明的是:阿里云的弹性 IP 实际上是一个“伪IP”,也就是并没有真的绑定到我们的主机上,而是通过 SNAT 和 DNAT 的方式来模拟了绑定IP的行为。可以通过 ip addr show 命令验证一下,并没用弹性 IP 的任何信息。

阿里云内网必须建立一个虚拟交换机来连接各个主机,在后台我们可以配置这个主机的路由表。为了实现让 10.1.1.1 作为出口的功能,我们配置交换机的路由表,添加如下一行:

把所有的流量都转发到 10.1.1.1

然后,在 10.1.1.1 上执行:

echo 1 > /proc/sys/net/ipv4/ip_forward   # 打开转发功能

# 所有来自10.0.0.0/8 的流量通过 eth0 发出
iptables -t nat -A POSTROUTING -s 10.0.0.0/8 -o eth0 -j MASQUERADE

iptables -A FORWARD -d 10.0.0.0/8 -j ACCEPT # 有人说需要这两句,但是亲测这两句不需要,但是也不知道什么意思
iptables -A FORWARD -s 10.0.0.0/8 -j ACCEPT

这时候在 10.1.1.2 上就可以上网了

说在最后:自己搭建DNAT/SNAT只能单机,无法做到高可用,因为阿里云不给我们提供VIP。如果你考虑构建高可用的私有云,还是直接购买阿里云的负载均衡+NAT网关吧,它们分别对应DNAT和SNAT,但是可靠性更高。

参考:https://yuerblog.cc/2017/03/25/vpc-in-aliyun/

epoll 的优势

select 和 poll 每次获取可读写的描述符都需要遍历所有的文件描述符,它们的时间复杂度都是 O(n),而 epoll 是基于回调的,每个 socket 上有事件发生都会调用回调函数放到 epoll 的就序列表中,因此 epoll_wait 只需要简单地读取这个列表,所以epoll的时间复杂度是 O(1) 的。

添加监控的socket只需要使用 epoll_ctl 添加一次,而获取消息 epoll 使用 mmap 加速内核与用户态的消息传递,不需要每次都把 socket 在内核态和用户态之间复制来复制去。

epoll 的工作模式

epoll 中有两个模式,水平触发(LT)和边缘触发(ET)。其中水平触发如果不做任何操作,就会一直触发,而边缘触发只会触发一次。就好比电工电子里面的两种触发模式。默认模式是 LT

Level Triggered (LT) 水平触发

  1. socket接收缓冲区不为空 有数据可读 读事件一直触发
  2. socket发送缓冲区不满 可以继续写入数据 写事件一直触发

符合思维习惯,epoll_wait返回的事件就是socket的状态

Edge Triggered (ET) 边沿触发

  1. socket的接收缓冲区状态变化时触发读事件,即空的接收缓冲区刚接收到数据时触发读事件
  2. socket的发送缓冲区状态变化时触发写事件,即满的缓冲区刚空出空间时触发读事件

仅在状态变化时触发事件

ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死(在while循环中调用read、write、accept,若是阻塞套接字,当资源不够时,进程会被阻塞,则其他准备就绪的文件描述符得不到处理,如果是非阻塞套接字,当资源不够时,上述系统调用返回-1,同时将errno设置为EAGAIN)

LT模式下开发基于epoll的应用要简单些,不太容易出错。而在ET模式下事件发生时,如果没有彻底地将缓冲区数据处理完,则会导致缓冲区中的用户请求得不到响应。

ET处理EPOLLOUT(socket 可写事件)方便高效些,LT不容易遗漏事件、不易产生bug。如果server的响应通常较小,一次性可以写完,不需要监听EPOLLOUT,那么适合使用LT,例如redis等、或者大多数的网络库。而nginx作为高性能的通用服务器,网络流量可以跑满达到1G,这种情况下很容易触发EPOLLOUT,则使用ET。关于某些场景下ET模式比LT模式效率更好,

nginx 中的使用

nginx 使用的是边缘触发模式

epoll 常用于构建事件驱动的非阻塞异步的事件循环,但是需要注意,本身 epoll_wait 这个操作是同步的。elect/poll/epoll的意义在于同时等待多个socket上的活动。select/poll/epoll永远都是阻塞的,跟socket是否阻塞无关。当然一般来说 epoll 管理的 socket 要设置成非阻塞的。

nginx会一直(阻塞)等待epoll返回事件通知或者epoll_wait超时,一旦有事件触发,nginx就会调用关联的(read/write)handler处理事件。开发者必须保证每一个事件handler都不得包含任何阻塞调用。否则,nginx worker的主线程将会因为一个事件阻塞,导致队列里面可能还有一大堆事件不能及时处理,这会严重影响nginx的效率。所以 nginx 的 socket不能设置为阻塞的,如果socket是阻塞的,那么一个socket的IO事件就会阻塞后续所有的事件处理,CPU就会空转,等在那里没事干了。而在socket非阻塞调用期间,nginx可以继续处理其他的事件。

epoll 的使用

epoll 总共有三个系统调用:epollcreate, epollctl, epollwait。其中 epollcreate 在内核中创建一个 eventpoll 结构体,epollctl 增加或者删除 socket 到 epoll 中,epollwait 等待事件发生。

创建 epoll 文件描述符

int epoll_create(int size);  // size 参数会被忽略

添加 socket 到 epoll 对象中

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  1. epfd 是 epoll_create 创建的
  2. op 是这三种常量,表示操作。
    1. EPOLLCTLADD:注册新的fd到epfd中;
    2. EPOLLCTLMOD:修改已经注册的fd的监听事件;
    3. EPOLLCTLDEL:从epfd中删除一个fd;
  3. fd,需要更改的 socket
  4. events 是一些参数

监听事件

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

一般如果网络主循环是单独的线程的话,可以用-1来等(即阻塞调用epoll_wait),这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0(立即返回)来保证主循环的效率。

参考:

  1. https://blog.csdn.net/dongfuye/article/details/50880251
  2. https://www.zhihu.com/question/63193746
  3. epoll 详解:https://www.cnblogs.com/ljygoodgoodstudydaydayup/p/3916760.html
  4. https://www.zhihu.com/question/22576054

TCP 握手常见考点

连接建立

图挂了

上面的图说的已经很好了,不再赘述。

为什么需要三次握手

这主要是为了防止已失效的连接请求报文段突然又传送到了服务端,服务端建立一个新的连接,因而产生错误。

所谓已失效的连接请求报文段是这样产生的。A 发送连接请求,但因连接请求报文丢失而未收到确认,于是 A 重发一次连接请求,成功后建立了连接。数据传输完毕后就释放了连接。

现在假定 A 发出的第一个请求报文段并未丢失,而是在某个网络节点长时间滞留了,以致延误到连接释放以后的某个时间才到达 B。本来这是一个早已失效的报文段。但 B 收到此失效的连接请求报文段后,就误以为 A 又发了一次新的连接请求,于是向 A 发出确认报文段,同意建立连接。假如不采用三次握手,那么只要 B 发出确认,新的连接就建立了。

由于 A 并未发出建立连接的请求,因此不会理睬 B 的确认,也不会向 B 发送数据。但 B 却以为新的运输连接已经建立了,并一直等待 A 发来数据,因此白白浪费了许多资源。

采用 TCP 三次握手的方法可以防止上述现象发生。例如在刚才的情况下,由于 A 不会向 B 的确认发出确认,连接就不会建立。

如果在 TCP 第三次握手中的报文段丢失了会发生什么情况?

Client 认为这个连接已经建立,如果 Client 端向 Server 写数据,Server 端将以 RST 包响应,方能感知到 Server 的错误。

链接释放

图挂了

需要注意的是,TCP 是全双工的协议,因此链接建立之后就没有客户端服务器的概念了,两边是对等的,都可以释放链接

MSS(最长报文长度), 由两端的较短值决定,在以太网中的典型值为 1460, 是以太网的 MTU(1500) 减去 IP 的头部 40B 得到的。

图挂了

为什么需要 TIME_WAIT 状态

首先是另一个概念 MSL, 最长报文生命周期,在 BSD 系统上一般设定为 30s, 不过可以长到 2min。TIME_WAIT 状态的时间设定为 2MSL。

TIMEWAIT 是主动关闭端进入的状态,发送完最后一个 ACK 之后进入 TIMEWAIT 状态等待 2MSL 才进入 CLOSED 状态

TIMEWAIT 状态存在的两个理由:
1. 可靠地实现 TCP 全双工链接的终止;假如对方没能收到最后一个 ACK, 那么他将会重新发送 FIN, 这时候如果客户端已经关闭了显然不能再次回复 ACK 了。
2. 保证上一个相同连接的数据包已经在网络上消失;如果一个新的链接建立在了同一个端口上,那么他将可能收到上一个进程的数据包,这是我们为老连接保留了 2MSL 的 TIME
WAIT 值,那么就可以保证原有的链接都不存在网络上了。

如果服务端主动关闭链接,也需要等待两个 MSL,那么重启服务器怎么绑定原有端口?

主动关闭链接端相应的端口会在 2 MSL 内处于 TIME-WAIT 状态而不能用。如果是客户端,那么问题不大,客户端一般会重新使用一个新的端口。如果是在服务端,因为服务端使用的都是常用端口,不能改变,也就是需要等待两个 MSL 才能使用刚拿的端口,比如 Nginx 重启,内核会显示当前端口是 busy 的,不能使用,等待 4min 显然是不现实的。为了解决这个问题,可以强制复用端口,在创建 socket 的时候使用 SOREUSEADDR 就可以了。不过需要注意的是,SOREUSEADDR 仅对 TIMEWAIT 状态有用,如果 socket 在其他状态,是不能复用的。注意不是 SOREUSEPORT。

其他

多个进程绑定同一个端口

我们知道一个同一个端口只有一个进程可以 bind 成功,那么 web 服务器是如何做到多进程呢?传统方法是在主进程使用 listen 然后在其他进程使用 accept 接受链接。不过这样会导致当有链接来的时候,所有的进程都会被唤醒,影响性能。在 3.9 内核之后,添加了 SOREUSEPORT 属性,可以使用 SOREUSEPORT 让每一个进程有当单独的监听队列,这样当有链接来的时候,内核只会唤醒一个进程。

当网络断开时,TCP 链接会断开吗?

TCP 链接有 keepalive 的功能,需要手动打开,但是一般来说没有人用。所以可以认为一般情况下,当物理网络断开时的时候,如果仅考虑 TCP 层,那么这个链接永远不会断开。不过一般来说,通过其他层面会知道这个链接断开了。比如说一般的应用层协议都会有心跳包的机制,这样就可以知道链接断开了。

参考

  1. https://serverfault.com/questions/329845/how-to-forcibly-close-a-socket-in-time-wait
  2. http://www.unixguide.net/network/socketfaq/4.5.shtml
  3. https://blog.csdn.net/Yaokai_AssultMaster/article/details/68951150
  4. https://www.zhihu.com/question/53672815
  5. https://blog.qiusuo.im/blog/2014/09/14/linux-so-reuseport/
  6. https://blog.csdn.net/xy010902100449/article/details/48274635
  7. https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/

关于 CDATA

CDATA stands for Character Data and it means that the data in between these strings includes data that could be interpreted as XML markup, but should not be.

So we could use CDATA to smuggle some HTML into the XML document, so that the HTML doesn’t confuse the XML document structure, and then use XSLT later to pull it out and spit it into a HTML document that is being output.
 
In short, you don’t have to escape all the < and & in CDATA section
 

RSS 2.0

<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
    <channel>
        <title>Example Feed</title>
        <description>Insert witty or insightful remark here</description>
        <link>http://example.org/</link>
        <lastBuildDate>Sat, 13 Dec 2003 18:30:02 GMT</lastBuildDate>
        <managingEditor>[email protected] (John Doe)</managingEditor>
        <item>
            <title>Atom-Powered Robots Run Amok</title>
            <link>http://example.org/2003/12/13/atom03</link>
            <pubDate>Sat, 13 Dec 2003 18:30:02 GMT</pubDate>
            <description>Some text.</description>
            <source>Shit News</source>
        </item>
        <item>...</item>
    </channel>
</rss>

RSS 协议的一些不足和改进方向

  1. 没有标识文章重要度的字段
  2. 没有途径把订阅数量等信息反馈给 RSS 提供方
  3. 没有品牌特性
  4. 没有机器推荐
  5. 如果能够把 RSS 包装成像是 Amazon Prime 那样的服务,用户可能会很愿意付钱

实际上文章的增删改查是一套组合操作,而只使用一个 RSS 作为列表显然是不够的,必然要拓展。

现代的 RSS 阅读器需要做三个方面

  1. 一个社区
  2. 能够把所有服务都提供 RSS,包括不提供 RSS 的站点
  3. 评论服务
  4. 转码。有的 RSS 只提供了文章的摘要,有的 RSS 有实效性,有的 RSS 有自己的字体

     

    Atom 1.0

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>Example Feed</title>
    <subtitle>Insert witty or insightful remark here</subtitle>
    <link href="http://example.org/"/>
    <updated>2003-12-13T18:30:02Z</updated>
    <author>
        <name>John Doe</name>
        <email>[email protected]</email>
    </author>
    <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
 
    <entry>
        <title>Atom-Powered Robots Run Amok</title>
        <link href="http://example.org/2003/12/13/atom03"/>
        <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
        <updated>2003-12-13T18:30:02Z</updated>
        <summary>Some text.</summary>
    </entry>
 
</feed>

 

reference

 
[1] http://www.intertwingly.net/wiki/pie/Rss20AndAtom10Compared