Bootstrap

详解TCP IP网络协议栈底层原理到徒手实现

TCP/IP网络协议栈分为四层, 从下至上依次是:

这四层协议对应的数据包封装如下图:

四层协议对应的通信过程如下图:

链路层 以太网数据帧

以太网数据帧格式如下:

说明如下:

ARP协议

在网络通信过程中, 源主机的应用程序只知道目的应用程序的IP地址, 并不知道对方主机的硬件地址, 所以在数据发送之前, 需要先找到目标及其的硬件地址, 这就是ARP协议所起的作用了。

每次在建立连接之前, 会在本地网络广播发送目的IP地址, 所有机器都会受到该请求, 目的机器发现该请求中的IP地址跟自己一样, 就把自己的硬件地址返回回去, 否则忽略该请求。

一般来说, 每台机器都维护的有一个ARP缓存表, 存储了近期的IP地址和硬件地址的映射关系, 可以用arp -a命令来查看缓存表中内容。

如果目的机器和本机器不在同一个网段之内的话, 会将数据发送给网关来处理, 一般网关就是路由器, 此时网关会进行IP路由, 将ARP请求发送到目的网络地址, 然后再依次将应答返回给该发起请求的机器。

【文章福利】小编推荐自己的Linux、C/C++技术交流群:【960994558】整理了一些个人觉得比较好的学习书籍、视频资料共享在里面(包括C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等等.),有需要的可以自行添加哦!~

IP协议

IP协议数据包格式如下:

几个字段解释如下:

IP地址的一共分为如下几类:

在互联网刚出来的时候, 大部分组织都申请的B类网络地址, 导致B类地址很快就用完了, 但是A类又有很多空闲的地址, 而每个路由器又必须掌握所有网络的信息, 随着C类网络的增多, 路由器中的路由表项数也就越来越多了。

针对这种情况, 后来人们发现, 绝大部分内部网络的机器都不需要一个独立的公网IP的, 这些机器通过一个公网IP跟外部连接, 在自己的网络内部为每台机器申请一个私有IP, 内部再建设一个路由器, 做内网IP地址的定位即可。

私有IP的出现大大解决了IP浪费的问题, 所以我们日常中可以看到很多如192.168.xx这样的IP, 这些IP都只是局域网内部IP, 不会浪费IP地址。

于是, RFC1918就规定了组建局域网的私有IP地址规范:

这些私有IP地址虽然没有公网IP, 但是仍然可以通过NAT等技术来跟公网进行连接交互。

除了私有IP之外, 还有几种特殊的IP地址:

TCP协议

数据包格式

TCP协议数据包如下:

部分字段解释如下:

交互过程

上图中每次连接线上的数字标记了此次数据包中的关键信息, 比如

那么接下来我们看TCP协议的交互过程:

滑动窗口

如上讲的都是一来一回的交互, 一般情况下可能会存在一方数据发得特别快, 另一方数据发得特别慢, 这种时候如果不做控制, 势必会让慢的这方数据处理不过来从而导致丢包。

TCP协议中采用了滑动窗口协议来解决该问题, 类似上面的mss, 再增加一个新的选项win, 告诉对方自己的滑动窗口大小, 对方在发送数据的时候每次发送数据就知道对方到底窗口空间还够不够, 如果不够了就不发了, 从而解决了一快一慢这种问题。

连接状态

如下图:

其他状态都还好, 在工作中常会碰到TIME_WAIT连接过多的问题, 这里把TIME_WAIT状态单独拿出来说一下。

TIME_WAIT是主动关闭方在收到被动关闭方发的FIN包之后处于的状态, 这个包是主动关闭方收到的最后一个包了, 在收到这个包之后还不能直接就把连接给关闭了, 还得等待一段时间才能关闭, 等待时间为2MSL。

为什么要等待一段时间呢? 主要是两个原因:

所以一定要有一个TIME_WAIT的状态等待一段时间, 等待的MSL时间RFC上面建议是2分钟, 但是笔者实际工作中测试往往是30秒。

但是如果你的服务是一个高并发短连接服务, TIME_WAIT可能会导致连接句柄被大量占用, 而你又相信服务内部是一个非常稳定的网络服务, 或者即使有两个连接交互出现故障也可以接受或者有应用层处理, 不希望有那么多的TIME_WAIT状态的连接, 一般有两种方式:

UDP协议

UDP协议就简单很多了, 基本上就只包含源地址, 目的地址, 长度, 校验, 数据。

交互过程也不再像TCP这样经过很复杂的建立连接和关闭连接的过程了, 就直接每次都发送数据了, 这样会有如下的一些问题:

所以如前面所说, UDP协议并不保证数据的可靠性, 他一般用于一些高性能的场景, 且需要应用层再做一些简单的封装处理。