网络传输层总结
Posted 孙晓凯
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络传输层总结相关的知识,希望对你有一定的参考价值。
背景知识
一:目前业界网络体系结构的实际标准是什么?分为几层?
答:分为5层,从低到高(以应用层为最高)分别如下:
1. 物理层
2. 数据链路层
3. 网络层
4. 传输层
5. 应用层
二:传输层的主要协议是什么?
答:TCP和UDP
TCP协议结构
先说TCP,说起TCP还要先从TCP协议的包头文件格式说起。包结构主要包含如下几个:
- 源端口
- 目的端口
- 包的序号
- 确认序号
- 标志位(SYN,FIN等,我称之为标志位)
- 窗口大小
源端口和目的端口的作用是识别发送和接收数据的应用。包的需要解决的是传输过程中包的顺序问题和可靠性保证。确认序号保证的是包的可靠传输。标志位在三次握手和四次挥手的时候用于状态转移。窗口的大小用于进行流量控制和拥塞控制(流量控制和拥塞控制的主要区别在与,流量控制是根据接收放的处理信息能力进行控制,而拥塞控制主要是针对整个网络环境状态进行控制)
TCP的连接与释放
三次握手(连接)
所谓的三次握手指的是数据在双方之间的三次发送与接收。TCP的定位是一个面向连接的协议,在传输数据之前需要先建立连接,一个经典的比喻是打电话,必须先拨通号码,然后才能说话。但是当拨通了号码后,真的是在双方之间假设了一根电话线吗(我说的是现代,是手机,不要杠),铁定没有。那所谓的连接究竟指的是什么呢?连接指的是就是接收方与发送方各自在本方维护的对方的一种状态。
举个例子,地震的后一家人中有个人(B)被埋在了废墟里,并且还活着,然后外面的人(A)知道他的具体访问,于是就会产生如下对话:
A:B你还活着吗,能听到吗?
B:活着,能听到
A:好,我听到了
这个对话后,各自都建立了一种对方的状态,A保存了B还活着,而且能通信的状态,而B保存着A找到了自己并且可以通信的状态。于是双方就可以说建立了一个连接。
那为什么建立连接要三次握手呢?因为三次握手后,可以保证任意一方都能知道自己和对方收发数据的能力是否正常。还是用上面的例子分析一下:
当B收到了A的“你还活着吗”的信息时,B就知道了A还活着并且还能说话,也知道了自己还活着,并且耳朵还好使。接着B回复说“活着,能听到”,这时候B就知道自己的耳朵,嗓子都没毛病,A收到后也知道自己的耳朵,嗓子铁定好使,而且B的耳朵,嗓子也好使。但是此时B并不知道A的耳朵好不好使,有没有收到自己的回复。当A回复了“好,我听到了”之后,B接到回复才知道双方的耳朵,嗓子都好使。于是就可以进行沟通,援助等工作了。
那问题来了,如果最后A说的话被废墟给吸走了呢,导致B没有收到A的回复,那莫A会声嘶力竭的呼喊“我在这呢”,A听到一遍又一遍的呼喊,也一遍一遍的回复,只要有一个能听到,就可以建立连接。
如果是都没有听到呢,这是后A可以之间数据发送,如果B能收到,连接成功。B还是收不到,连接失败。
那建立连接有啥用呢?上面说了,建立连接其实就是维护了一个状态,那维护状态的本质是啥呢?其实就是在包头里设置了相应的数据,有了这些数据,就可以进行一些算法设计了啊。A发送B一个数据包,包头里一个数据代表该包有没有发送成功。然后B收到了这个包,回复给A一个确认包,A收到确认包后,就知道发送成功了。就改下状态,并从自己的已发送未确认发送记录中删除。否则就重新发送数据包。
三次握手除了进行连接,还有一个目的,就是确定发送包的其实序列号。每个包都是有一个序列号的,用于确定组成一个完整数据的各个包的序号。每次连接序列号从1开始不行吗?不行,因为如果A和B连接后,A发送了1,2,3三个包(三个包组成一个字符串“hello”)给B,序号分别为1,2,3.然后B只收到了1,2.接着A断开了B,然后又重新连上了B,于是序号还是从1开始,然后又发送了三个包1,2,3.会发生什么?会发生第二次的第三个包和第一次的1,2包组合后被当成了一个完整数据。
所以,每次连接不能从一个确定的数开始,而是需要一个重复概率比较小的随机数开始,在TCP中是这样设计的:包头结构中有一个序号是32位,然后每4毫秒加1,加到最大值是一个循环。算下来一个循环需要几个小时,所以理论上来说同一个连接不会存在序号相同的问题。
那如果是两个不同的连接呢,这种方法在两个不同的连接上序号也是会相同的呀!对在不同的连接上,也许4毫秒内有多个连接,所以多个连接的其实序号是相同的。但是连接和连接的起始地址,目的地址,源端口,目的端口 肯定不是完全一样的。(起始地址,目的地址,源端口,目的端口)也被成为四元组,这四个信息能唯一确定一个连接。
TCP所要解决的问题
- 顺序问题
- 丢包问题
- 流量控制
- 拥塞控制
四次挥手(释放)
为啥连接的时候需要三次握手,而断开连接却需要四次握手呢?
可以这么理解,对与连接的时候,收发双方都是啥也没干,所开可以认为开始状态是静止的。
但是断开的时候,收发双方都在工作,不在一个状态,一方可能已经没有数据发送了,但是另一方还有数据需要处理和发送。所以需要同步一下(等双发都没有数据发送和处理),达到同一种状态,然后在进行下一步动作----关闭。
四次挥手的过程(以A,B为例子):
- A发送FIN请求给B
- B返回一个ACK
- B发送一个FIN
- A发送一个ACK
第一步A发送FIN给B后,此时A不再发送数据,但是可以接收数据。B可以发送和接收数据。
第二步B返回一个ACK,相当于收到消息确认,使得A不再重新发送消息
第三步B发送一个FIN后,B不再发送数据。此时A,B都不再发送数据。
第四步A直接发送个ACK,关掉连接即可。
UDP协议结构
- 源端口号
- 目的端口号
- 数据长度
- 校验和
从结构都能开出来,UDP这玩意异常简单,没存什么数据,也就设计不了什么高深算法,也就没啥可说。
UDP使用场景:
- 需要速度,不要求质量的场景,如流媒体
- 不需要一对一建立连接,而是可以广播,如移动通信
- 资源有限的场景,如自己的服务器
TCP和UDP的比较
都数据传输层的协议,哪能没有个比较呢?
TCP:建立连接,安全可靠,速度慢,占用资源多,传输数据流
UDP:无连接,不保证数据一定到达,速度快,占用资源少,传送数据块
在程序中使用TCP/UDP
一般的应用开发者,在编码中是不需要直接面向TCP编程的。而是使用大牛封装好的工具Socket.
使用TCP
服务端:调用Socket方法,bind地址和端口,得到一个SocketA.然后用Socket进行listen和accept,得到SocketB。然后对SocketB进行后续的处理。
SocketA和SocketB都是Socket,有什么不一样吗?
SocketA:是用于监听的Socket,主要用于监听连接。其中,有一个已经建立连接的队列和一个还没有完全建立连接的队列。每次accept的时候,都会从已经建立连接的队列中取出一个进行处理。
SocketB:是已经连接的Socket,主要用于接下来的处理。
此时一个问题已经出现了,accept是取出一个连接,然后处理一个连接,是阻塞的,也就是如果有10000个连接过来,即使都握手成功,连接上了,那么也需要一个一个的进行处理。那么对于用户两几亿几十亿的应用,铁定完蛋。
解决服务器连接数和效率问题的有以下几种方法:
多进程方式:
为每个连接建立一个进程,单独处理。听起来可以解决效率问题,但是当连接数过多的时候,这个解决方案根本没用。因为,每个进程都有PCB,需要存储空间,进程多的时候需要大量的内存空间。而且进程的调度需要PCB状态的保存与获取,以及切换中的其他资源。这就造成进程多的时候,效率很慢。关键是因为资源的限制,进程也多不了。
多线程方式:
相较于多进程方式,多线程方式是一个很大的进步。因为一个进程可以有多个线程,而且线程不需要有自己的存储空间,而是公用进程的存储空间。所以可以在耗费资源比较小的情况下创建大量的线程。
但是对于服务器来说,是创建不了这么多的线程的,因为大量线程的切换需要耗费大量的CPU资源。并且即使一个服务器可以创建10000个线程,那么对于一个有一亿用户的应用,则需要10000台服务器。那么像对于阿里巴巴这样的公司来说,可能需要包个山头来放他们的服务器了。这也就是C10问题。那么能不能让一个线程处理好多的连接呢,就相当于以前一个人干一个活,现在让一个人干好多活。这就是接下来的方式。
IO多路复用(同步非阻塞):
在Linux中,每个进程都有PCB,在PCB中有一个fd array,存放的是进程的文件描述符。文件描述符是干啥的?它就可以代表一个进程所打开的文件。当一个连接连接成功后,就放在创建一个fd,然后把fd放在进程的fd array里面。然后每隔一段时间,线程就扫描以下fd array,找到数据变化的Socket,然后进行处理。
这样一来,一个线程就可以处理多个Socket了。但是每次需要线程去扫描,也就是循环遍历,时间复杂度O(n),所以这种方式最大的连接数是1024,太大了扫描的效率太低。
终极杀手----IO多路复用(异步非阻塞):
这种方式和上一种方式区别就在于同步和异步,同步的话需要线程自己去遍历fd array.而异步的设计思路是,当fd array中有发生数据变化的Socket时,会自动通知线程。所以线程就不需要遍历了,也就突破了连接数的限制。这种方式每个线程可以处理非常多的连接,而每个进程还可以创建多个线程。所以它可以极大的提升服务器的连接数和效率。
注释:fd array怎么找到socket?fd array 的下表是fd的号,元素是fd的地址偏移量。通过这找到fd,然后fd中有inode。inode有一个字段指向内存中Socket的位置。
那是不是用最后一种方式就可以肆无忌惮的进行连接了呢?如果不是,还有什么可以限制的了它呢?
一:每个Socket占据一个fd,而一个系统中fd的数量是有限的。
二:每个Socket里面有一个sk_write_queue和一个sk_read_queue,前者用于存放要发送的包,后者存放要读取的包。而文章开头就说了TCP包头的结构,所以这些数据放在内存是需要大量的内存资源的,自然不能肆无忌惮创建。什么?你说是不是UDP的连接是不是占用的内存少?因为他的包头小? 答:UDP还需要连接???
使用UDP
UDP因为不需要连接,所以只需要服务端绑定地址和端口,然后进行数据收发就行了。
欢迎关注公众号:
以上是关于网络传输层总结的主要内容,如果未能解决你的问题,请参考以下文章