一. TCP/IP五层模型
1. 实体层:
光缆,电缆,无线电波,把电脑连接起来的物理手段。传输的是0和1这种电信号。但是光是0和1是没有意义的,需要数据链路层来定义这些0和1代表什么意义。
2. 数据链路层:
上图为一个帧,一组电信号构成一组数据包,就叫做帧。
head包含了发送者设备和接受者设备的信息。所谓的设备就是网卡,网卡的地址就是数据包的发送地址和接收地址,就是MAC地址。每个网卡在出厂的时候都有一个世界独一无二的地址。长度是48个二进制位,12个十六进制数。
前6个十六进制数是厂商的编号,后六组是网卡的流水号。
那一个网卡如何知道另一个网卡的地址呢?以太网中有一个ARP协议解决这个问题。
广播:比如1号要向2号设备发送数据,它会给所有网络中的设备都发送数据,让它们自己判断自己是不是接收方。通过读取这个数据包的包头,找到接收方的地址,然后与自身的MAC地址做一个对比。
但是广播这种方式有一个缺点,就是它只能在子网,就是同一块局域网里进行广播,那么如果上海的设备要向北京的设备发送数据,基本不可能。还有就是要向子网里所有的设备都发送一个数据包,效率太低。所以我们需要使用一种方法来确定哪些设备属于同一个子网里的,如果确定了是属于同一个子网的话,再去使用广播的形式传递数据包。否则的话,我们就需要采取路由的方式发送。路由就是指如何向不同的子网络去分发数据包。
3. 网络层:
引入一套新的地址,让我们能区分不同的计算机是否在同一个子网内。这套地址就叫做网络地址,简称网址。
所以现在每台计算机都有两个地址,一个是MAC地址,一个是网址。二者没有关联。MAC地址是绑定在网卡上的,而网址是由管理员分配的。要先处理网络地址,再处理Mac地址。
ip地址:
ip协议:规定网络地址的协议。目前最广泛采用的是ipv4协议。
由32个二进制位组成,一般分为4段10进制数字。
范围:0.0.0.0 - 255.255.255.255
在互联网上,每个计算机都会分配一个ip地址。
这个ip地址分为两部分,前面部分代表网络部分(举个例子),后面代表主机:
所以同一个子网的前面部分是相同的,只有后面部分不同。但是,实际上网络部分不一定是由前24位组成的,也有可能是由前16位组成的。
那么如何判断两个ip地址是不是在同一个子网里呢?
子网掩码:表示子网络特征的一个参数。指明哪些是主机部分,哪些是网络部分,如果网络部分相同,那主机就在同一网络里。
形式上和ip一样,也是32位二进制数字。但是它的网络部分全部是1,主机部分全部是0。(二进制)。
11111111 11111111 11111111 00000000
255.255.255.0
通过将两个IP地址分别和子网掩码做 AND 运算,比较他们的结果是否相同,来判断这两个IP是不是在同一个子网下。
IP协议总结:有两个作用:
1. 为每个计算机分配一个IP地址
2. 确定哪些地址在同一个子网内。
IP协议发送的数据叫做IP数据包
但是以太网的数据包帧head里只包含Mac地址,没有包含IP地址,head长度也是固定的,那怎么将IP地址放进帧里?我们可以放入帧里的data里,这样就不用去修改以太网的规格。这就是分层的好处,上层的变动不会影响下层的结构。
IP数据包head主要包含版本号,长度,IP地址等信息。长度在20-60个字节。
data包含了IP数据包的具体内容。
所以以太网的数据包帧的结构就变成这样:帧的head还是不变,data放的是IP数据包的内容。
IP数据包的长度最大为65535个字节,减去head最小长度20个字节,就得到理论上IP数据包能装下的最大容量的数据,65535 - 20 = 65515个字节。
而以太网数据包的数据部分最大是1500个字节。如果IP数据包超过了1500个字节,那就需要进行分割。
通常情况下,我们知道对方的IP地址,但是不知道对方的Mac地址,这时就需要ARP协议。
我们假设存在两台计算机,分为两种情况:
1. 不在同一个子网络,所以没法知道对方的Mac地址,只能把数据包传送到两个子网络相连的网关处(路由器)处理。
2. 在同一个子网络,通过ARP协议我们就可以得知对方的Mac地址。具体就是向子网里每个计算机发送一个数据包,里面包含了目标计算机的IP地址,这时候其他的计算机接收到这个包会取出IP地址和自己比对,如果一样那就把自己的Mac地址传回去,如果不一样就丢弃这个包。
所以,只要有IP地址和Mac地址,两台计算机就可以建立起通信了。
但是现在问题是,一个电脑上可能很多程序进程都需要进行网络通信,那如何确定接收到的数据包是属于哪个程序的呢?
这时候我们需要一个参数,端口(port):
范围在0~65535之间,正好16个二进制位。其中0~1023的端口是被系统占用的。用户只能选择1023以上的端口。
4. 传输层:
建立端口到端口之间的通信。
而网络层是建立主机到主机之间的通信。
只要确定了主机和端口,我们就能进行程序之间的交流。
unix系统把主机+端口 叫做套接字(socket)有了socket之后,我们就能进行网络编程了。
传输层有两个重要的协议UDP TCP。
UDP协议
head里包含了发出端口和接收端口。包含8个字节。
data就是真正的数据了。然后把整个udp包放到IP数据包的数据部分。
所以以太网的数据包现在变成这样:
udp包总长度最大为65535字节。正好能放进一个IP数据包。
优点:简单,容易实现。
缺点:可靠性差,数据包发出去不知道是否已经被接收了。
为了解决这个问题,出现了TCP协议。
TCP协议
每发出一个tcp包就要进行确认,如果有一个数据包遗失,那就收不到确认。发送方就需要重新发送数据包了。
优点:保证数据不会丢失。
缺点:过程复杂,实现困难。会消耗比较多的资源。
tcp传包的时候,会通过:校验和,重传控制,序号表识,滑动窗口,确认应答去实现一个可靠的传输。
-TCP 提供一种面向连接的、可靠的字节流服务
-在一个 TCP 连接中,仅有两方进行彼此通信(点对点)。广播和多播不能用于 TCP
-TCP 使用校验和,确认和重传机制来保证可靠传输
-TCP 给数据分节进行排序,并使用累积确认保证数据的顺序不变和非重复
-TCP 使用滑动窗口机制来实现流量控制,通过动态改变窗口的大小进行拥塞控制
注意:TCP 并不能保证数据一定会被对方接收到,因为这是不可能的。TCP 能够做到的是,如果有可能,就把数据递送到接收方,否则就(通过放弃重传并且中断连接这一手段)通知用户。因此准确说 TCP 也不是 100% 可靠的协议,它所能提供的是数据的可靠递送或故障的可靠通知。
udp适用于高速传输,实时性较高的通信,或者广播通信。支持多对多的交互通信。
5. 应用层:
由于互联网是开放的网络,数据来源五花八门,比如email,www,ftp所以事先需要规定好数据格式。
而应用层做的就是规定这些数据格式。有不同的协议来规定这些数据格式,这些数据就放在了tcp数据包的data部分。
1向4发送数据包,就需要知道对方的Mac地址和IP地址。但是不在同一个子网下,是不能知道对方的Mac地址的。所以需要通过网关(Gateway)来转发。
具体流程:
1. 判断4是否和它属于同一个子网。不是的话就把数据包发送给A网关。
2. 网关通过路由协议发现4处于B网关内,就把数据转发到B网关,B网关再把数据转发给4。
3. 1把数据包发给A网关需要知道A网关的Mac地址。所以数据包的目标地址实际上分为:
这里在同一个子网络下,为什么还需要对方的IP地址呢?
同一子网下确实不需要IP层插手,但是他们之间的通信依然是要先封装成IP报文,再封装成以太帧。对方收到帧后,也是要先去帧头,再去IP头才能看到数据,这样是为了网络通信的统一性。
我们在配置一台新电脑上网设置时,要配置如下信息:
1. 本机IP地址
2. 子网掩码
3. 网关(路由器)的IP地址
4. dns的IP地址。
如果本机的IP地址保持不变,那就是静态地址,这样这个地址就被永久占用了。
大多还是使用动态IP地址。就是每次开机都会通过DHCP协议,重新分配一个IP地址。所有新计算机加入这个网络必须像DHCP服务器发送一个DHCP请求数据包,去申请自己的IP地址和相关的网络参数。
如果要发送数据包给对方,需要知道对方的Mac地址和IP地址,但是对于新加入的计算机并不知道这些信息。那么如何进行传输呢?通过DHCP协议去向DHCP服务器发送一个DHCP请求数据包,去申请自己的IP地址和相关的网络参数。有了这些信息之后,这台新电脑就可以在网上冲浪了。
DHCP协议是一种应用层协议,建立在udp协议之上。
当输入一个网址后,发生了什么?
当我们从本地请求一个网络地址(www.google.com)的时候,首先要通过dns协议,就是把这个网址转换成一个IP地址。具体就是向dns服务器发送一个请求包,然后它会返回给你谷歌的IP地址。
这时得到了返回给我们的IP地址和端口号。
接下来我们要做的是判断是否和自己属于同一个子网下。通过与自己的子网掩码和自己IP地址以及新浪的IP地址做一个按位与操作。不在同一个子网的话,就要通过网关(路由器)进行转发,接收方的Mac地址是网关的Mac地址。
而我们浏览网页时使用的是http协议
与前面的udp不同的是,这里使用的是tcp的形式。
tcp协议需要设置一个端口,对方的端口一般默认是80,而发送方,就是本机则随机选取1024到65535之间的一个整数,假设是14256,tcp标头长度是20个字节。
再往下走是IP协议,需要设置双方的IP地址。
再往下是以太网协议,需要设置双方的Mac地址。(对方的Mac地址需要设置为网关的Mac地址)
以太网数据包长度最大为1500个字节,而如果目前IP数据包已经达到5000字节,这时这个IP数据包必须要被分割为4个包
这个过程经过多个网关的转发。新浪服务器接收到了这四个包之后,会根据IP标头里的序号对这四个包进行按序拼接。形成一个完整的tcp数据包,然后再去读取http的请求。再作出一个http的响应,最后将他们应用tcp的协议再发回去。
当我们请求结束,服务端会告诉我们一个响应,返回了一个html的网页,然后由浏览器帮我们渲染出来。
二. 面试相关:
1. 抓包,三次握手:
序列号seq:占4个字节,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。
确认号ack number:占4个字节,期待收到对方下一个报文段的第一个数据字节的序号;序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号+1即为确认号。
确认ACK:标志位,占1位。当ACK=1时,则表示这个报文是一个用于确认的报文。 仅当ACK=1时,确认号字段才有效。ACK=0时,确认号无效
同步SYN:标志位,连接建立时用于同步序号。当SYN=1,ACK=0时表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使得SYN=1,ACK=1。因此,SYN=1表示这是一个连接请求,或连接接受报文。SYN这个标志位只有在TCP建产连接时才会被置1,握手完成后SYN标志位被置0。
终止FIN:用来释放一个连接。FIN=1表示:此报文段的发送方的数据已经发送完毕,并要求释放运输连接
PS:ACK、SYN和FIN这些大写的单词表示标志位,其值要么是1,要么是0;ack、seq小写的单词表示序号。
seq是数据包本身的序列号;ack是期望对方继续发送的那个数据包的序列号。
所谓三次握手(Three-way Handshake),是指建立一个 TCP 连接时,需要客户端和服务器总共发送3个包。
三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息。在 socket 编程中,客户端执行 connect() 时。将触发三次握手:
三次握手流程:
第一次握手(SYN=1, seq=x):
客户端发送一个 TCP 数据包,会将SYN 标志位设置为1,还会随机产生一个序列号seq,然后把这个数据包发送给服务器。发送完了客户端进入 SYN_SEND 状态。
第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):
服务器看到客户端发来的SYN=1就知道客户端要请求建立一个连接,所以服务器需要发回给客户端一个确认包(ACK)应答。这个确认包里会把即SYN 标志位和 ACK 标志位都设置为1。同时服务器还要随机产生一个seq序列号,放到自己这个包的 Seq 域里,还要把确认序号就是ack number设置为客户端的序列号+1。 发送完毕后,服务器端进入 SYN_RCVD 状态。
第三次握手(ACK=1,ACKnum=y+1)
客户端收到确认之后,会先检查对方的ack number是不是自己的序列号+1,还有ACK标志位是不是为1,如果都正确的话,客户端会再次发送确认包,具体就是把SYN 标志位设为0,这是因为SYN=1表示这是一个连接请求。只有在TCP建立连接时才会被置1,握手完成后SYN标志位被置0。同时ACK 标志位也是设为1,还要把ack number设置为服务器数据包的序列号+1,自己的序列号也要改成第一次握手时的序列号+1。
发送完了后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED状态,TCP 握手结束。
三次握手的过程的示意图如下:
四次挥手过程:
TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake)。客户端或服务器均可主动发起挥手动作,在 socket 编程中,任何一方执行 close() 操作即可产生挥手操作。
1)客户端进程向服务器发出释放连接报文,而且会开始停止发送数据。然后会释放数据报文首部,并设置FIN标志位=1,序列号为seq=u(u等于前面服务器传送过来的数据的最后一个字节的序号加1),这时候客户端就进入FIN-WAIT-1(终止等待1)状态。
2)服务器收到连接释放报文,就会对客户端发出一个确认报文,同时要设置ACK=1,ack number=u(收到数据包的序列号)+1,同时还要设置自己的序列号seq=v,这时服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候服务器处于半关闭状态,就是说客户端已经没有数据要发送了,但是服务器如果发送数据,客户端仍然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
3)客户端收到服务器的确认请求后,会进入FIN-WAIT-2(终止等待2)状态,还要等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
4)服务器把最后的数据都发送完成之后,就向客户端发送连接释放报文,设置FIN标志位=1,ack number=u(收到数据包的序列号)+1。由于服务器这时在半关闭状态,服务器很可能又发送了一些数据,所以服务器这时的序列号不一定是v+1了,假设这时的序列号为seq=w。发送完成后服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
5)客户端收到服务器的连接释放报文后,必须要再发出确认,设置ACK=1,ack number=w(最后一次服务器发的包的序列号)+1,然后它自己的序列号是seq=u+1。发送结束后客户端就进入了TIME-WAIT(时间等待)状态。这时候TCP连接还没有释放,必须经过2倍的MSL(最长报文段寿命)的时间后,才进入CLOSED状态。
6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。结束这次连接。
【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
【问题3】为什么不能用两次握手进行连接?
答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
【问题5】简单解释一些ARP协议的工作过程
【问题6】如果TCP三次握手的第三次的 ack包丢失会怎样?
第三次的 ack 包丢失就是说在 client 端接收到 syn + ack 之后,向 server 发送的 ack 包 由于各种原因 server 没有收到。这时 client, server 分别会进行怎样的处理呢?
Server 端
第三次的ACK在网络中丢失,那么Server 端该TCP连接的状态为SYN_RECV,并且会根据 TCP的超时重传机制,会等待3秒、6秒、12秒后重新发送SYN+ACK包,以便Client重新发送ACK包。
而Server重发SYN+ACK包的次数,可以通过设置/proc/sys/net/ipv4/tcp_synack_retries修改,默认值为5.
如果重发指定次数之后,仍然未收到 client 的ACK应答,那么一段时间后,Server自动关闭这个连接。
Client 端
在linux c 中,client 一般是通过 connect() 函数来连接服务器的,而connect()是在 TCP的三次握手的第二次握手完成后就成功返回值。也就是说 client 在接收到 SYN+ACK包,它的TCP连接状态就为 established (已连接),表示该连接已经建立。那么如果 第三次握手中的ACK包丢失的情况下,Client 向 server端发送数据,Server端将以 RST包响应,方能感知到Server的错误。