第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

Java TCP Socket 數(shù)據(jù)收發(fā)

1. 前言

TCP 是面向字節(jié)流的傳輸協(xié)議。所謂字節(jié)流是指 TCP 并不理解它所傳輸?shù)臄?shù)據(jù)的含義,在它眼里一切都是字節(jié),1 字節(jié)是 8 比特。比如,TCP 客戶端向服務(wù)器發(fā)送“Hello Server,I’m client。How are you?”,TCP 客戶端發(fā)送的是具有一定含義的數(shù)據(jù),但是對(duì)于 TCP 協(xié)議棧來說,傳輸?shù)氖且淮?strong>字節(jié)流,具體如何解釋這段數(shù)據(jù)需要 TCP 服務(wù)器的應(yīng)用程序來完成,這就涉及到“應(yīng)用層協(xié)議設(shè)計(jì)”的問題。

在 TCP/IP 協(xié)議棧的四層協(xié)議模型中,操作系統(tǒng)內(nèi)核協(xié)議棧實(shí)現(xiàn)了鏈路層、網(wǎng)絡(luò)層、傳輸層,將應(yīng)用層留給了應(yīng)用程序來實(shí)現(xiàn)。在編程實(shí)踐中,通常有文本協(xié)議二進(jìn)制協(xié)議兩種類型,前者通常通過一個(gè)分隔符區(qū)分消息語義,而后者通常是需要通過一個(gè) length 字段指定消息體的大小。比如著名的 HTTP 協(xié)議就是文本協(xié)議,通過 “\r\n” 來區(qū)分 HTTP Header 的每一行。而 RTMP 協(xié)議是一個(gè)二進(jìn)制協(xié)議,通過 length 字段來指定消息體的大小。

解析 TCP 字節(jié)流的語義通常叫做消息解析,如果按照傳統(tǒng) C 語言函數(shù)的方式來實(shí)現(xiàn),還是比較麻煩的,有很多細(xì)節(jié)需要處理。好在 Java 為我們提供了很多工具類,給我們的工作帶來了極大地便利。

2. Java 字節(jié)流結(jié)構(gòu)

Java 的 java.io.* 包中包含了 InputStream 和 OutputStream 兩個(gè)類,是 Java 字節(jié)流 I/O 的基礎(chǔ)類,其他具體的 Java I/O 字節(jié)流功能類都派生自這兩個(gè)類。

圖片描述

圖中只列出了我們 Socket 編程中常用的 I/O 字節(jié)流類。java.net.SocketInputStream 類是 Socket 的輸入流實(shí)現(xiàn)類,它繼承了 java.io.FileInputStream 類。java.net.SocketOutputStream 類是 Socket 的輸出流實(shí)現(xiàn)類,它繼承了 java.io.FileOutputStream 類,下來我們逐一介紹這些類的基本功能。

2.1 Java InputStream & OutputStream

java.io.InputStream 類是一個(gè)抽象超類,它提供最小的編程接口和輸入流的部分實(shí)現(xiàn)。java.io.InputStream 類定義的幾類方法:

  • 讀取字節(jié)或字節(jié)數(shù)組,一組 read 方法。
  • 標(biāo)記流中的位置,mark 方法。
  • 跳過輸入字節(jié),skip 方法。
  • 找出可讀取的字節(jié)數(shù),available 方法。
  • 重置流中的當(dāng)前位置,reset 方法。
  • 關(guān)閉流,close 方法。

InputStream 流在創(chuàng)建實(shí)例時(shí)會(huì)自動(dòng)打開,你可以調(diào)用 close 方法顯式關(guān)閉流,也可以選擇在垃圾回收 InputStream 時(shí),隱式關(guān)閉流。需要注意的是垃圾回收機(jī)制關(guān)閉流,并不能立刻生效,可能會(huì)造成流對(duì)象泄漏,所以一般需要主動(dòng)關(guān)閉。

java.io.OutputStream 類同樣是一個(gè)抽象超類,它提供最小的編程接口和輸出流的部分實(shí)現(xiàn)。java.io.OutputStream 定義的幾類方法:

  • 寫入字節(jié)或字節(jié)數(shù)組,一組 write 方法。
  • 刷新流,flush 方法。
  • 關(guān)閉流,close 方法。

OutputStream 流在創(chuàng)建時(shí)會(huì)自動(dòng)打開,你可以調(diào)用 close 方法顯式關(guān)閉流,也可以選擇在垃圾回收 OutputStream 時(shí),隱式關(guān)閉流。

2.2 FileInputStream & FileOutputStream

java.io.FileInputStream 和 java.io.FileOutputStream 是文件輸入和輸出流類,用于從本機(jī)文件系統(tǒng)上的文件讀取數(shù)據(jù)或向其寫入數(shù)據(jù)。你可以通過文件名、java.io.File 對(duì)象、java.io.FileDescriptor 對(duì)象創(chuàng)建一個(gè) FileInputStream 或 FileOutputStream 流對(duì)象。

2.3 SocketOutputStream & SocketInputStream

java.net.SocketInputStream 和 java.net.SocketOutputStream 代表了 Socket 流的讀寫,他們分別繼承自 java.io.FileInputStream 和 java.io.FileOutputStream 類,這說明 Socket 讀寫包含了文件讀寫的特性。另外,這兩個(gè)類是定義在 java.net.* 包中,并沒有對(duì)外公開。

2.4 FilterInputStream & FilterOutputStream

java.io.FilterInputStream 和 java.io.FilterOutputStream 分別是 java.io.InputStream 和 java.io.OutputStream 的子類,并且它們本身都是抽象類,為被過濾的流定義接口。java.io.FilterInputStream 和 java.io.FilterOutputStream 的主要作用是為基礎(chǔ)流提供一些額外的功能,這些不同的功能都是單獨(dú)的類,繼承了他們的接口。例如,過濾后的流 BufferedInputStream 和BufferedOutputStream 在讀寫時(shí)會(huì)緩沖數(shù)據(jù),以加快數(shù)據(jù)傳輸速度。

2.5 BufferedInputStream & BufferedOutputStream

java.io.BufferedInputStream 類繼承自 java.io.FilterInputStream 類,它的作用是為 java.io.FileInputStream、java.net.SocketInputStream 等輸入流提供緩沖功能。一般通過 java.io.BufferedInputStream 的構(gòu)造方法傳入具體的輸入流,同時(shí)可以指定緩沖區(qū)的大小。java.io.BufferedInputStream 會(huì)從底層 Socket 讀取一批數(shù)據(jù)保存到內(nèi)部緩沖區(qū)中,后續(xù)通過 java.io.BufferedInputStream 的 read 方法讀取數(shù)據(jù),實(shí)際上都從緩沖區(qū)中讀取,等讀完緩沖中的這部分?jǐn)?shù)據(jù)之后,再從底層 Socket 中讀取下一部分的數(shù)據(jù)。

  • 注意:
  • 當(dāng)你調(diào)用 java.io.BufferedInputStream 的 read 方法讀取一個(gè)數(shù)組時(shí),只有當(dāng)讀取的數(shù)據(jù)達(dá)到數(shù)組長(zhǎng)度時(shí)才會(huì)返回,否則線程會(huì)被阻塞。

java.io.BufferedOutputStream 類繼承自 java.io.FilterOutputStream 類,它的作用是為 java.io.FileOutputStream、java.net.SocketOutputStream 等輸出流提供緩沖功能。一般通過 java.io.BufferedOutputStream 的構(gòu)造方法傳入底層輸出流,同時(shí)可以指定緩沖區(qū)的大小。每次調(diào)用 java.io.BufferedOutputStream 的 write 方法寫數(shù)據(jù)時(shí),實(shí)際上是寫入它的內(nèi)部緩沖區(qū)中,當(dāng)內(nèi)部緩沖區(qū)寫滿或者調(diào)用了 flush 方法,才會(huì)將數(shù)據(jù)寫入底層 Socket 的緩沖區(qū)。

BufferedInputStream 和 BufferedOutputStream 在讀取或?qū)懭霑r(shí)緩沖數(shù)據(jù),從而減少了對(duì)原始數(shù)據(jù)源所需的訪問次數(shù)。緩沖流通常比類似的非緩沖流效率更高。

2.6 DataInputStream & DataOutputStream

java.io.DataInputStream 和 java.io.DataOutputStream 類繼承自 java.io.FilterInputStream 和 java.io.FilterOutputStream 類,同時(shí)實(shí)現(xiàn)了 java.io.DataInput 和 java.io.DataOutput 接口,功能是以機(jī)器無關(guān)的格式讀取或?qū)懭朐?Java 數(shù)據(jù)類型。

3. 數(shù)據(jù)讀寫的案例程序

我們?cè)O(shè)計(jì)一個(gè)簡(jiǎn)單的協(xié)議,每個(gè)消息的開頭 4 字節(jié)表示消息體的長(zhǎng)度,格式如下:

+-----------------+
| 4 字節(jié)消息長(zhǎng)度   |
+-----------------+
|                 |
|   消息體         |
|                 |
+-----------------+

我們通過這個(gè)簡(jiǎn)單的協(xié)議演示 java.io.DataInputStream 、java.io.DataOutputStream 和 java.io.BufferedInputStream、java.io.BufferedOutputStream 類的具體用法。TCP 客戶端和服務(wù)器的編寫可以參考上一節(jié)內(nèi)容,本節(jié)僅展示數(shù)據(jù)讀寫的代碼片段。

客戶端數(shù)據(jù)讀寫代碼:

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;

public class TCPClientIO {
    // 服務(wù)器監(jiān)聽的端口號(hào)
    private static final int PORT = 56002;
    private static final int TIMEOUT = 15000;

    public static void main(String[] args) {
        Socket client = null;
        try {
            // 調(diào)用無參構(gòu)造方法
            client = new Socket();
            // 構(gòu)造服務(wù)器地址結(jié)構(gòu)
            SocketAddress serverAddr = new InetSocketAddress("127.0.0.1", PORT);
            // 連接服務(wù)器,超時(shí)時(shí)間是 15 毫秒
            client.connect(serverAddr, TIMEOUT);

            System.out.println("Client start:" + client.getLocalSocketAddress().toString());

            // 向服務(wù)器發(fā)送數(shù)據(jù)
            DataOutputStream out = new DataOutputStream(
                    new BufferedOutputStream(client.getOutputStream()));
            String req = "Hello Server!\n";
            out.writeInt(req.getBytes().length);
            out.write(req.getBytes());
            // 不能忘記 flush 方法的調(diào)用
            out.flush();
            System.out.println("Send to server:" + req + " length:" +req.getBytes().length);

            // 接收服務(wù)器的數(shù)據(jù)
            DataInputStream in = new DataInputStream(
                    new BufferedInputStream(client.getInputStream()));

            int msgLen = in.readInt();
            byte[] inMessage = new byte[msgLen];
            in.read(inMessage);
            System.out.println("Recv from server:" + new String(inMessage) + " length:" + msgLen);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (client != null){
                try {
                    client.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

服務(wù)端數(shù)據(jù)讀寫代碼:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;

public class TCPServerIO {
    private static final int PORT =56002;

    public static void main(String[] args) {
        ServerSocket ss = null;
        try {
            // 創(chuàng)建一個(gè)服務(wù)器 Socket
            ss = new ServerSocket(PORT);
            // 監(jiān)聽新的連接請(qǐng)求
            Socket conn = ss.accept();
            System.out.println("Accept a new connection:" + conn.getRemoteSocketAddress().toString());

            // 讀取客戶端數(shù)據(jù)
            DataInputStream in = new DataInputStream(
                    new BufferedInputStream(conn.getInputStream()));
            int msgLen = in.readInt();
            byte[] inMessage = new byte[msgLen];
            in.read(inMessage);
            System.out.println("Recv from client:" + new String(inMessage) + "length:" + msgLen);

            // 向客戶端發(fā)送數(shù)據(jù)
            String rsp = "Hello Client!\n";

            DataOutputStream out = new DataOutputStream(
                    new BufferedOutputStream(conn.getOutputStream()));
            out.writeInt(rsp.getBytes().length);
            out.write(rsp.getBytes());
            out.flush();
            System.out.println("Send to client:" + rsp + " length:" + rsp.getBytes().length);
            conn.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ss != null){
                try {
                    ss.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        System.out.println("Server exit!");
    }
}

注意讀寫消息長(zhǎng)度需要用 readInt 和 writeInt 方法。

4. 總結(jié)

通過本節(jié)學(xué)習(xí),你需要樹立一個(gè)觀念:TCP 是面向字節(jié)流的協(xié)議,TCP 傳輸數(shù)據(jù)的時(shí)候并不保證消息邊界,消息邊界需要程序員設(shè)計(jì)應(yīng)用層協(xié)議來保證。將字節(jié)流解析成自定義的協(xié)議格式,需要借助 java.io.* 中提供的工具類,一般情況下,java.io.DataInputStream 、java.io.DataOutputStream 和 java.io.BufferedInputStream、java.io.BufferedOutputStream 四個(gè)類就足以滿足你的需求了。DataInputStream 和 DataOutputStream 類主要是用以讀寫 java 相關(guān)的數(shù)據(jù)類型,BufferedInputStream 和 BufferedOutputStream 解決緩沖讀寫的問題,目的是提高讀寫效率。

本節(jié)簡(jiǎn)要介紹了 Socket 編程中常用的 I/O 流類,關(guān)于 java.io.* 包中的各種 I/O 流類不是本節(jié)的重點(diǎn),需要你自己參考相關(guān) Java 書籍。