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

如何創(chuàng)建 Java TCP Socket

1. 前言

TCP 的英文全稱是 Transmission Control Protocol,翻譯成中文叫做傳輸控制協(xié)議,它是 TCP/IP 協(xié)議族中非常重要的一個(gè)傳輸層協(xié)議。TCP 是一個(gè)面向連接的、面向字節(jié)流的、可靠的傳輸層協(xié)議,有丟包重傳機(jī)制、有流控機(jī)制、有擁塞控制機(jī)制。TCP 保證數(shù)據(jù)包的順序,并且對重復(fù)包進(jìn)行過濾。相比不可靠傳輸協(xié)議 UDP,TCP 完全是相反的。

對于可靠性要求很高的應(yīng)用場景來說,選擇可靠 TCP 作為傳輸層協(xié)議肯定是正確的。例如,著名的 HTTP 協(xié)議和 FTP 協(xié)議都是采用 TCP 進(jìn)行傳輸。當(dāng)然 TCP 為了保證傳輸?shù)目煽啃?,引入了非常?fù)雜的保障機(jī)制,比如:TCP 連接建立時(shí)的三次握手和連接關(guān)閉時(shí)的四次揮手機(jī)制,滑動窗口機(jī)制,發(fā)送流控機(jī)制,慢啟動和擁塞避免機(jī)制等。當(dāng)然,操作系統(tǒng)的網(wǎng)絡(luò)協(xié)議棧已經(jīng)實(shí)現(xiàn)了這些復(fù)雜的機(jī)制,

本小節(jié)主要是介紹通過 Java 語言編寫 TCP 客戶端、服務(wù)器程序的方法。

編寫 TCP 客戶端、服務(wù)器程序主要分為如下幾個(gè)步驟:

  • 創(chuàng)建客戶端 Socket,連接到某個(gè)服務(wù)器監(jiān)聽的端口,需要指定服務(wù)器監(jiān)聽的 host 和 port。host 可以是 IP 地址,也可以是域名。
  • 創(chuàng)建服務(wù)端 Socket,綁定到一個(gè)固定的服務(wù)端口,監(jiān)聽客戶端的連接請求。
  • 客戶端發(fā)起連接請求,完成三次握手過程。
  • TCP 連接建立成功后,雙方進(jìn)行數(shù)據(jù)流交互。
  • 數(shù)據(jù)流交互完成后,關(guān)閉連接。

2. 傳統(tǒng) TCP 客戶端和服務(wù)器建立過程

為了更好地理解編寫 TCP 客戶端和服務(wù)器程序的步驟,下圖展示了通過 C 語言 Socket API 編寫客戶端和服務(wù)器程序的過程。

圖片描述

圖中的矩形方框都是 C 函數(shù),很好的展示了客戶端和服務(wù)器 Socket 的建立過程。對于 Java 語言來說,只是應(yīng)用面向?qū)ο蟮乃季S對上面的過程進(jìn)行了抽象,下來我們就探討一下如何編寫 Java 客戶端和服務(wù)器程序。

3. Java Socket 類分析

Java 語言抽象了 java.net.Socket 類,表示一個(gè) Socket,既可以用在客戶端,又可以用在服務(wù)器端。其實(shí) java.net.Socket 也是一個(gè)包裝類,對外抽象了一組公共方法,具體實(shí)現(xiàn)是在 java.net.SocketImpl 類中完成的,它允許用戶自定義具體實(shí)現(xiàn)。java.net.Socket 類包含的主要功能如下:

  • 創(chuàng)建 Socket,具體就是創(chuàng)建一個(gè) java.net.Socket 類的對象。
  • 建立 TCP 連接,可以通過 java.net.Socket 類的構(gòu)造方法完成,也可以調(diào)用它的 connect 方法完成。
  • 將 Socket 綁定到本地接口 IP 地址或者端口,可以調(diào)用 java.net.Socket 類的 bind 方法完成。

提示:
服務(wù)器需要做 bind 操作,客戶端一般不需要做 bind 操作。

  • 關(guān)閉連接,可以調(diào)用 java.net.Socket 類的 close 方法完成。

  • 接收數(shù)據(jù),可以通過 java.net.Socket 類的 getInputStream 方法,返回一個(gè) java.io.InputStream 對象實(shí)現(xiàn)數(shù)據(jù)接收。

  • 發(fā)送數(shù)據(jù),可以通過 java.net.Socket 類的 getOutputStream 方法,返回一個(gè) java.io.OutputStream 對象實(shí)現(xiàn)數(shù)據(jù)發(fā)送。

java.net.Socket 類提供了一組重載的構(gòu)造方法,方便程序員選擇,大體分為四類:

  • 可以傳入服務(wù)器的 host 和 port 參數(shù)

原型如下:

  public Socket(String host, int port)
        throws UnknownHostException, IOException
  public Socket(InetAddress address, int port) throws IOException

對于 host 參數(shù),你可以傳入 IP 地址或者是域名。當(dāng)然,你可以傳入構(gòu)造好的 InetAddress 地址結(jié)構(gòu)。

java.net.Socket 的構(gòu)造方法中,首先會構(gòu)造一個(gè) InetAddress 地址結(jié)構(gòu),然后進(jìn)行域名解析,最后調(diào)用它的 connect 方法和服務(wù)器建立連接。

  • 可以傳入綁定的本地地址參數(shù)

原型如下:

  public Socket(String host, int port, InetAddress localAddr,  int localPort) throws IOException
  public Socket(InetAddress address, int port, InetAddress localAddr,  int localPort) throws IOException

這類構(gòu)造方法也可以傳入 host 和 port 外,功能和上面類似。另外,還可以傳入 localAddr 和 localPort,會調(diào)用 java.net.Socket 類的 bind 方法,綁定在本地的接口地址和端口。

  • 無參構(gòu)造方法
  public Socket()

此構(gòu)造方法,除了構(gòu)造一個(gè) java.net.Socket 類的對象,并不會去 connect 服務(wù)器。你需要調(diào)用它的 connect 方法連接服務(wù)器。

public void connect(SocketAddress endpoint, int timeout) throws IOException

自己調(diào)用 connect 方法,需要構(gòu)造 SocketAddress 結(jié)構(gòu),當(dāng)然你可以設(shè)置連接的超時(shí)時(shí)間,單位是毫秒(milliseconds)。

  • 訪問代理服務(wù)器
public Socket(Proxy proxy) 

當(dāng)你需要訪問某個(gè)代理服務(wù)器時(shí),可以調(diào)用此構(gòu)造方法,Socket 會自動去連接代理服務(wù)器。

創(chuàng)建一個(gè)簡單的 java.net.Socket 客戶端,示例代碼如下:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;

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

    public static void main(String[] args) {
        Socket client = null;
        try {
            // 在構(gòu)造方法中傳入 host 和 port
            // client = new Socket("192.168.43.49", PORT);

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

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

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

            // 接收服務(wù)器的數(shù)據(jù)
            BufferedInputStream in = new BufferedInputStream(client.getInputStream());
            StringBuilder inMessage = new StringBuilder();
            while(true){
                int c = in.read();
                if (c == -1 || c == '\n')
                    break;
                inMessage.append((char)c);
            }
            System.out.println("Recv from server:" + inMessage.toString());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (client != null){
                try {
                    client.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

這里我們創(chuàng)建的是阻塞式的客戶端,有幾點(diǎn)需要注意的地方:

  • 通過 OutputStream 的對象向服務(wù)器發(fā)送完數(shù)據(jù)后,需要調(diào)用 flush 方法。
  • BufferedInputStream 的 read 方法會阻塞線程,所以需要設(shè)計(jì)好消息邊界的識別機(jī)制,示例代碼是通過換行符 ‘\n’ 表示一個(gè)消息邊界。
  • java.net.Socket 的各個(gè)方法都拋出了 IOException 異常,需要捕獲。
  • 注意調(diào)用 close 方法,關(guān)閉連接。

4. Java ServerSocket 類分析

Java 語言抽象了 java.net.ServerSocket 類表示服務(wù)器監(jiān)聽 Socket,此類只用在服務(wù)器端,通過調(diào)用它的 accept 方法來獲取新的連接。accept 方法的返回值是 java.net.Socket 類型,后續(xù)服務(wù)器和客戶端的數(shù)據(jù)收發(fā),都是通過 accept 方法返回的 Socket 對象完成。

java.net.ServerSocket 類也提供了一組重載的構(gòu)造方法,方便程序員選擇。

  public ServerSocket(int port) throws BindException, IOException
  public ServerSocket(int port, int queueLength) throws BindException, IOException
  public ServerSocket(int port, int queueLength, InetAddress bindAddress) throws IOException
  public ServerSocket() throws IOException
  • port 參數(shù)用于傳入服務(wù)器監(jiān)聽的端口號。如果傳入的 port 是 0,系統(tǒng)會隨機(jī)選擇一個(gè)端口監(jiān)聽。
  • queueLength 參數(shù)用于設(shè)置連接接收隊(duì)列的長度。不傳入此參數(shù),采用系統(tǒng)默認(rèn)長度。
  • bindAddress 參數(shù)用于將監(jiān)聽 Socket 綁定到一個(gè)本地接口。如果傳入此參數(shù),服務(wù)器會監(jiān)聽指定的接口地址;如果不指定此參數(shù),默認(rèn)會監(jiān)聽通配符 IP 地址,比如 IPv4 是 0.0.0.0。
  • 提示:
  • 可以通過 netstat 命令查看服務(wù)器程序監(jiān)聽的 IP 地址和端口號。

如果你是通過無參構(gòu)造方法構(gòu)造 java.net.ServerSocket 類的對象,需要手動調(diào)用它的 bind 方法,綁定監(jiān)聽端口和接口地址。

創(chuàng)建一個(gè)簡單的服務(wù)器監(jiān)聽 Socket,示例代碼如下:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {
    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)聽新的連接請求
            Socket conn = ss.accept();
            System.out.println("Accept a new connection:" + conn.getRemoteSocketAddress().toString());

            // 讀取客戶端數(shù)據(jù)
            BufferedInputStream in = new BufferedInputStream(conn.getInputStream());
            StringBuilder inMessage = new StringBuilder();
            while(true){
                int c = in.read();
                if (c == -1 || c == '\n')
                    break;
                inMessage.append((char)c);
            }
            System.out.println("Recv from client:" + inMessage.toString());

            // 向客戶端發(fā)送數(shù)據(jù)
            String rsp = "Hello Client!\n";
            BufferedOutputStream out = new BufferedOutputStream(conn.getOutputStream());
            out.write(rsp.getBytes());
            out.flush();
            System.out.println("Send to client:" + rsp);
            conn.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ss != null){
                try {
                    ss.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

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

我們創(chuàng)建的阻塞式服務(wù)端,所以 java.net.ServerSocket 的 accept 方法會阻塞線程,直到新連接返回。同樣,在接收客戶端的消息的時(shí)候注意消息邊界的處理,最后向客戶端發(fā)送響應(yīng)的時(shí)候,需要調(diào)用 flush 方法。

5. 小結(jié)

用 Java 語言編寫 TCP 客戶端和服務(wù)器程序非常方便,你只需要創(chuàng)建一個(gè) java.net.ServerSocket 實(shí)例,然后調(diào)用它的 accept 方法監(jiān)聽客戶端的請求;你只需要創(chuàng)建一個(gè) java.net.Socket 實(shí)例,可以通過構(gòu)造方法或者 connect 連接對應(yīng)的服務(wù)器,然后就可以進(jìn)行數(shù)據(jù)的收發(fā),最后數(shù)據(jù)交互完成后,調(diào)用 close 方法關(guān)閉連接即可。

示例代碼中的服務(wù)器功能還不完善,不能持續(xù)提供服務(wù),不能同時(shí)接收多個(gè)客戶端的連接請求,需要在后續(xù)的小節(jié)逐步完善。