TCP首部格式
TCP的首部相对于UDP的首部还是相对复杂的,下面我们依次分析下每一个字段代表的意思。
- 源端口(Source Port)
表示发送端端口号,占16位。
- 目标端口(Destination Port)
表示接收端端口号,占16位。
- 序列号(Sequence Number)
占32位。指发送数据的位置,每发送一次数据,就累加一次该数据字节数的大小。
- 确认号(Acknowledgement Number)
占32位。确认应答号的值为接收到的数据包的序列号的值+1,表示下一个发送的数据包占所有数据编号的位置。
数据偏移
表示TCP所传输的数据部分应该从TCP包的那个位置开始计算,也可以看成TCP首部的长度。该字段占4位,单位为4字节。但上图所示TCP的首部为20字节长,因此TCP的首部中有长度不确定的字段。保留
该字段主要是为了以后扩展的时候使用,上图显示占6位。一般都设置为0,即使收到的包在该字段不为0,此包也不会被丢弃。其实这六位中的后两位分别为CWR和ECE。
CWR:CWR和后面的ECE都用于IP首部的ECN字段。ECE标志为1时,则通知对方已将拥塞窗口缩小。就是之前提到的TCP可靠传输技术的一种——拥塞控制。
ECE:为1,通知通信对方,从对方到这边的网络有拥塞。
- URG
为1,表示包中有需要紧急处理的数据。对于需要紧急处理的数据,会在后面的紧急指针中再进行解释。
- ACK
为1,确认应答字段有效。TCP规定除了最初建立连接的SYN包之外必须设置为1
- PSH
为1,表示需要将收到的数据立刻传给上层应用协议。为0,不需要立即传而是先进行缓存。
- RST
为1,表示TCP连接中出现异常必须强制断开连接。(未被使用的端口发来连接请求、切断电源、死机等情况)
- SYN
建立连接。为1,表示希望建立连接,并在序列号的字段进行序列号初始值的设定。
- FIN
为1,表示希望断开连接。
- 窗口大小
用于通知从相同TCP首部的确认应答号所指位置开始能够接受的数据大小(8字节)。
- 校验和
TCP的校验和
检验和目的
为了发现TCP首部和数据在发送端到接收端之间发生的任何改动。如果接收方检测到检验和有差错,TCP段会被直接丢弃。
TCP在计算校验和时,要加上一个12字节的伪首部。
检验和计算过程
TCP首部校验和 = TCP首部 + TCP数据 + TCP伪首部
- 将TCP报文中的检验和字段置为0
- 将伪首部 首部 数据部分分为16位依次相加,如果长度为奇数则在后面补0
- 将计算的值取反得到校验和
下面是TCP数据包的代码解析:
public class TCPacket {
private final static int SOURCEPORT_BIT = 0;
private final static int DESTINATIONPORT_BIT = 2;//占2字节
private final static int SEQUENCE_BIT = 4;//序列号,占4字节
private final static int ACKNOWLEDGEMENT_BIT = 8;//应答号
private final static int HEADER_LENGTH_BIT = 12;//数据偏移 占4位
private final static int TAG_BIT = 13;
private final static int WINDOW_SIZE_BIT = 14;//窗口大小
private final static int CHECK_SUM_BIT = 16;//占两个字节
private final static int URGENT_POINTER_BIT = 18;//紧急指针,当URG为1时这里会进行描述
public byte[] m_Data;
public int m_Offset;
public TCPacket(byte[] data,int offset){
this.m_Data = data;
this.m_Offset = offset;
}
//获取源端口
public int getSourcePort(){
return Packet.readShort(m_Data,m_Offset + SOURCEPORT_BIT);
}
//获取目的端口
public int getDestinationPort(){
return Packet.readShort(m_Data,m_Offset + DESTINATIONPORT_BIT);
}
public int getSequenceNumber(){
return Packet.readInt(m_Data,m_Offset + SEQUENCE_BIT);
}
public int getAcknowledgementNumber(){
return Packet.readInt(m_Data,m_Offset + ACKNOWLEDGEMENT_BIT);
}
//获取TCP头部的长度 占4字节
public int getHeaderLength(){
return ((m_Data[m_Offset + HEADER_LENGTH_BIT] & 0xFF) >> 4) * 4;
}
//获取第13个字节的后六位
public int getTag(){
return m_Data[m_Offset + TAG_BIT] & 0x2F;
}
public boolean isURG(){
return (getTag() >> 5) == 1;
}
public boolean isACK(){
return ((getTag() >> 4) & 1) == 1;
}
public boolean isPSH(){
return ((getTag() >>3) & 1) == 1;
}
public boolean isRST(){
return ((getTag() >>2) & 1) == 1;
}
public boolean isSYN(){
return ((getTag() >>1) & 1) == 1;
}
public boolean isFIN(){
return (getTag() & 1) == 1;
}
public int getWindowSize(){
return Packet.readShort(m_Data,m_Offset + WINDOW_SIZE_BIT);
}
public short getCheckSum(){
return Packet.readShort(m_Data,m_Offset + CHECK_SUM_BIT);
}
public void setCheckSum(short value){
Packet.writeInt(m_Data,m_Offset + CHECK_SUM_BIT,value);
}
//当ARG=1时,使用此方法
public int getUrgentPointer(){
return Packet.readShort(m_Data,m_Offset + URGENT_POINTER_BIT);
}
}
计算校验和的几个方法:
//计算TCP的校验和 TCP伪首部+TCP首部+TCP数据
public static boolean computeTCPChecksum(IPacket iPacket,TCPacket tcPacket){
//TCP首部+TCP数据 = IP整体长度 - IP首部
int tcp_length = iPacket.getTotalLength() - iPacket.getHeaderLength();
if(tcp_length < 0)
return false;
//计算 TCP伪首部 = 源IP地址 + 目标IP地址 + 协议号 + TCP包长度
long sum = getPseudoHeadLength(iPacket,tcp_length);
short oldChecksum = tcPacket.getCheckSum();
tcPacket.setCheckSum((short) 0);//将校验和置0
short newChecksum = checksum(sum,tcPacket.m_Data,tcPacket.m_Offset,tcp_length);
tcPacket.setCheckSum(newChecksum);
return oldChecksum == newChecksum
}
//计算伪首部长度
public static long getPseudoHeadLength(IPacket iPacket,int len){
byte[] buf = iPacket.m_Data;
int offset = iPacket.m_Offset + IPacket.SOURCE_IP_BIT;
long sum = 0;
//ip包中地址占8字节 计算地址
int address = 8;
while (address > 1){
sum += readShort(buf,offset) & 0xFFFF;
offset += 2;
address -= 2;
}
if(address > 1){//可能会有剩余的字节
sum += (buf[offset] & 0xFF) << 8;
}
//在此基础上计算协议号
sum += iPacket.getProtocol() & 0xFF;
//在此基础上计算TCP包长度
sum += len;
return sum;
}
//计算校验和
public static short checksum(long sum,byte[] buf,int offset,int len){
while (len > 1){
sum += readShort(buf,offset) & 0xFFFF;
offset += 2;
len -= 2;
}
if(len > 0){
sum += (buf[offset] & 0xFF) << 8;
}
while((sum >> 16) > 0){
sum = (sum & 0xFFFF) + (sum >> 16);
}
return (short) ~sum;
}
在写项目的过程中会继续完善,有什么问题请大家指出。