IP/TCP/UDP报文解析(3)UDP报文

前言

本文中涉及很多的位运算,如果对位运算不太了解的请看这篇博文《Java中的位运算》

正文

UDP报文格式

UDP报文格式

  • 源端口:占16位 在UDP数据段第1、2字节(下标0、1)中,标识本地端口号。取值 0 - 65535
  • 目的端口:占16位 在UDP数据段第3、4字节(下标2、3)中,标识远程端口号。取值0 - 65535
  • 总长度:占16位 在UDP数据段第5、6字节(下标4、5)中,标识该报文段包括报头部分的所有数据字节的长度。取值0 - 65535
  • 校验和:占16位 在UDP数据段第7、8字节(下标6、7)中,该字段通过校验和计算得出,用于校验数据段是否被正确就收处理。
  • 数据:数据为可变长度。由于总长度位的最大取值为65535,所以理论上该值最大为65535 - 8 = 65523字节。

UDP校验和

UDP中的校验和不是必要的,因为即使UDP数据段校验不通过也没办法通知对端重发数据,这也就是为什么UDP可靠性不如TCP的原因。UDP是无连接无状态协议,因为它不像TCP协议一样会经过三次握手建立连接,两端都不知道各自的状态。发信方发出的UDP报文有没有完全送达,自己与对端都不知道。正因为UDP协议不需要三次握手建立连接,每次收到信息后无需发送确认信息,每次发送信息后无需等待对端确认,所以在效率上要高于TCP协议。总的来说,UDP协议的可靠性不如TCP协议,但效率要高于TCP。

校验和的计算

UDP校验和计算与TCP校验和计算是一样的过程,具体请参见《IP/TCP/UDP报文解析(2)TCP报文》这篇博文。

数据校验

UDP数据校验与TCP数据校验的过程是一样的,具体请参见 《IP/TCP/UDP报文解析(2)TCP报文》 这篇博文。

UDP数据报解析代码
public class UDPPacket extends Packet{
    private static final String TAG = UDPPacket.class.getSimpleName();

    static final int SOURCE_PORT_BIT = 0;
    static final int DESTINATION_PORT_BIT = 2;
    static final int TOTAL_LENGTH_BIT = 4;
    static final int CHECK_SUM_BIT = 6;

    private byte[] pseudoHeader;

    public UDPPacket(byte[] bytes , int... parameters){
        super(bytes,parameters);
    }

    /**
     * 获取UDP数据报源端口
     * @return
     */
    public int getLocalPort(){
        return byteToInt(bytes[SOURCE_PORT_BIT + offset],bytes[SOURCE_PORT_BIT + offset + 1]);
    }

    /**
     * 获取UDP数据报目的端口
     * @return
     */
    public int getRemotePort(){
        return byteToInt(bytes[DESTINATION_PORT_BIT + offset],bytes[DESTINATION_PORT_BIT + offset + 1]);
    }

    /**
     * 获取UDP数据报总长度
     * @return
     */
    public int getTotalLenth(){
        return byteToInt(bytes[TOTAL_LENGTH_BIT + offset],bytes[TOTAL_LENGTH_BIT + offset + 1]);
    }

    /**
     * 获取校验和
     * @return
     */
    public int getCheckSum(){
        return byteToInt(bytes[CHECK_SUM_BIT],bytes[CHECK_SUM_BIT + 1]);
    }

    /**
     * 检查校验和是否正确
     * @return
     */
    public boolean checkSum(){
        return (~getSum() & 0xFFFF) == 0;
    }

    /**
     * 获取校验和
     * @return
     */
    private int getSum(){
        int sum = 0;

        if(pseudoHeader != null){
            for(int i = 0;i < pseudoHeader.length; i += 2){
                sum += byteToInt(pseudoHeader[i],pseudoHeader[i+1]);
            }
        }

        for(int i = offset;i < validLength;i++){
            sum += byteToInt(bytes[i],bytes[i + 1]);
        }

        if((validLength - offset) % 2 > 0){
            sum += (bytes[validLength - 1] & 0xFF) << 8;
        }

        while ((sum >> 16) > 0){
            sum = (sum >> 16) + (sum & 0xFFFF);
        }

        return sum;
    }

    public void initPseudoHeader(int localIP,int remoteIP,int length){
        pseudoHeader = new byte[12];

        for(int i = 0;i < 4;i++){
            pseudoHeader[i] = (byte) (localIP >> ((3 - i) * 8));
            pseudoHeader[i + 4] = (byte) (remoteIP >> ((3 - i) * 8));
        }

        pseudoHeader[8] =  0 ;
        pseudoHeader[9] =  17 ;
        pseudoHeader[10] = (byte) (length >> 8);
        pseudoHeader[11] = (byte) length;
    }

    /**
     * 刷新校验和
     */
    public void refreshCheckSum(){
        bytes[CHECK_SUM_BIT + offset] = 0;
        bytes[CHECK_SUM_BIT + offset + 1] = 0;

        int sum = ~ getSum();

        bytes[CHECK_SUM_BIT + offset] = (byte) (sum >> 8);
        bytes[CHECK_SUM_BIT + offset + 1] = (byte) sum;
    }

    public void setLocalPort(int port){
        bytes[SOURCE_PORT_BIT + offset] = (byte) (port >> 8);
        bytes[SOURCE_PORT_BIT + offset + 1] = (byte) port;
    }

    public void setRemotePort(int port){
        bytes[DESTINATION_PORT_BIT + offset] = (byte) (port >> 8);
        bytes[DESTINATION_PORT_BIT + offset +1] = (byte) port;
    }
}

总结

以上就是我对UDP协议的理解,希望对你有所帮助。