網(wǎng)絡(luò)編程三——TCP和UDP

學(xué)習(xí)鏈接

這篇介紹TPC和UDP的文章,講解得十分詳細(xì)易懂:Android 網(wǎng)絡(luò)編程之TCP、UDP詳解。

我在這篇文章的基礎(chǔ)上,做一些自己的總結(jié)和補(bǔ)充。

TCP部分

TCP部分主要是總結(jié)一下建立TCP連接時(shí),發(fā)送的序號(hào)值和標(biāo)志位的意思,然后在Java中一個(gè)TCP運(yùn)用的實(shí)例。

連接時(shí)的序號(hào)和標(biāo)志位

  • 第一次握手:客戶端發(fā)送SYN=1,seq=xxx,其中,SYN表示標(biāo)志位,我理解是連接有效的標(biāo)志,seq表示這次消息的序列號(hào);

    第一次握手

  • 第二次握手:服務(wù)端發(fā)送SYN=1,seq=yyy ACK=1,ack=xxx+1,其中SYNseq和前一次意思相同,這次的seq表示的是服務(wù)端的序列號(hào),ACKb表示確認(rèn)了客戶端的序列號(hào),ack的值是客戶端的序列號(hào)值+1,表示收到了此序列號(hào),希望下次傳過(guò)來(lái)的序列號(hào)是之前的+1;

    第二次握手

  • 第三次握手:客戶端發(fā)送ACK=1,ack=yyy+1 seq=xxx+1,示意和上述一致。

    第三次握手

為什么要三次握手

當(dāng)客戶端第一次握手時(shí),若此時(shí)網(wǎng)絡(luò)堵塞,導(dǎo)致服務(wù)端很久才收到信息,那此時(shí)客戶端可能已經(jīng)超時(shí)或其他原因放棄此次連接,而服務(wù)端并不知道客戶端等待了多久,依然和客戶端建立連接,那么此時(shí)的報(bào)文可能是失效的,導(dǎo)致此次連接是無(wú)效的,這樣會(huì)大量浪費(fèi)網(wǎng)絡(luò)資源和時(shí)間。

另外,為了保證可靠性傳輸,TCP雙方連接時(shí)都必須保證先有一個(gè)對(duì)方的起始序列號(hào)和期望的下次序列號(hào),這樣在以后的傳輸中,才能知道哪些序號(hào)的報(bào)文是接收了,哪些是沒(méi)有接收的。

因此,建立三次握手的原因是:

  1. 保證可靠性傳輸,獲取對(duì)方的起始序列號(hào)和下次期望收到的序列號(hào);
  2. 避免資源浪費(fèi),保證此次連接是有效的。

斷開(kāi)連接時(shí)的序號(hào)和標(biāo)志位

若主機(jī)A要和主機(jī)B斷開(kāi)連接,那么主機(jī)A和主機(jī)B需要發(fā)送四次消息來(lái)完成斷開(kāi)連接。

  • 第一次:主機(jī)A發(fā)送信息FIN=1 seq=p給主機(jī)B,其中FIN表示要斷開(kāi)連接,seq表示序列號(hào);
  • 第二次:主機(jī)B發(fā)送給主機(jī)AACK=1 ack=p+1,表示收到了此信息;
  • 第三次:主機(jī)B發(fā)送給主機(jī)AFIN=1, seq=q, ACK=1, ack=p+1
  • 第四次:主機(jī)A發(fā)送給主機(jī)B:ACK=1,ack=q+1,seq=u+1,并等待2*最大報(bào)文壽命時(shí)間后關(guān)閉連接。

其中:

  1. 第一次消息主機(jī)A告訴主機(jī)B我要斷開(kāi)連接了;
  2. 第二次消息主機(jī)B告訴主機(jī)A我收到了,此時(shí)主機(jī)A關(guān)閉了寫(xiě)通道,主機(jī)B關(guān)閉了讀通道;
  3. 第三次消息主機(jī)B告訴主機(jī)A我要斷開(kāi)連接了;
  4. 第四次消息主機(jī)A告訴主機(jī)B我收到了,此時(shí)主機(jī)A關(guān)閉讀通道,主機(jī)B關(guān)閉寫(xiě)通道。

為什么要四次才釋放連接

若主機(jī)A第一次消息告訴主機(jī)B我要斷開(kāi)連接后,就關(guān)閉讀和寫(xiě)通道,則由于網(wǎng)絡(luò)原因,主機(jī)B可能收不到消息,還在等待A的消息,或者還在給A發(fā)消息,此時(shí)就會(huì)引發(fā)網(wǎng)絡(luò)錯(cuò)誤,因此主機(jī)A不能關(guān)閉讀通道,需要等待B發(fā)過(guò)來(lái)的確認(rèn)消息后才能關(guān)閉。因此,A關(guān)閉寫(xiě)通道需要2次消息,同理,B關(guān)閉寫(xiě)通道也需要2次。

TCP在Java中的代碼

服務(wù)端:

ServerSocket serverSocket = null;
boolean isEnd = false;
try {
    serverSocket = new ServerSocket(14455);

    while (!isEnd) {
        Socket dataSocket = serverSocket.accept(); // 等待客戶端連接

        // 讀取客戶端傳來(lái)的數(shù)據(jù)
        InputStream is = dataSocket.getInputStream();
        BufferedReader input = new BufferedReader(new InputStreamReader(is));
        String message = input.readLine();
        System.out.println("服務(wù)端收到消息:" + message);

        // 給客戶端寫(xiě)數(shù)據(jù)
        OutputStream os = dataSocket.getOutputStream();
        PrintWriter output = new PrintWriter(os, true);
        output.println("服務(wù)端收到了!");
        dataSocket.close();
    }
    serverSocket.close();
} catch (Exception e) {
    e.printStackTrace();
}

客戶端:

try {
    Socket socket = new Socket("localhost", 14455);
    OutputStream os = socket.getOutputStream();
    PrintWriter output = new PrintWriter(os, true);
    output.println("我是客戶端!");

    InputStream is = socket.getInputStream();
    BufferedReader input = new BufferedReader(new InputStreamReader(is));
    String message = input.readLine();
    System.out.println("客戶端收到消息: " + message);
    input.close();
} catch (IOException e) {
    e.printStackTrace();
}

執(zhí)行結(jié)果:

服務(wù)端收到消息:我是客戶端!
客戶端收到消息: 服務(wù)端收到了!

UDP部分

UDP和TCP之間的區(qū)別:

對(duì)比 TCP UDP
連接 有連接 無(wú)連接
可靠性 可靠 不可靠
性能 相對(duì)較低 相對(duì)較高
流量 相對(duì)多 相對(duì)少
連接對(duì)象 一對(duì)一 一對(duì)一、多對(duì)多

UDP在Java中的代碼

服務(wù)端:

byte[] bytes = new byte[1024];
try {
    DatagramSocket socket = new DatagramSocket(14455);
    DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
    while (true) {
        socket.receive(packet);
        System.out.println("服務(wù)端收到:" + new String(packet.getData(), "utf-8"));
    }

} catch (Exception e) {
    e.printStackTrace();
}

客戶端代碼:

DatagramSocket socket = null;

        try {
            socket = new DatagramSocket();
            InetAddress address = InetAddress.getByName("localhost");
            String message = new String("聽(tīng)媽媽的話");
            byte[] bytes = message.getBytes();
            DatagramPacket packet = new DatagramPacket(bytes, bytes.length, address, 14455);
            //客戶端發(fā)消息
            socket.send(packet);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            socket.close();
        }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容