网络原理-进阶
端⼝号
端⼝号(Port)标识了⼀个主机上进⾏通信的不同的应⽤程序。
在TCP/IP协议中, ⽤ "源IP", "源端⼝号", "⽬的IP", "⽬的端⼝号", "协议号" 这样⼀个五元组来标识⼀个通信(可以通过netstat -n查看);
端⼝号范围划分
• 0 - 1023: 知名端⼝号, HTTP, FTP, SSH等这些⼴为使⽤的应⽤层协议, 他们的端⼝号都是固定的。
• 1024 - 65535: 操作系统动态分配的端⼝号. 客⼾端程序的端⼝号, 就是由操作系统从这个范围分配的。
UDP协议

UDP的特点:
• ⽆连接: 知道对端的IP和端⼝号就直接进⾏传输, 不需要建⽴连接;
• 不可靠: 没有确认机制, 没有重传机制; 如果因为⽹络故障该段⽆法发到对⽅, UDP协议层也不会给应⽤层返回任何错误信息;
• ⾯向数据报: 不能够灵活的控制读写数据的次数和数量;
⾯向数据报:
应⽤层交给UDP多⻓的报⽂, UDP原样发送, 既不会拆分, 也不会合并;
UDP使⽤注意事项
UDP协议⾸部中有⼀个16位的最⼤⻓度. 也就是说⼀个UDP能传输的数据最⼤⻓度是
64K(包含UDP⾸部).
如果我们需要传输的数据超过64K, 就需要在应⽤层⼿动的分包, 多次发送, 并在接收端⼿动拼装;
UDP报头中有4个字段,每个字段两个字节,一共八个字节。
校验和:
在一定程度上识别数据是否改变。

MD5:


基于UDP的应⽤层协议
• NFS: ⽹络⽂件系统
• TFTP: 简单⽂件传输协议
• DHCP: 动态主机配置协议
• BOOTP: 启动协议(⽤于⽆盘设备启动)
• DNS: 域名解析协议
TCP协议
TCP全称为 "传输控制协议(Transmission Control Protocol"). ⼈如其名, 要对数据的传输进⾏⼀个详细的控制;
TCP协议段格式

• 源/⽬的端⼝号: 表⽰数据是从哪个进程来, 到哪个进程去;
• 4位TCP报头⻓度: 表⽰该TCP头部有多少个32位bit(有多少个4字节); 所以TCP头部最⼤⻓度是15 * 4 = 60
• 6位标志位:
◦ URG: 紧急指针是否有效
◦ ACK: 确认号是否有效
◦ PSH: 提⽰接收端应⽤程序⽴刻从TCP缓冲区把数据读⾛
◦ RST: 对⽅要求重新建⽴连接; 我们把携带RST标识的称为复位报⽂段
◦ SYN: 请求建⽴连接; 我们把携带SYN标识的称为同步报⽂段
◦ FIN: 通知对⽅, 本端要关闭了, 我们称携带FIN标识的为结束报⽂段
• 16位窗⼝⼤⼩: 后⾯再说
• 16位校验和: 发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含TCP⾸部, 也包含TCP数据部分.
• 16位紧急指针: 标识哪部分数据是紧急数据;
确认应答

TCP将每个字节的数据都进⾏了编号. 即为序列号:

每⼀个ACK都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下⼀次你从哪⾥开始发。



超时重传




如果应答报文丢了,发送端就会再发送一次数据,这该如何解决?



连接管理
在正常情况下, TCP要经过三次握⼿建⽴连接, 四次挥⼿断开连接
建⽴连接的意义:1. 投⽯问路, 确认当前通信路径是否畅通.2. 协商参数, 通信双⽅共同确认⼀些通信中的必备参数数值.
三次握手







四次挥手


为什么挥手是的ACK和FIN不能合并为一个数据报?
因为程序执行过程中,两个信息产生的时间不一定相同,可能相隔很长时间,那就没必要强行将两个数据报合二为一。
TCP的状态



滑动窗⼝



显然是下边的方式。
如果滑动窗口丢包了,会怎么样?


如果发送端收到2001,那么就代表1001的数据也收到了。接收端是按顺序使用数据的,如果1001之前的数据报丢了,那接收端就在下一次接收数据时,发送1001的回应,不会发出2001的ACK的应答数据报。


流量控制
接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送, 就会造成丢包, 继⽽引起丢包重传等等⼀系列连锁反应。
因此TCP⽀持根据接收端的处理能⼒, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow
Control);
• 接收端将⾃⼰可以接收的缓冲区⼤⼩放⼊ TCP ⾸部中的 "窗⼝⼤⼩" 字段, 通过ACK端通知发送端;
• 窗⼝⼤⼩字段越⼤, 说明⽹络的吞吐量越⾼;
• 接收端⼀旦发现⾃⼰的缓冲区快满了, 就会将窗⼝⼤⼩设置成⼀个更⼩的值通知给发送端;
• 发送端接受到这个窗⼝之后, 就会减慢⾃⼰的发送速度;
• 如果接收端缓冲区满了, 就会将窗⼝置为0; 这时发送⽅不再发送数据, 但是需要定期发送⼀个窗⼝探测数据段, 使接收端把窗⼝⼤⼩告诉发送端。


拥塞控制
虽然TCP有了滑动窗⼝这个⼤杀器, 能够⾼效可靠的发送⼤量的数据. 但是如果在刚开始阶段就发送⼤量的数据, 仍然可能引发问题.
因为⽹络上有很多的计算机, 可能当前的⽹络状态就已经⽐较拥堵. 在不清楚当前⽹络状态下, 贸然发送⼤量的数据, 是很有可能引起雪上加霜的.
TCP引⼊ 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的⽹络拥堵状态, 再决定按照多⼤的速度传输数据;



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

捎带应答
在延迟应答的基础上, 我们发现, 很多情况下, 客⼾端服务器在应⽤层也是 "⼀发⼀收" 的. 意味着客⼾端 给服务器说了 "How are you", 服务器也会给客⼾端回⼀个 "Fine, thank you";
那么这个时候ACK就可以搭顺⻛⻋, 和服务器回应的 "Fine, thank you" ⼀起回给客⼾端

⾯向字节流
创建⼀个TCP的socket, 同时在内核中创建⼀个 发送缓冲区 和⼀个 接收缓冲区;
• 调⽤write时, 数据会先写⼊发送缓冲区中;
• 如果发送的字节数太⻓, 会被拆分成多个TCP的数据包发出;
• 如果发送的字节数太短, 就会先在缓冲区⾥等待, 等到缓冲区⻓度差不多了, 或者其他合适的时机发 送出去;
• 接收数据的时候, 数据也是从⽹卡驱动程序到达内核的接收缓冲区;
• 然后应⽤程序可以调⽤read从接收缓冲区拿数据;
• 另⼀⽅⾯, TCP的⼀个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这⼀个连接, 既可以读数据, 也可以写数据. 这个概念叫做 全双⼯
由于缓冲区的存在, TCP程序的读和写不需要⼀⼀匹配, 例如:
• 写100个字节数据时, 可以调⽤⼀次write写100个字节, 也可以调⽤100次write, 每次写⼀个字节;
• 读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以⼀次read 100个字节, 也可以⼀ 次read⼀个字节, 重复100次;
粘包问题
• ⾸先要明确, 粘包问题中的 "包" , 是指的应⽤层的数据包.
• 在TCP的协议头中, 没有如同UDP⼀样的 "报⽂⻓度" 这样的字段, 但是有⼀个序号这样的字段.
• 站在传输层的⻆度, TCP是⼀个⼀个报⽂过来的. 按照序号排好序放在缓冲区中.
• 站在应⽤层的⻆度, 看到的只是⼀串连续的字节数据.
• 那么应⽤程序看到了这么⼀连串的字节数据, 就不知道从哪个部分开始到哪个部分, 是⼀个完整的应 ⽤层数据包.
那么如何避免粘包问题呢? 归根结底就是⼀句话, 明确两个包之间的边界.
• 对于定⻓的包, 保证每次都按固定⼤⼩读取即可; 例如上⾯的Request结构, 是固定⼤⼩的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可;
• 对于变⻓的包, 可以在包头的位置, 约定⼀个包总⻓度的字段, 从⽽就知道了包的结束位置;
• 对于变⻓的包, 还可以在包和包之间使⽤明确的分隔符(应⽤层协议, 是程序猿⾃⼰来定的, 只要保证分隔符不和正⽂冲突即可);
思考: 对于UDP协议来说, 是否也存在 "粘包问题" 呢?
• 对于UDP, 如果还没有上层交付数据, UDP的报⽂⻓度仍然在. 同时, UDP是⼀个⼀个把数据交付给应⽤层. 就有很明确的数据边界.
• 站在应⽤层的站在应⽤层的⻆度, 使⽤UDP的时候, 要么收到完整的UDP报⽂, 要么不收. 不会出
现"半个"的情况.
异常情况
• 进程终⽌: 进程终⽌会释放⽂件描述符, 仍然可以发送FIN. 和正常关闭没有什么区别.
• 机器重启: 和进程终⽌的情况相同.
• 机器掉电/⽹线断开: 接收端认为连接还在, ⼀旦接收端有写⼊操作, 接收端发现连接已经不在了, 就会进⾏reset. 即使没有写⼊操作, TCP⾃⼰也内置了⼀个保活定时器, 会定期询问对⽅是否还在. 如果对⽅不在, 也会把连接释放.
另外, 应⽤层的某些协议, 也有⼀些这样的检测机制. 例如HTTP⻓连接中, 也会定期检测对⽅的状态. 例如QQ, 在QQ断线之后, 也会定期尝试重新连接.
