Linux网络编程_01_网络基础
Linux网络编程_02_socket套接字
Linux网络编程_03_应用层HTTP协议
Linux网络编程_04_传输层UDP和TCP协议
Linux网络编程_05_网络层IP协议
Linux网络编程_06_数据链路层MAC帧协议
Linux网络编程_07_多路转接
端口号(Port)标识了一个主机上进行通信的不同的应用程序,在TCP/IP协议中,用 “源IP”,“源端口号”,“目的IP”,“目的端口号”,“协议号” 这样一个五元组来标识一个通信
- 0-1023: 知名端口号,ssh服务器(22),ftp服务器(21),telnet服务器(23),http服务器(80),https服务器(443)等等,这些都是固定的
- 1024-65535: 操作系统动态分配的端口号, 比如客户端程序的端口号,就是由操作系统从这个范围分配的
常用选项
- n 拒绝显示别名,能显示数字的全部转化成数字
- l 仅列出有在 Listen (监听) 的服务状态
- p 显示建立相关链接的程序名
- t (tcp)仅显示tcp相关选项
- u (udp)仅显示udp相关选项
- a (all)显示所有选项,默认不显示LISTEN相关
常用命令
# 举例
netstat -nlpt # 查看TCP类型的端口
查看对应名字的进程的pid
pidof a.out # 查看a.out这个进程的pid
- 16位源端口号: 在需要回信是会用到
- 16位目的端口号: 交付时需要用到
- 16位UDP长度: 整个UDP数据报的长度,其最小值是8字节,就是仅有首部没有数据的时候,最大值位64K
- 16位校验和: 检测UDP用户数据报在传输中是否有错,有错就丢弃
- 解包: 因为UDP数据包的首部是8个字节定长的,所以解包的时候可以根据这个来分开首部和数据(有效载荷)部分。
- 分用: 根据16位的目的端口号
- 无连接: 不需要建立连接,知道对端的IP和端口号就直接进行传输不需要建立连接
- 不可靠: 没有确认机制,没有重传机制,如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息
- 面向数据报: 应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并,不能够灵活的控制读写数据的次数和数量
- UDP没有真正意义上的 发送缓冲区,调用sendto会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作
- UDP具有接收缓冲区,但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致。如果缓冲区满了, 再到达的UDP数据就会被丢弃
- NFS: 网络文件系统
- TFTP: 简单文件传输协议
- DHCP: 动态主机配置协议
- BOOTP: 启动协议(用于无盘设备启动)
- DNS: 域名解析协议
16位源端口号: 在需要回信是会用到
16位目的端口号: 交付时需要用到
32位序列号: TCP将每个字节的数据都进行了标号,发送的时候是按照这个标号的顺序发送的
32位确认序号: 发送ACK确认时会带上确认序号信息,表面这个序号之前的数据都收到了
4位首部长度: 表示该TCP首部有多少个(4字节),所以首部的最大长度是15*4=60字节
6位标志位:
URG: 紧急指针是否有效
ACK: 确认序列号是否有效
PSH: 提示接收端尽快从TCP缓冲区把数据读走
RST: 要求对方重新建立连接,我们把携带RST标识的称为复位报文段
SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段
FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段
16位窗口大小: 表示接收缓冲区的大小,谁发的就表示谁的接收缓冲区大小
16位校验和: 发送端填充,CRC校验,接收端校验不通过,则认为数据有问题
16位紧急指针: 标识哪部分数据是紧急数据
选项: 暂时忽略
解包: 根据4位首部字节,算出TCP头部的长度,从而将首部和数据(有效载荷分开)
分用: 根据16位目的端口号向上交付给对应的进程
TCP接收到另一端的数据后,会在历史接到连续的最大序列号+1填入确认序号返回给对端,告诉对方这个确认序列号之前的数据我都收到了,下次发送请从这个序号开始发。这叫确认应答(ACK)机制,可以保证数据有序地发送给对方。
TCP发送一份数据,如果在特定时间没有收到对方的确认应答,会重发这份数据。 Linux中超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。如果重发一次之后, 仍然得不到应答, 等待 2x500ms 后再进行重传。仍然得不到应答, 等待 4x500ms 进行重传。 依次类推, 以500ms整数倍指数的形式递增,累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接。
如图所示,TCP协议是自带发送和接收缓冲区的,我们平时在应用层调用write/send系统接口时,其实是将数据放到发送缓冲区,具体什么时候发,发多少,出错了怎么办由TCP协议处理。数据发送到对方的缓冲区后,对端应用层调用read/recv系统调用接口拿到数据。因为缓冲区的存在,可以做到应用层和传输层进行解耦。
如图滑动窗口是发送缓冲区的一部分,里面存放的是已经发送的没有被确认的数据。滑动窗口是分很多段的,如下图分为四个段,每段可以容纳100个字节,这个滑动窗口的大小是400字节。滑动窗口的大小是会变的,会根据对面的接收缓冲区剩余空间来调整的。在滑动窗口里面的数据可以不用等前面一段的确认应答就可以发了,比如1-100这个数据段发了,后面的101-200可以不用等应答直接发,后面的201-300和301-400也是一样。到了401-500就需要等待前面的确认应答,然后窗口右移,将401-500这一段包含在滑动窗口的时候就可以发送了。这个发送缓冲区实际上是环形的,已经发送且确认应答的数据后面会被直接覆盖(删去)。
确认应答是根据收到的序号来发的,如下图的201这个ACK被发出,说明前面101这个ACK一定发出了。所以如果是部分ACK丢包是可以的,比如101这个ACK丢包了,但是后面收到了201的ACK,那就证明1~100的数据已经收到。
如图所示,101-200这个数据段丢包了,主机B就会重复发送101这个ACK,当主机A连续3次接收到101这样的ACK之后,就会将101-200这个数据段重新发送。主机B接收到101-200这个数据段之后,就会将已经收到连续的最大数据段,图中的401-500的确认应答发给主机A。这种机制被称为“高速重发控制”,也叫“快重传”。
如图所示,TCP要经过三次握手建立连接,然后通信,最后四次挥手断开连接
TCP协议规定,主动关闭连接的一方要处于TIME_ WAIT状态,等待两个MSL(maximum segment lifetime,TCP报文的最大生存时间)的时间后才能回到CLOSED状态。 比如说有客户端连接上绑定8080端口的服务端,然后终止服务端,虽然服务端的程序终止了,但TCP协议层的连接并没有完全断开,此时开启服务端,会出现bind error,不能立即再次监听这个8080号端口,需要等两个MSL时间过去。
TIME_WAIT是两个MSL的原因
- 就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失
- 保证最后一个ACK报文可靠到达
关闭地址复用功能
// 在创建套接字之后,后面带上下面的代码,这样服务器主动断开后也可以马上绑定这个端口
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
如果端口连接后服务器还处于CLOSE_WAIT状态,说明断开时,服务器没有close通信的文件描述符fd,导致四次挥手没有正确完成,我们写程序时要记得关闭fd。
- [CLOSED -> LISTEN] 服务器端调用listen后进入LISTEN状态,等待客户端连接
- [LISTEN -> SYN_RCVD] 一旦监听到连接请求(同步报文段),就将该连接放入内核等待队列中,并向客户端发送SYN确认报文
- [SYN_RCVD -> ESTABLISHED] 服务端一旦收到客户端的确认报文,就进入ESTABLISHED状态,可以进行读写数据了
- [ESTABLISHED -> CLOSE_WAIT] 当客户端主动关闭连接(调用 close), 服务器会收到结束报文段, 服务器返回确认报文段并进入CLOSE_WAIT
- [CLOSE_WAIT -> LAST_ACK] 进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据)。当服务器真正调用close关闭连接时,会向客户端发送FIN, 此时服务器进入LAST_ACK状态,等待最后一个ACK到来
- [LAST_ACK -> CLOSED] 服务器收到了对FIN的ACK,彻底关闭连接
[CLOSED -> SYN_SENT] 客户端调用connect, 发送同步报文段
[SYN_SENT -> ESTABLISHED] connect调用成功,则进入ESTABLISHED状态, 开始读写数据
[ESTABLISHED -> FIN_WAIT_1] 客户端主动调用close时, 向服务器发送结束报文段,同时进入FIN_WAIT_1
[FIN_WAIT_1 -> FIN_WAIT_2] 客户端收到服务器对结束报文段的确认,则进入FIN_WAIT_2(半关闭状态),开始等待服务器的结束报文段
[FIN_WAIT_2 -> TIME_WAIT] 客户端收到服务器发来的结束报文段,进入TIME_WAIT,并发出LAST_ACK
[TIME_WAIT -> CLOSED] 客户端要等待一个2MSL(Max Segment Life, 报文最大生存时间)的时间,才会进入CLOSED状态
根据接收端的处理能力, 来决定发送端的发送速度,这个机制就叫做流量控制(Flow Control)。接收端可以把自己接收缓冲区的大小填入TCP首部的16位窗口大小里面,通过ACK通知发送方。发送端会根据这个窗口大小来控制自己的发送速度,如果窗口大小为0就停止发送,但是需要定期发一个窗口探测数据段,让接收段把窗口大小告诉发送端。注意:实际上TCP首部40字节选项中还包含了一个窗口扩大因子M, 实际窗口大小是窗口字段的值左移M位。
因为网络上有很多计算机,刚刚建立TCP连接时,不确定当前的网络状态,如果贸然发送大量数据可能会引起网络拥塞。TCP为了防止这种现象,引入了慢启动机制,先发送少量的数据,先摸清网络的情况,再决定发送的速度。这里的发送速度是用拥塞窗口表示的,建立连接一开始发送时,窗口的大小为1,单位不是字节,而是数据段。如下图所示,一开始为1,后面按指数增长,因为指数增长一开始的时候是相对慢的,所以叫慢启动。为了使后面增长没有那么快,会有一个慢启动的阈值ssthresh,初始值为16,超过这个阈值后就按照线性方式增长。在遇到网络拥塞时,慢启动阈值会变成拥塞值的一半,然后拥塞窗口重新从1开始。滑动窗口的大小实际上是对于拥塞窗口和16位窗口大小两者的最小值。
接收端接收到数据后,可以延迟一段时间,这段时间应用层可能就从接收缓冲区取走数据,接收缓冲区的空间就多了,这样确认应答的16位窗口大小就可以大一点了,窗口越大,传输效率也就越高。可以每隔一个包应答一次,或者超过最大延迟时间就应答一次。
在确认应答时,顺便可以捎带上想发给对方的数据。
调用write之后数据会先写入发送缓冲区,如果发送的字节数太长,就会被拆分成很多个TCP数据包发出。如果发送的字节数太短,就会拼接后再发出去。由于缓冲区的存在,TCP程序的读和写不需要一一匹配,比如说发送100字节数据,可以调用write写100字节,也可以调用100次write写1字节,读的时候也是不用考虑写是怎么写的。
这里的包指的是指的应用层的数据包,在TCP的协议头中,没有如同UDP一样的 “报文长度” 这样的字段,但是有一个序号这样的字段。站在传输层的角度,TCP是一个一个报文过来的, 按照序号排好序放在缓冲区中。站在应用层的角度,看到的只是一串连续的字节数据。那么应用程序看到了这么一连串的字节数据,就不知道从哪个部分开始到哪个部分,是一个完整的应用层数据包,所以TCP是有粘包问题的。对于UDP,如果还没有上层交付数据, UDP的报文长度仍然在,UDP是一个一个把数据交付给应用层,就有很明确的数据边界。站在应用层的站在应用层的角度,使用UDP的时候,要么收到完整的UDP报文,要么不收,所以UDP是不存在粘包问题的。
对于定长的包,保证每次都按固定大小读取即可。对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置。还可以在包和包之间使用明确的分隔符,要保证分隔符不和正文冲突即可。
- 进程终止: 进程终止会释放文件描述符,仍然可以发送FIN, 和正常关闭没有什么区别
- 机器重启: 和进程终止的情况相同
- 机器掉电/网线断开: 接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行rese。即使没有写入操作, TCP自己也内置了一个保活定时器,会定期询问对方是否还在,如果对方不在,也会把连接释放。应用层的某些协议,也有一些这样的检测机制。 例如HTTP长连接中,也会定期检测对方的状态。
- 校验和
- 序列号,确认序号(按序到达)
- 确认应答
- 超时重发
- 连接管理
- 流量控制
- 拥塞控制
- 滑动窗口
- 快速重传
- 延迟应答
- 捎带应答
- 超时重传定时器
- 保活定时器
- TIME_WAIT定时器
- HTTP: 超文本传输协议(Hyper Text Transfer Protocol)
- HTTPS: 超文本传输安全协议(Hyper Text Transfer Protocol Secure)
- SSH: 安全shell协议(Secure Shell)
- Telnet: 基于TCP的传统命令行远程控制协议
- FTP: 文件传输协议(File Transfer Protocol)
- SMTP: 简单邮件传输协议(Simple Mail Transfer Protocal)
TCP用于可靠传输的情况,应用于文件传输,重要状态更新等场景。UDP用于对高速传输和实时性要求较高的通信领域,例如, 视频、广播等。
下一章节点击我直达 --> Linux网络编程_05_网络层IP协议