欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java語言獲取TCP流的實(shí)現(xiàn)步驟

 更新時(shí)間:2023年11月29日 08:30:42   作者:半夏之沫  
使用Wireshark分析網(wǎng)絡(luò)包時(shí),一個(gè)很常用的功能就是選中一個(gè)TCP報(bào)文,然后查看這個(gè)TCP報(bào)文的TCP流,從而可以進(jìn)一步分析建連是否慢了,斷連是否正常等情況,那么本文就TCP流的概念以及在Java中如何獲取,做一個(gè)簡單的學(xué)習(xí),需要的朋友可以參考下

正文

一. TCP流概念

如果去搜索引擎搜索:什么是TCP流,那么大概率是很難得到一個(gè)有效的答案的,而在Wireshark中,選中一個(gè)TCP報(bào)文并單擊右鍵時(shí),在菜單的追蹤流中可以選擇到TCP流這個(gè)功能,如下所示。

當(dāng)點(diǎn)擊TCP流后,Wireshark會把選中的TCP報(bào)文對應(yīng)的TCP連接的所有TCP報(bào)文過濾出來并順序展示,那么這里就知道了,TCP流就是一次TCP連接中,從連接建立,到數(shù)據(jù)傳輸,再到連接斷開整個(gè)過程中的TCP報(bào)文集合。

那么Wireshark憑什么可以從那么多TCP報(bào)文中,精確的把某一條TCP連接的TCP報(bào)文過濾出來并順序展示呢,其實(shí)就是基于TCP報(bào)文的序列號和確認(rèn)號。下面是TCP報(bào)文頭的格式。

可以看到每個(gè)TCP報(bào)文都有一個(gè)序列號SeqNum和確認(rèn)號AckNum,并且他們的含義如下。

  • 序列號:表示本次傳輸?shù)臄?shù)據(jù)的起始字節(jié)在整個(gè)TCP連接傳輸?shù)淖止?jié)流中的編號。舉個(gè)例子,某個(gè)TCP報(bào)文的SeqNum為500,然后報(bào)文長度length為100,則表示本次傳輸數(shù)據(jù)的起始字節(jié)在整個(gè)TCP流中的序列號為100,并且本次傳輸?shù)臄?shù)據(jù)的序列號范圍是500到599,根據(jù)序列號,能夠?qū)鬏數(shù)臄?shù)據(jù)有序的排列組合起來,以解決網(wǎng)絡(luò)傳輸中的數(shù)據(jù)亂序問題;
  • 確認(rèn)號:用來告訴對端本端期望下一次收到的數(shù)據(jù)的序列號,換言之,告訴對端本端已經(jīng)正常接收了序列號等于AckNum之前的所有數(shù)據(jù),根據(jù)確認(rèn)號,可以解決網(wǎng)絡(luò)傳輸中的數(shù)據(jù)丟包問題。

那么序列號和確認(rèn)號的變化有什么規(guī)則呢,規(guī)則總結(jié)如下。

  • 本次發(fā)送報(bào)文的SeqNum等于上一次發(fā)送報(bào)文的SeqNum加上上一次發(fā)送報(bào)文的length;
  • 本次發(fā)送報(bào)文的AckNum等于上一次接收報(bào)文的SeqNum加上上一次接收報(bào)文的length
  • SYN報(bào)文和FIN報(bào)文的length默認(rèn)為1,而不是0。

結(jié)合下面一張圖,可以更好的理解上面的變化規(guī)則。

二. TCP流獲取的Java實(shí)現(xiàn)

結(jié)合第一節(jié)的內(nèi)容,想要獲取某一個(gè)TCP報(bào)文所屬TCP連接的TCP流,其實(shí)就可以根據(jù)這個(gè)報(bào)文的SeqNumAckNum,向前和向后查找符合序列號和確認(rèn)號變化規(guī)則的報(bào)文,只要符合規(guī)則,那么這個(gè)報(bào)文就是屬于TCP流的。

Java語言中,要實(shí)現(xiàn)TCP流的獲取,可以先借助io.pkts工具把網(wǎng)絡(luò)包先解開,然后把每個(gè)報(bào)文封裝為我們自定義的Entityio.pkts工具包解開后的報(bào)文對象不太易用),最后就是根據(jù)序列號和確認(rèn)號的變化規(guī)則,來得到某一個(gè)報(bào)文所屬的TCP流。

現(xiàn)在進(jìn)行實(shí)操,先引入io.pkts工具的依賴,如下所示。

<dependency>
    <groupId>io.pkts</groupId>
    <artifactId>pkts-streams</artifactId>
    <version>3.0.10</version>
</dependency>
<dependency>
    <groupId>io.pkts</groupId>
    <artifactId>pkts-core</artifactId>
    <version>3.0.10</version>
</dependency>

同時(shí)自定義一個(gè)TCP報(bào)文的Entity,如下所示。

/**
 * TCP報(bào)文Entity。
 */
@Getter
@Setter
@AllArgsConstructor
public class TcpPackage {

    /**
     * 源地址IP。
     */
    private String sourceIp;
    /**
     * 源地址端口。
     */
    private int sourcePort;
    /**
     * 目的地址IP。
     */
    private String destinationIp;
    /**
     * 目的地址端口。
     */
    private int destinationPort;
    /**
     * 報(bào)文載荷長度。
     */
    private int length;
    /**
     * ACK報(bào)文標(biāo)識。
     */
    private boolean ack;
    /**
     * FIN報(bào)文標(biāo)識,
     */
    private boolean fin;
    /**
     * SYN報(bào)文標(biāo)識。
     */
    private boolean syn;
    /**
     * RST報(bào)文標(biāo)識。
     */
    private boolean rst;
    /**
     * 序列號。
     */
    private long seqNum;
    /**
     * 確認(rèn)號。
     */
    private long ackNum;
    /**
     * 報(bào)文到達(dá)時(shí)間戳。
     */
    private long arriveTimestamp;
    /**
     * 報(bào)文體。
     */
    private String body;

}

現(xiàn)在假設(shè)已經(jīng)拿到了網(wǎng)絡(luò)包對應(yīng)的MultipartFile,下面給出基于io.pkts工具解析網(wǎng)絡(luò)包的實(shí)現(xiàn),如下所示。

public static List<TcpPackage> parseTcpPackagesFromFile(MultipartFile multipartFile) {
    List<TcpPackage> tcpPackages = new ArrayList<>();
    try (InputStream inputStream = multipartFile.getInputStream()) {
        GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream);
        Pcap pcap = Pcap.openStream(gzipInputStream);
        pcap.loop(packet -> {
            if (packet.hasProtocol(Protocol.TCP)) {
                TCPPacket tcpPacket = (TCPPacket) packet.getPacket(Protocol.TCP);
                tcpPackages.add(convertTcpPacket2TcpPackage(tcpPacket));
            }
            return true;
        });
        return tcpPackages;
    } catch (Exception e) {
        String message = "從網(wǎng)絡(luò)包解析TCP報(bào)文失敗";
        log.error(message);
        throw new RuntimeException(message, e);
    }
}

上述實(shí)現(xiàn)中,假定網(wǎng)絡(luò)包是gzip的壓縮格式,所以使用了GZIPInputStream來包裝網(wǎng)絡(luò)包文件的輸入流,同時(shí)因?yàn)槲覀円@取的是TCP流,所以我們只處理有TCP協(xié)議的報(bào)文,并會在convertTcpPacket2TcpPackage() 方法中完成到TcpPackage結(jié)構(gòu)的轉(zhuǎn)換,convertTcpPacket2TcpPackage() 方法實(shí)現(xiàn)如下所示。

public static TcpPackage convertTcpPacket2TcpPackage(TCPPacket tcpPacket) {
    // 報(bào)文長度=IP報(bào)文長度-IP報(bào)文頭長度-TCP報(bào)文頭長度
    IPPacket ipPacket = tcpPacket.getParentPacket();
    int length = ipPacket.getTotalIPLength() - ipPacket.getHeaderLength() - tcpPacket.getHeaderLength();
    Buffer bodyBuffer = tcpPacket.getPayload();
    String body = ObjectUtils.isNotEmpty(bodyBuffer)
            ? bodyBuffer.toString() : StringUtils.EMPTY;
    long arriveTimestamp = tcpPacket.getArrivalTime() / 1000;
    return new TcpPackage(ipPacket.getSourceIP(), tcpPacket.getSourcePort(), ipPacket.getDestinationIP(), tcpPacket.getDestinationPort(),
            length, tcpPacket.isACK(), tcpPacket.isFIN(), tcpPacket.isSYN(), tcpPacket.isRST(), tcpPacket.getSequenceNumber(),
            tcpPacket.getAcknowledgementNumber(), arriveTimestamp, body);
}

上述方法需要注意的一點(diǎn)就是TCP報(bào)文載荷長度的獲取,我們能夠拿到的數(shù)據(jù)是IP報(bào)文長度,IP報(bào)文頭長度和TCP報(bào)文頭長度,所以IP報(bào)文長度減去IP報(bào)文頭長度可以得到TCP報(bào)文長度,再拿TCP報(bào)文長度減去TCP報(bào)文頭長度就能得到TCP報(bào)文載荷長度。

現(xiàn)在我們已經(jīng)拿到網(wǎng)絡(luò)包里面所有TCP報(bào)文的集合了,并且這些報(bào)文是按照時(shí)間先后順序進(jìn)行正序排序的,我們隨機(jī)選中一個(gè)報(bào)文,拿到這個(gè)TCP報(bào)文以及其在集合中的索引,然后我們就可以基于下面的實(shí)現(xiàn)拿到對應(yīng)的TCP流。

public static List<TcpPackage> getTcpStream(List<TcpPackage> tcpPackages, int index) {
    LinkedList<TcpPackage> tcpStream = new LinkedList<>();
    TcpPackage beginTcpPackage = tcpPackages.get(index);
    long currentSeqNum = beginTcpPackage.getSeqNum();
    long currentAckNum = beginTcpPackage.getAckNum();
    // 從index位置向前查找
    for (int i = index - 1; i >=0; i--) {
        TcpPackage previousTcpPackage = tcpPackages.get(i);
        long previousSeqNum = previousTcpPackage.getSeqNum();
        long previousAckNum = previousTcpPackage.getAckNum();
        if (isPreviousTcpPackageSatisfied(currentSeqNum, currentAckNum, previousSeqNum, previousAckNum)) {
            tcpStream.addFirst(previousTcpPackage);
            currentSeqNum = previousSeqNum;
            currentAckNum = previousAckNum;
        }
    }
    // index位置的報(bào)文也要放到tcp流中
    tcpStream.add(beginTcpPackage);
    currentSeqNum = beginTcpPackage.getSeqNum();
    currentAckNum = beginTcpPackage.getAckNum();
    // 從index位置向后查找
    for (int i = index + 1; i < tcpPackages.size(); i++) {
        TcpPackage nextTcpPackage = tcpPackages.get(i);
        long nextSeqNum = nextTcpPackage.getSeqNum();
        long nextAckNum = nextTcpPackage.getAckNum();
        if (isNextTcpPackageSatisfied(currentSeqNum, currentAckNum, nextSeqNum, nextAckNum)) {
            tcpStream.add(nextTcpPackage);
            currentSeqNum = nextSeqNum;
            currentAckNum = nextAckNum;
        }
    }
    return tcpStream;
}

上述方法中,向前查找時(shí)判斷TCP報(bào)文是否屬于TCP流是基于isPreviousTcpPackageSatisfied() 方法,向后查找時(shí)判斷TCP報(bào)文是否屬于TCP流是基于isNextTcpPackageSatisfied() 方法,而這兩個(gè)方法其實(shí)就是把序列號和確認(rèn)號的變化規(guī)則翻譯成了代碼,如下所示。

public static boolean isPreviousTcpPackageSatisfied(long currentSeqNum, long currentAckNum,
                                                    long previousSeqNum, long previousAckNum) {
    boolean condition1 = currentSeqNum == previousSeqNum && currentSeqNum != 0;
    boolean condition2 = currentAckNum == previousAckNum && currentAckNum != 0;
    boolean condition3 = currentSeqNum == previousAckNum;
    boolean condition4 = currentAckNum - 1 == previousSeqNum;
    return condition1 || condition2 || condition3 || condition4;
}

public static boolean isNextTcpPackageSatisfied(long currentSeqNum, long currentAckNum,
                                                long nextSeqNum, long nextAckNum) {
    boolean condition1 = currentSeqNum == nextSeqNum && currentSeqNum != 0;
    boolean condition2 = currentAckNum == nextAckNum && currentAckNum != 0;
    boolean condition3 = currentAckNum == nextSeqNum;
    boolean condition4 = currentSeqNum + 1 == nextAckNum;
    return condition1 || condition2 || condition3 || condition4;
}

至此,使用Java語言如何從網(wǎng)絡(luò)包中獲得TCP流就介紹完畢。

總結(jié)

TCP流就是一次TCP連接中,從連接建立,到數(shù)據(jù)傳輸,再到連接斷開整個(gè)過程中的TCP報(bào)文集合,而獲取TCP流是基于TCP報(bào)文序列號和確認(rèn)號的變化規(guī)則,規(guī)則如下。

  • 本次發(fā)送報(bào)文的SeqNum等于上一次發(fā)送報(bào)文的SeqNum加上上一次發(fā)送報(bào)文的length
  • 本次發(fā)送報(bào)文的AckNum等于上一次接收報(bào)文的SeqNum加上上一次接收報(bào)文的length;
  • SYN報(bào)文和FIN報(bào)文的length默認(rèn)為1,而不是0。

使用Java語言解析網(wǎng)絡(luò)包并得到TCP流,步驟總結(jié)如下。

  • 使用io.pkts工具解開網(wǎng)絡(luò)包;
  • 將網(wǎng)絡(luò)包中的TCP報(bào)文轉(zhuǎn)換為自定義的可讀性更強(qiáng)的數(shù)據(jù)結(jié)構(gòu);
  • 選中一個(gè)TCP報(bào)文;
  • 根據(jù)序列號和確認(rèn)號變化獲取TCP流。

以上就是Java語言獲取TCP流的實(shí)現(xiàn)步驟的詳細(xì)內(nèi)容,更多關(guān)于Java獲取TCP流的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 劍指Offer之Java算法習(xí)題精講二叉搜索樹與數(shù)組查找

    劍指Offer之Java算法習(xí)題精講二叉搜索樹與數(shù)組查找

    跟著思路走,之后從簡單題入手,反復(fù)去看,做過之后可能會忘記,之后再做一次,記不住就反復(fù)做,反復(fù)尋求思路和規(guī)律,慢慢積累就會發(fā)現(xiàn)質(zhì)的變化
    2022-03-03
  • Java8?Stream?collect(Collectors.toMap())的使用

    Java8?Stream?collect(Collectors.toMap())的使用

    這篇文章主要介紹了Java8?Stream?collect(Collectors.toMap())的使用,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • spring boot上傳文件出錯問題如何解決

    spring boot上傳文件出錯問題如何解決

    這篇文章主要介紹了spring boot上傳文件出錯問題如何解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • 深入了解MyBatis參數(shù)

    深入了解MyBatis參數(shù)

    今天小編就為大家分享一篇關(guān)于深入了解MyBatis參數(shù),小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • 提升java開發(fā)效率工具lombok使用爭議

    提升java開發(fā)效率工具lombok使用爭議

    這篇文章主要介紹了提升java開發(fā)效率工具lombok使用爭議到底該不該使用的分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • 解析Java?中for循環(huán)和foreach循環(huán)哪個(gè)更快

    解析Java?中for循環(huán)和foreach循環(huán)哪個(gè)更快

    這篇文章主要介紹了Java中for循環(huán)和foreach循環(huán)哪個(gè)更快示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Java實(shí)現(xiàn)順序棧的示例代碼

    Java實(shí)現(xiàn)順序棧的示例代碼

    線性表和棧都是我們常用的數(shù)據(jù)結(jié)構(gòu),棧可以看成一種特殊狀態(tài)的線性表。線性表分為順序表和鏈表,使用線性表中的順序表來實(shí)現(xiàn)棧時(shí)這種棧被稱為順序棧。這篇文章總結(jié)了如何使用順序表實(shí)現(xiàn)棧,需要的可以參考一下
    2022-11-11
  • Java字節(jié)流 從文件輸入輸出到文件過程解析

    Java字節(jié)流 從文件輸入輸出到文件過程解析

    這篇文章主要介紹了Java字節(jié)流 從文件輸入 輸出到文件過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-09-09
  • JDK8新特性-java.util.function-Function接口使用

    JDK8新特性-java.util.function-Function接口使用

    這篇文章主要介紹了JDK8新特性-java.util.function-Function接口使用,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • Spring Boot中的Properties的使用詳解

    Spring Boot中的Properties的使用詳解

    這篇文章主要介紹了Spring Boot中的Properties的使用詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02

最新評論