udp模仿tcp类似于socket实现tcp的功能(单方向),比如三次握手,四次挥手,超时重传快速重传等等。
模拟TCP的功能
三次握手
四次挥手
发送端要带有计时器,要实现RTT估计和RTO(重传计时器)估计。RTT如下
SampleRTT某报文段被发出到对该报文段的确认被收到之间的时间量
EstimatedRTT=(1-α)* EstimatedRTT+α*SampleRTT(α=0.125?)
DevRTT=(1-β)DevRTT+β|SampleRTT-EstimatedRTT| (β=0.25?)
TimeoutInterval = EstimatedRTT + 4 *DevRTT(初始值:EstimatedRTT = 500 milliseconds, DevRTT = 250 milliseconds)
TimeoutInterval = EstimatedRTT + gamma * DevRTT
可靠数据传输:发送端维护最早未被确认的字节序号(sendbase), 下一个要发送的字节的序号(nextSeqNum), 通过单一计时器来实现超时重传,通过冗余ACK来实现快速重传。
流量控制:有一个在任何时刻能保持的最大的未确认的 《字节数》(最大窗口数),包能够有的最多字节数。
因为是在环回网络上测试,所以要模拟数据包出现TCP中的意外–丢失,延迟,损坏,乱序
接收端收到后立刻返回ACK。不对ACK包进行操作
设计参考《计算机网络自顶向下方法》
设计思路之包头设计
三次握手需要syn,ack两个标志位,四次挥手需要fin的标志位,校验错误需要crc码,传输数据需要ackNo码跟seqNo码。因为java中datagramSocket.receive收到的packet大小是固定的。比如
byte[] receBuf = new byte[1024]; DatagramPacket datagramPacket = new DatagramPacket(receBuf, receBuf.length);
则无论实际数据是多少datagramPacket.getData().length()都会是1024, 所以java还需要一个len来记录一个包中数据的长度(udp不会粘包)。tcp粘包,udp不会原因
而python没发现这种情况,所以不需要也可以。
所以包头设计为
//fin:四次挥手用,表示要断开连接 //syn:三次握手用,表示要连接接收端 //ack:数据传输用,表示收到包了 //crc_code: 校验用 //len:包体长度 //seqNo:序号(具体意思百度一下TCP, seq_num就有) //ackNo:确认号(同上) private int fin, syn, ack, seqNo, ackNo, crc_code, len;
因为发送时是发送字节数组。所以包头需要一个将Header转换为byte[] 的方法。通过位操作将3个标志位放在1个字节中。其他各为4个字节python使用pack,java使用ByteBuffer
public static byte[] getIntBytes(int data) { ByteBuffer buffer = ByteBuffer.allocate(4); buffer.order(ByteOrder.BIG_ENDIAN); buffer.putInt(data); byte[] bytes = buffer.array(); return bytes; } public static int getInt(byte[] bytes) { ByteBuffer buffer = ByteBuffer.allocate(bytes.length); buffer.order(ByteOrder.BIG_ENDIAN); buffer.put(bytes); int result = buffer.getInt(0); return result; }
设计思路之状态
两边都需要使用状态值来记录当前处于哪种状态。因为收到ack=1的的确认包,那是处于接收数据状态还是四次挥手状态。所以有如下状态:
class States(Enum):
CLOSE = 0
LAST_ACK = 1
SYN_RCVD = 2
SYN_SENT = 3
CLOSE_WAIT = 4
ESTABLISHED = 5
FIN_WAIT = 6
发送端发送第一次握手后进入SYN_SENT, 接收端接收后进入SYN_RCVD, 回复第二次握手, 发送端接收后回复第三次握手,进入ESTABLISHED, 准备发送数据。接收端接收后进入ESTABLISHED等待数据。
快速重传
收到乱序包时,回复等待接收的正确顺序的那个ACK包。发送端收到3个同样的冗余ACK后就可以重传了。因此发送端需要维护一个缓冲区。里面存着已发送但还未确认的包。该缓冲区大小 小于最大窗口数。
接受端
接收数据,要区分一下情况:
包丢失,即包的seq_num比last_ack_num大(last_ack_num:最后一个收到并确认的数据包的seq_num)
包重复,丢弃即可
包损坏,丢弃,当作没收到
包延时:先存后面的包,使用快速重传,回复冗余ACK
包乱序:同包延时处理