第七色在线视频,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ù)包的順序,并且對(duì)重復(fù)包進(jìn)行過(guò)濾。相比不可靠傳輸協(xié)議 UDP,TCP 完全是相反的。

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

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

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

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

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

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

圖片描述

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

3. Java Socket 類分析

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

  • 創(chuàng)建 Socket,具體就是創(chuàng)建一個(gè) java.net.Socket 類的對(duì)象。
  • 建立 TCP 連接,可以通過(guò) 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ù),可以通過(guò) java.net.Socket 類的 getInputStream 方法,返回一個(gè) java.io.InputStream 對(duì)象實(shí)現(xiàn)數(shù)據(jù)接收。

  • 發(fā)送數(shù)據(jù),可以通過(guò) java.net.Socket 類的 getOutputStream 方法,返回一個(gè) java.io.OutputStream 對(duì)象實(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

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

java.net.Socket 的構(gòu)造方法中,首先會(huì)構(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,會(huì)調(diào)用 java.net.Socket 類的 bind 方法,綁定在本地的接口地址和端口。

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

此構(gòu)造方法,除了構(gòu)造一個(gè) java.net.Socket 類的對(duì)象,并不會(huì)去 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èn)代理服務(wù)器
public Socket(Proxy proxy) 

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

創(chuàng)建一個(gè)簡(jiǎn)單的 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)聽(tīng)的端口號(hào)
    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)用無(wú)參構(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)需要注意的地方:

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

4. Java ServerSocket 類分析

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

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

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

創(chuàng)建一個(gè)簡(jiǎn)單的服務(wù)器監(jiān)聽(tīng) 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)聽(tīng)新的連接請(qǐng)求
            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 方法會(huì)阻塞線程,直到新連接返回。同樣,在接收客戶端的消息的時(shí)候注意消息邊界的處理,最后向客戶端發(fā)送響應(yīng)的時(shí)候,需要調(diào)用 flush 方法。

5. 小結(jié)

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

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