Linux网络编程 传输层 TCP/UDP

Posted 蚍蜉撼树谈何易

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux网络编程 传输层 TCP/UDP相关的知识,希望对你有一定的参考价值。

如何标识一个通信

在TCP/IP协议中, 用 “源IP”, “源端口号”, “目的IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信(可以通过netstat -n查看);

端口号范围划分

0 - 1023: 知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的.
1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的.

常见知名端口号


查看当前主机端口号:cat /etc/services

进程与端口号

  1. 一个进程是否可以bind多个端口号?
    因为一个进程可以打开多个文件描述符,而每个文件描述符都对应一个端口号,所以一个进程可以绑定多个端口号。
    Linux内核会给每一个socket分配一个唯一的文件描述符,进程通过该文件描述符来区分对应的套接字。
  2. 一个端口号是否可以被多个进程bind?
    同种协议通常不可以,但有一种情况可以。
    ps:如果进程先绑定一个端口号,然后在fork一个子进程,这样的话就可以是实现多个进程绑定一个端口号,但是两个不同的进程绑定同一个端口号是不可以的。

常见网络命令

查看当前主机的连接情况

netstat命令:
-a (all)显示所有选项,默认不显示LISTEN相关
-t (tcp)仅显示tcp相关选项
-u (udp)仅显示udp相关选项
-n 拒绝显示别名,能显示数字的全部转化成数字。
-l 仅列出有在 Listen (监听) 的服務状态

-p 显示建立相关链接的程序名
-r 显示路由信息,路由表
-e 显示扩展信息,例如uid等
-s 按各个协议进行统计
-c 每隔一个固定时间,执行该netstat命令。

提示:LISTEN和LISTENING的状态只有用-a或者-l才能看到

查看I/O情况

iostat 命令
常用选项说明:

-c:只显示系统CPU统计信息,即单独输出avg-cpu结果,不包括device结果
-d:单独输出Device结果,不包括cpu结果
-k/-m:输出结果以kB/mB为单位,而不是以扇区数为单位
-x:输出更详细的io设备统计信息
interval/count:每次输出间隔时间,count表示输出次数,不带count表示循环输

查看cpu情况

cat /proc /cpuinfo

查看内存

cat /proc/meminfo
或者free命令

查看进程pid

pidof + 进程名

xargs:将标准输入转化为命令行参数

短连接与长连接问题

短连接:指的是client端与server端进行通信时,此时先三次握手,建立连接,然后client端向服务器发送请求,服务器回复,此时进行四次挥手,断开连接。早期使用短连接,拿网页来说,早期的网页数据量较小,一次可以发送完,所以采用短连接可以做到管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段http 0.9
长连接:

client向server发起连接,server接受client连接,双方建立连接。Client与server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。
首先说一下TCP/IP详解上讲到的TCP保活功能,保活功能主要为服务器应用提供,服务器应用希望知道客户主机是否崩溃,从而可以代表客户使用资源。如果客户已经消失,使得服务器上保留一个半开放的连接,而服务器又在等待来自客户端的数据,则服务器将应远等待客户端的数据,保活功能就是试图在服务器端检测到这种半开放的连接。
如果一个给定的连接在两小时内没有任何的动作,则服务器就向客户发一个探测报文段,客户主机必须处于以下4个状态之一:
1.客户主机依然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常的,服务器在两小时后将保活定时器复位。
2.客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务端将不能收到对探测的响应,并在75秒后超时。服务器总共发送10个这样的探测 ,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。
3.客户主机崩溃并已经重新启动。服务器将收到一个对其保活探测的响应,这个响应是一个复位,使得服务器终止这个连接。
4.客户机正常运行,但是服务器不可达,这种情况与2类似,TCP能发现的就是没有收到探查的响应

UDP



udp报头

UDP特点


面向数据报
应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并;
用UDP传输100个字节的数据:如果发送端调用一次sendto, 发送100个字节, 那么接收端也必须调用对应的一次recvfrom, 接收100个字节; 而不能循环调用10次recvfrom, 每次接收10个字节

UDP缓冲区

UDP没有真正意义上的 发送缓冲区. 调用sendto会直接交给内核, 由内核将数据传给网络层协议进行后续的传输动作;
UDP具有接收缓冲区. 但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果缓冲区满了, 再到达的UDP数据就会被丢弃;
UDP的socket既能读, 也能写, 这个概念叫做 全双工

常见UDP的应用层协议

TCP

首部长度与报头长度关系:首部长度至少是5,20个字节。最长为60个字节,因为四位所能表示的最大十进制数为15,所以最大字节数为15*4=60
除去20个字节,剩余的在选项中加
如何做到报头与有效载荷的分离?
先读取20个字节,先把4位首部长度拿出来,分析首部长度,拿到报头的长度。进而分离数据。
如何做到向上交付?
由目的端口号确定要交付的哪个上层协议。

TCP传输的可靠性

核心思想:
可靠性:相对可靠性(非绝对可靠性),因为在发送端,只要没有发送完,永远都会有数据需要确认应答,我们只可以通过应答来确认之前发送的数据服务器已经收到。

TCP:基于确认应答机制。

序号和确认序号同时存在的原因?

不可靠的情况?
1.乱序:通过序号来解决乱序问题
2.丢包:通过ACK+确认序号 来避免丢包问题。

序列号“tcp对每个传送数据按字节排序,每一个字节对应一个序列号。


序号+=序号仍有去重功能,因为在超时重传中有两种情况,一是ACK丢了,二是没有传送到接收端,在第一种情况下,接收端实际上是收到数据了,所以再发的话便是数据重复了,所以这时序列号就可以起到去重的功能。

TCP中的接收缓冲区和发送缓冲区


send()本质:是将用户空间的数据拷贝到发送缓冲区内。
recv()本质:是将接收缓冲区内接收到的数据。

TCP首部的16位窗口大小

流量控制

流量控制:根据16位窗口大小决定的。
接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,就会造成丢包, 继而引起丢包重传等等一系列连锁反应.
因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control);
1.接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端;
2.窗口大小字段越大, 说明网络的吞吐量越高;
3.接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;
4.发送端接受到这个窗口之后, 就会减慢自己的发送速度;
5.如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端

6个标志位


URG: 紧急指针是否有效
ACK: 确认号是否有效
PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走
RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段
SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段
FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段

16位紧急指针


超时重传机制

两种情况:
1.数据发出了,但是接受方没有收到
2.数据发出了,接受发也收到了,但是ACK丢了。
1.Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时
2.时间都是500ms的整数倍.
3.如果重发一次之后, 仍然得不到应答, 等待 2500ms 后再进行重传.
4.如果仍然得不到应答, 等待 4
500ms 进行重传. 依次类推, 以指数形式递增.
累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接

三次握手


三次握手原因
1.避免洪水效应


4,交换双方缓冲区的大小。
5,MSS协商:
TCP的一个数据报也不能无限大, 还是受制于MTU. TCP的单个数据报的最大消息长度, 称为MSS(Max Segment Size);TCP在建立连接的过程中, 通信双方会进行MSS协商

四次挥手


1.CLOSE_WAIT

如果服务端存在大量的CLOSE_WAIT说明服务端的上层没有调用close即客户端发送FIN并且收到服务端的ACK应答之后,服务端由于没有调用close,因此并不会向客户端发送FIN,即在内部会存在大量的CLOSE_WAIT状态
因此当我们检查服务器发现存在大量的CLOSE_WAIT状态时,就需要检查上层代码,是否调用了close关闭服务端的链接

2.TIME_WAIT
为什么TIME_WAIT的时间是2MSL?
MSL是TCP报文最大的生存时间,因此TIME_WAIT持续存在2MSL,就能够保证在两个传输方向上,的尚未被接收或者迟到接收的报文段都已经消失(否则服务器重启,可能会收到来自上一个进程的迟到的数据,这种数据可能是错误的)

2.等待历史数据从网络消散
比如,客户端给服务器发送:你好、再见(FIN)! 但是FIN先到达的服务端,此时服务端会进行应答,开始进行断开连接流程而TIME_WAIT状态会等待2MSL的时间(MSL数据单向最大传送时间)。由于TIME_WAIT的存在,就可以接收到遗留的信息。

3.CLOSED
只有当双方都变成CLOSED状态,才代表双方已经达成共识,这时,双方才会真正意义上的释放链接对应的资源,不再能够进行通信。
TIME_WAIT情况
当有连接的情况下,此时服务器主动退出后,没有完成四次挥手,就会出现bind error,也就是对应TIME_WAIT情况


如果服务器此时宕机的话,再启动,端口号是不能改变的,此时应该怎么办?
通过端口复用/地址复用来解决bind error的问题
在bind’前

常用抓包命令

滑动窗口

窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。
无需确认:指的是可容忍时间内,并不是真正的无需确认。可以推迟确认,但确认应答仍需要的。只不过不是发完一段立即就给应答。

滑动窗口并不是一成不变的,

流量控制

接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,就会造成丢包, 继而引起丢包重传等等一系列连锁反应 .因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control);
1.接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端;
2.窗口大小字段越大, 说明网络的吞吐量越高;
3.接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;
4.发送端接受到这个窗口之后, 就会减慢自己的发送速度;
5.如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端

窗口探测:
对方缓冲区被塞满后,返回来的窗口大小为0,因此滑动窗口大小也为0,此时TCP层会进入阻塞状态
那么什么时候,会进行更新呢?
这时,就需要自己发送报文去询问,是否有空间了
窗口更新通知:
假设窗口探测间隔为1s,但是窗口立马更新好了,此时要等待1s后再进行通信,是很浪费时间的
因此,需要有窗口更新通知,更新好后,立马通知对方

扩张延伸:
上层不关心底层通信:
流量控制是由内核进行的,上层不关心这些,上层只关心缓冲区是否还有容量可以让自己写入数据,并不会关心这些通信细节
分层的意义:
假如现在接收方窗口满了,发送方给对方发送PSH都不取走数据,这就是应用层的BUG,因为协议是分层的,每层都不关心另外一层是怎么样的。

高低水位线:
recv、send都是系统调用,在调用的时候都需要进行身份的切换,在进行切换的时候也是需要消耗时间的,而缓冲区的高低水位线就可以提高这种效率,当数据的内容低于低水位线的时候,TCP就不会去通知上层,高于水位线的时候,才会通知上层一次调用读取大量数据这种减少用户态-内核态的切换,也是效率提升的一种。

拥塞控制/拥塞窗口



慢启动设计目的:摸清当前网络拥堵状态
并不是真正的慢,在最初采用指数增长的方式,当达到某一阈值时,此时会由指数增长变为线性增长。

延迟应答机制

如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小.
假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;
但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;
在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M;
一定要记得, 窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;
那么所有的包都可以延迟应答么? 肯定也不是;
数量限制: 每隔N个包就应答一次;
时间限制: 超过最大延迟时间就应答一次;
具体的数量和超时时间, 依操作系统不同也有差异; 一般N取2, 超时时间取200ms

捎带应答

在回复ACK’时加上相应的数据。在有效载荷中。

小结

相关面试

如何理解TCP面向字节流

二、如何解决粘包问题

三、TCP异常情况

全连接队列与半连接队列


1. 半链接队列(用来保存处于SYN_SENT和SYN_RECV状态的请求)
2. 全连接队列(accpetd队列)(用来保存处于established状态,但是应用层没有调用accept取走的请求)

3. 全连接队列满了的时候, 就无法继续让当前连接的状态进入 established 状态了,就意味着三次握手没有完成。

导图


syn-table半链接队列

以上是关于Linux网络编程 传输层 TCP/UDP的主要内容,如果未能解决你的问题,请参考以下文章

Linux网络基础--传输层详解

Linux网络基础--传输层详解

Linux网络基础--传输层详解

linux网络管理之网络基础

Linux网络优化篇

Linux网络优化篇