TCP解析
TCP 是面向连接的,提供可靠、有序的传输并且还提供流控和拥塞控制,单独提取出 TCP 层而不是在 IP层实现是因为 IP 层有更多的设备需要使用,加了复杂的逻辑不划算。
三次握手主要是为了定义初始序列号,为之后的传输打下基础。
四次挥手是因为全双工协议,双方都得说确认。
SYN 超时了就阶梯性重试,如果有 SYN攻击,可以加大队列数,或减少重试次数,或直接拒绝。
TIME_WAIT 是怕对方没收到最后一个 ACK,然后又发了 FIN 过来,并且也是等待处理网络上残留的数据,怕影响新连接。
超时重传是为了保证对端一定能收到包。
快速重传是为了避免在偶尔丢包的时候需要等待超时这么长时间。
SACK 是为了让发送方知道重传哪些。
D-SACK 是为了让发送方知道这次重传的原因是对方真的没收到还是自己太心急了。
滑动窗口是为了平衡发送方的发送速率和接收方的接受数率,不至于瞎发,注意 Silly Window、纳格算法和延迟确认不能一起搭配。
拥塞控制,保证道路通畅。
TCP数据报结构

序号:Seq(Sequence Number)32位,标识从源端口发送到目标端口的数据包的序号,即发送数据(Data)的第一个字节的编号。范围:0~2^32-1。
确认号:Ack(Acknowledge Number)32位。期望收到对方下一个报文段的数据第一个字节的序号。表明前面的字段已经正确收到。
标志位:6位,分别为:
- URG:(Urgent)=1时紧急指针有效,有紧急数据。发送方希望接收方尽快接收数据报,高优先级,而不是按原来的排队顺序传送。
- ACK:(Acknowledge)=1时确认号Ack有效。TCP规定,连接建立后,ACK必须为1。
- PSH:(Push)=1时接收方会尽快对发送方作出响应。表示应将这个报文交给应用层,而不再等到整个缓存都填满了后再向上交付。
- RST:(Reset)=1时重置连接。TCP连接中出现差错(如主机崩溃),必须释放连接,然后重新建立。
- SYN:(synchronous)=1时建立同步连接。若ACK=0,表明这是一个连接请求;若ACK=1,表明对方同意建立连接。只有在前2次握手中SYN才为1。SYN=1时报文段不能携带数据,但要消耗掉一个序号。
- FIN:(Finish)=1时断开连接。说明数据已经发送完毕,可以释放连接。FIN=1时,要消耗掉一个序号。
源端口和目标端口:各2字节,端和端传送数据的起点和终点。
首部长度:(Header Length)4位。数据偏移。
保留:(Reserved)默认0。
窗口:(Window Size)2字节。发送方的接收窗口,窗口值(字节)表明从本报文段的确认号Ack算起,还可以接收的数据量。也表示当前接收方的接收窗口还有多少剩余空间。可用于TCP的流量控制。
校验和:(Checksum)TCP首部+TCP数据(Data)+伪首部(12字节=源IP地址4+目标IP地址4+1+协议号1+TCP长度2)。用于确认传输的数据是否有损坏。
紧急指针:(Urgent Pointer)本报文段中紧急数据的字节数(紧急数据结束后才是普通数据)。URG=1时有效。窗口为0也可以发送带有紧急数据的报文段、零窗口探测报文段、确认报文段。
选项:(Option)长度可变,但必须是32位的整数倍。最多可达40字节,即TCP首部长度范围:20~60字节。
tcp建立连接
三次握手。

两次握手只能保证单向连接是通畅的。
三次握手建立可靠的通信通道,保证两端同时具备发送、接收数据的能力。可以防止已失效的请求报文又传送到了服务端,建立了多余的链接,浪费资源。
四次握手没必要,效率较低。
tcp断开连接
四次挥手。

四次挥手确保客户端和服务端的数据能够完成传输。ACK是收到FIN之后立刻由内核返回的数据报,FIN是应用程序处理完接收缓存的数据之后,调用close方法触发的。
三次挥手通常情况下不行,中间两次操作的时机不一样。
2MSL(最长报文段寿命),确保ACK报文段能够到达服务端,从而使服务端正常关闭连接。
- 等待ACK经历一个最大时间到达服务器。
- 万一ACK报文段丢了,再等待一个最大时间,服务器重传FIN到达客户端。
拥塞控制

慢开始--》拥塞避免--》快重传--》快恢复
模拟开发
本地开发TCP功能时,可以用SocketTool 模拟TCP收发。

LogHelper.Instance().Register();
private static async void TestTcp()
{
var tcp = new TcpClient();
tcp.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);//启用KeepAlive机制
tcp.Connect("127.0.0.1", 60000);
int msgLength = 98;
_index = 0;
while (true)
{
await ReadMsgAsync(tcp, msgLength);
await Task.Delay(100);
}
}
public static async Task<byte[]> ReadMsgAsync(TcpClient tcp, int msgLength)
{
byte[] buffer = new byte[msgLength];
int timeout = 1000;
int read = 0, sum = 0, offset = 0;
Stopwatch sw = Stopwatch.StartNew();
// 通过循环读取确保获取所有数据
while (sum < msgLength)
{
read = await tcp.GetStream().ReadAsync(buffer, offset, msgLength - sum);
if (read == 0) break;
sum += read;
offset += read;
}
_index++;
int available = tcp.Available;
if (available > msgLength * 2)
{
var count = available / msgLength;
while (count > 0)
{
read += tcp.GetStream().Read(buffer, 0, msgLength);
count--;
}
LogHelper.Error($"ReadMsgAsync redundancy!! {read}");
}
sw.Stop();
var cost = sw.ElapsedMilliseconds;
LogHelper.Error($"{_index} msgLength={read}(offset={offset}),cost={cost}ms, content=({WSCommFunc.Byte2HexStringX(buffer)})");
if (cost > timeout)
{
LogHelper.Error($"ReadMsgAsync timeout!! {cost}ms");
}
return buffer;
}
串口开发可以参考:串口模拟开发

浙公网安备 33010602011771号