计算机网络(OSI和TCP篇)

前言

这篇文章主要总结计算机网络学习过程中的网络模型以及TCP连接的建立过程。
首先要明确一个概念:OSI模型并非现实中使用的网络协议。OSI主要是表明网络体系的一个模型,按照功能划分,一个网络应该分为哪几部分,每个部分应该发挥什么样的作用。
比如现实中,以太网、WIFI、光纤网等实现了第一层和第二层,IP协议则实现了第三层,IP协议不关心下面的层是如何实现的,只是提出几个基本要求,所以无论使用WIFI还是以太网,最后都可以使用IP协议进行上网。

网络模型

OSI模型

OSI模型将网络分为7层,分别是:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。

但是OSI由于过于复杂,并且层次划分不太合理,有些功能在多个层次中重复出现,所以在实际应用中很少有协议根据OSI来进行设计。

五层模型

但是实际上,我们通常在学习的时候将OSI体系结构和TCP/IP体系结构进行结合,变成了五层模型,相比OSI模型,主要是将最上面3层统一称为应用层。
五层模型以及各层次常用协议如下:

  • 应用层: 主要负责应用进程之间的交互来完成特定网络应用。活跃在应用层的协议主要有:DNS(域名系统)、HTTP(超文本传输协议)、SMTP(简单邮件传输协议)。
  • 运输层:主要负责向两台主机进程之间的通信提供数据传输服务。常用协议为:TCP(控制传输协议)、UDP(用户数据协议)
  • 网络层:负责为分组交换网上的不同主机提供通信服务,网络层主要讲传输层的保温和用户数据封装成分组和包进行传送。常用网络层的协议为:IP(网际互联协议)。
  • 数据链路层:两台主机之间的数据传输,总是在一段一段的链路上传递的。在两个相邻节点之间,数据链路层将网络层交代下来的IP数据报组装为帧,然后使用帧在两个节点之间传递。当接收到帧的时候,数据链路层将会根据帧中包含的必要控制信息来进行检错和纠错。
  • 物理层:主要功能是实现相邻计算机节点之间的比特流的透明传送,尽可能的屏蔽掉具体传送介质和物理设备的差异。

TCP连接过程

TCP,全称为Transimission Control Protocol,传输控制协议。
要想了解清楚三次握手和四次挥手的过程,首先必须要了解TCP报文段的格式。

TCP报文段

TCP报文段

  • 源端口、目的端口:这个报文从哪来,发送到哪去。具体的地址信息在IP头部中。
  • 序号:本报文段所发送数据的第1个字节的序号。
  • 确认号(ack): 期望收到对方下一个报文段的第1个数据字节的序号。如果ack=N,则表明N-1的所有数据已经正确收到。
  • ACK :标志位。除了在建立连接的时候ACK=0,其他时候都为ACK=1。
  • SYN:标志位。当SYN=1,ACK=0,表明这是一个连接请求的报文段;SYN=1,ACK=1,表明这是一个连接请求的响应字段;SYN=0,ACK=1表明这是一个连接建立之后的传输报文。
  • FIN:标志位。当FIN=1的时候,表明此报文段的发送方所有数据已经发送完毕要求释放连接。
  • RST:标志位。当RST=1的时候强制终止本次连接。
  • URG:标志位。当URG=1的时候说明紧急指针有效。
  • PUH:标志位。当PUH=1的时候该报文不进入缓冲区直接交付。

三次握手

了解了报文段的报文头结构之后,那么三次握手的过程就很容易理解了。
三次握手
首先客户端发送一个标志位SYN=1,ACK=0,序号为x的报文段给服务器,表明这是一个请求建立连接的报文段。
然后服务器会返回一个标志位SYN=1,ACK=1,序号为y,确认号为x+1的报文段表示我响应了你的连接请求,之前x的报文我收到了,我发送给你的报文序号为y。
最后,客户端就会返回一个标志位SYN=0,ACK=1,序号为x+1,确认号为y+1的报文给服务器,告诉服务器,我创建连接了,前面序号为y的报文我收到了,这是我序号为x+1的报文。
这样就可以进行正常的数据传输了。

为什么要使用三次握手

假设不使用三次握手,只要服务器发出确认报文段之后连接就算建立了。
那么会出现如下情形:

  • 客户端发出了一个报文段,但是在传输中滞留了很久,已经失效了。
  • 服务器收到了这个报文段,误以为这是一个新的连接建立请求,确认之后等待客户端发送信息。
  • 对于客户端来说这个报文段是一个失效的报文段,所以不会向服务器发送数据。
  • 所以服务器就一直傻傻的等着。

综上,主要的原因就是防止服务器接受了早就失效的报文,从而一直等待客户端传送数据,最终导致死锁。

四次挥手

四次挥手
其实上面这个图有一点不太正确,其实第一次客户端发送给服务器包中,ACK位也是为1的,TCP规定,除了第一次发送建立连接的包之外,所有包的ACK位都为1。
这是使用Wireshark抓包之后的第一次关闭连接发送的包。
关闭连接1
服务器收到关闭连接请求之后,会先返回一个确认信息,等待最后的数据传输完成,然后会发送一个关闭请求的包,FIN=1,ACK=1告诉客户端自己要关闭了。
客户端收到服务器的关闭请求之后会发送一个ACK=1的包,告诉服务器自己收到了服务器发送的关闭请求包。
服务器收到这个包之后,就进入了关闭状态。
客户端等待2MSL(两个最大报文生存时间)之后,如果没有收到服务器发送的消息,则也关闭。

为什么客户端最后要等待2MSL

假设客户端不等待2MSL,直接进行关闭,如果客户端发送的确认报文段丢失了,那么服务器无法收到FIN请求的确认,就会重新发送一个FIN请求包。
但是这个时候客户端已经关闭了,无法响应服务器,所以还是收不到确认报文段,所以服务器将无法判断客户端是否关闭了。
所以最后客户端需要等待2MSL(TCP中规定如果没有收到确认包则在2MSL后重新发送)。
除此之外,还保证了旧的报文段不会出现在新的连接中,如果关闭连接之后马上又建立连接,那么上一次遗留下来的报文段可能会影响新的连接,等待2MSL可以确保没有旧的报文段存活。

TCP和UDP的区别

TCP是面向连接的,传输可靠,用字节流进行传输。传输效率低,需要资源多。
UDP是无连接的,传输不可靠,使用数据报文段传输,传输效率低,所需资源少。

面向字节流

为什么TCP实际传输的是TCP报文段,但是仍然称是面向字节流的?
事实上,TCP每次传输的报文长度有限,如果太大则需要分块,分次传输。
但是由于TCP的可靠性,接收方可按照顺序接收数据块、重新组成分块之前的数据流。所以看起来就像是直接传送字节流一样。

粘包、拆包

刚才说到,TCP每次传输的报文有限,如果太长则需要分块,那么什么时候进行分块呢?
我们两个参数MSS和MTU。

  • MTU:一个网络包的最大长度,以太网中一般为1500字节。
  • MSS:除去IP和TCP头部之后,一个网络包能容纳的最大TCP数据长度。

MSS是建立连接的时候双方规定的,上面我们说到TCP报文包括报文头和报文数据部分,当数据部分超过MSS的时候就需要进行分片(这样就导致了组装好的IP报文一定不会超过MTU),这就是拆包。
而对于网卡来说,为了防止网络中充斥着小数据块(如果我要发送一个字节的数据,却组装了数个字节IP头部TCP头部看起来就很搞笑),会经常把一些比较小的包组装在一起,然后发送,这就是粘包。
这就导致了对于应用来说,看着是发送了两个报文段,接收的时候读了一次就把两个报文段都读出来了,又或者是发了一个报文段,却被拆成了两个,最终导致程序异常。

其实解决这个问题的方式在于,在写程序的时候编写消息边界,把TCP当做一个字节流的输入。当然也可以关闭Nagle算法,这样就不会产生粘包问题,不过这违反了TCP是面向字节流的原则。

TCP的可靠性

滑动窗口协议

定义:

  • 发送窗口:任意时刻,发送方维持的一组连续的、允许发送数据的序号。
  • 接收窗口:任意时刻,接收方维护的一组连续的、允许接收数据的序号。

滑动窗口不但出现在传输层,也会出现在数据链路层。区别在于一个是字节传输,一个是帧传输。(以下的协议主要说的是数据链路层)
对于发送方来说,只有收到了接收方的确认请求之后才会将发送窗口向前移动一格。响应的,接收方只有接收到了某一个数据之后才会将自己的窗口向前移动。
根据发送窗口和接收窗口的大小不同,分为了3种类型:

  • 停止等待协议:发送和接收窗口=1,即单帧滑动,发送方每次发送一个帧之后都必须等待接收方传回确认帧,如果超时的话则会进行重传。
  • 后退N帧协议:发送窗口>1,接收窗口=1,发送方可以每次发送多个数据,接收方会返回到目前为止接收到了哪些帧。如果出错,则发送方会根据最后一次确认号进行后退,重新发送之前的帧。如果超时则会尽心重传。。
  • 选择重传协议:发送窗口=1,接收窗口=1,在选择重传中与后退N帧相似,只是接收窗口大小不同。

流量控制&拥塞控制

流量控制

流量控制指的是就是 TCP传输中通过滑动窗口的模式来进行每次传输数据多少的调整。
接收方返回的报文段中会带有确认号和接收窗口大小,发送方会根据每次接受方返回的信息动态调整自己发送窗口的大小,以免发送速度过快。
当接收方来不及处理信息的时候,发送方将会把自己的发送窗口也变为0,然后定时地发送一个0窗口检测包,如果接收方返回了自己的接收窗口已经不为0了,那么可以继续发送,如果还是为0则继续等待。

拥塞控制

流量控制是为了防止信息发送太快导致接收方无法接收,拥塞控制则是为了防止过多的数据注入到链路中导致过载。
拥塞控制一个有4个算法,分别为慢开始&拥塞避免、快重传&快恢复,发送方维护一个拥塞窗口,根据拥塞窗口来定义自己的发送窗口大小(一开始的时候是相等,后面可能会根据接收窗口调整发送窗口大小,所以会小于拥塞窗口)。

  • 慢开始:指的是开始发送信息的时候拥塞窗口大小为1,每收到一个ACK,就把自己的拥塞窗口扩大为原来的2倍,直至一个阈值,到达阈值之后会进入拥塞避免算法。
  • 拥塞避免:每经过一个往返时间RTT,发送方的拥塞窗口大小加1。当网络出现拥堵的时候,将重新开始慢开始算法,并且将自己的阈值减少为原来的一半。
  • 快重传:接收方每收到一个失序的报文段的时候就直接发送重复确认,发送方只要连续收到3次重复确认就直接重传对方未收到的报文段。
  • 快恢复:当发送方收到连续3个重复确认之后,就会把拥塞窗口变为原来阈值的一半,然后开始拥塞避免算法,使窗口慢慢增大。

一些细枝末节的知识

虽然这一节的标题是一些细枝末节的知识,这些知识点很容易被人忽略,大多网上的文章也不能给出详细的说明,但是这些知识相当重要,一些大厂的面试官就非常喜欢问这些细枝末节的知识点以确定你是真的搞懂了而不是在背百度百科。

序列号是怎么初始化的

这个问题意思是在三次握手过程中客户端发起的第一次握手的序号seq=x和服务器发起的第二次握手的seq=y是怎么确定的。
为了保证不同连接不会相互产生影响,我们需要保证这个seq是不同的。
那么一般为了产生这个序列号就需要随机产生。
一般来说基于时钟的,每4毫秒+1。RFC11948中又提出了一个比较好的初始化方法:上面时钟产生的随机数后面增加一个hash算法,基于源IP、目的IP、源端口、目的端口生成一个数(比如MD5算法)。

为什么要三次握手

这个问题其实上面也说了,但是说的有一点片面。
其实三次握手的原因主要分为3个方面:

  • 三次握手才可以阻止历史重复连接的初始化
  • 三次握手才可以同步双方的序列号
  • 三次握手才可以避免资源浪费

顺便说一句,第三次握手是可以携带信息的,前两次则不携带。

原因一

根据RFC1 793指出三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。
有时候由于网络拥堵,客户端可能会发送多个SYN请求,导致了一个旧的无效的SYN请求达到了服务器。那么在这种情况下,服务器其实不知道这是一个失效的包,所以还是会返回一个ACK请求,这时候,客户端收到了这个ACK请求,一比较确认号,发现早就过期了,那么客户端会返回一个RST请求给服务端,表示连接中止。
注意,RST请求,也就说客户端收到请求之后是会返回一个过期消息的,并非什么都不做。
所以一共是三次,SYN请求、ACK确认,然后客户端检查ACK是否过期,如果没有过期就建立连接,过期则返回RST。

原因二

第二个原因是为了同步序列号,三次握手可以保证客户端和服务器之间确认自己的序列号和确认号都没有问题。
如果要确认相互之间的序列号和确认号,我们需要怎么做?
1.客户端发送一个序列号
2. 服务器收到这个序列号,发送一个这个序列号的确认号
3. 服务器发送一个自己的序列号
4. 客户端收到这个序列号,发送一个确认号

其中第二步和第三步可以一起进行,所以我们看到报文头里既有序列号也有确认号,而不是发送包归发送包,确认包归确认包。

这也就是为什么三次握手而不是四次握手的原因。

原因三

第三个原因是避免建立多个冗余的连接。

还是我们原因一中的情况,一个早已失效的SYN到达了服务器,,对于服务器来说当然不知道这个包已经失效了,万一客户端还等着呢?如果是两次握手的话就会直接开始传输数据。
那么由于网络拥堵,客户端发送了大量的失效包呢? 服务器要为每个失效的包都建立一个连接吗?那这样会大大浪费服务器资源。所以为了避免浪费服务器资源,还是需要确认客户端还需要这个连接才去建立连接。

Linux内核的连接队列

在Linux中存在两个不同的队列SYN队列和Accpet队列。
每当客户端发送一个SYN包之后,服务器收到了这个请求,并且把这个报文放入SYN队列中,然后返回SYN+ACK给客户端。
当客户端再次返回的时候,服务器会移除SYN队列中的对应包,然后将其加入到Accept队列中,这时候说明这是一个可用的连接了。
应用通过调用Accept()scoket接口,从Accept队列中取出连接。
通过linux的参数可以这些队列的最大值以及队列满时候的策略。

应对SYN攻击

当遭受到SYN攻击的时候,会导致SYN队列直接爆满,无法正常返回SYN+ACK包。
那这种情况下,可以采用linux参数net.ipv4.tcp_syncookies = 1,这样一来新进入的SYN包不在进入SYN队列,计算出一个cookie值,再以SYN+ACK中的序列号返回客户端。
当服务器收到客户端的应答报文的时候,服务器会检查这个ACK包的合法性,如果合法就直接放入到Accept队列中。

后记

下一篇将总结几个计算机网络常见的面试问题,比如:当在浏览器中输入url并按下回车之后发生了什么、常见状态码等等。
最近投了vivo的提前批,6月7号进行笔试和面试,所以在这之前要抓紧时间,赶紧把计算机网络、操作系统、数据库、设计模式等内容再复习一遍。
6月21日更新:
没想到vivo和tp-link的简历关都没过太惨了。
明天要进行招银的笔试,希望这次能经历一下面试吧。


参考文献:
搞定计算机网络面试,看这篇就够了(补充版)
TCP-慢启动与拥塞避免
TCP协议的滑动窗口具体是怎样控制流量的?
个人计算机里面包含网络7层协议的所有协议吗,每一层分别对应哪个部分?
硬不硬你说了算!近 40 张图解被问千百遍的 TCP 三次握手和四次挥手面试题


  1. Request For Comments(RFC),是一系列以编号排定的文件。文件收集了有关互联网相关信息,以及UNIX和互联网社区的软件文件。RFC文件是由Internet Society(ISOC)赞助发行。基本的互联网通信协议都有在RFC文件内详细说明。RFC文件还额外加入许多在标准内的论题,例如对于互联网新开发的协议及发展中所有的记录。因此几乎所有的互联网标准都有收录在RFC文件之中。 ↩︎ ↩︎

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值