程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java網絡編程菜鳥進階:TCP和套接字入門

Java網絡編程菜鳥進階:TCP和套接字入門

編輯:關於JAVA

JDK 提供了對 TCP(Transmission Control Protocol,傳輸控制協議)和 UDP(User Datagram Protocol,用戶數據報協議)這兩個數據傳輸協議的支持。本文開始探討 TCP。

TCP 基礎知識

在“服務器-客戶端”這種架構中,服務器和客戶端各自維護一個端點,兩個端點需要通過網絡進行數據交換。TCP 為這種需求提供了一種可靠的流式連接,流式的意思是傳出和收到的數據都是連續的字節,沒有對數據量進行大小限制。一個端點由 IP 地址和端口構成(專業術語為“元組 {IP 地址, 端口}”)。這樣,一個連接就可以由元組 {本地地址, 本地端口, 遠程地址, 遠程端口} 來表示。

連接過程

在 TCP 編程接口中,端點體現為 TCP 套接字。共有兩種 TCP 套接字:主動和被動,“被動”狀態也常被稱為“偵聽”狀態。服務器和客戶端利用套接字進行連接的過程如下:

1、服務器創建一個被動套接字,開始循環偵聽客戶端的連接。

2、客戶端創建一個主動套接字,連接服務器。

3、服務器接受客戶端的連接,並創建一個代表該連接的主動套接字。

4、服務器和客戶端通過步驟 2 和 3 中創建的兩個主動套接字進行數據傳輸。

下面是連接過程的圖解:

TCP 連接

一個簡單的 TCP 服務器

JDK 提供了 ServerSocket 類來代表 TCP 服務器的被動套接字。下面的代碼演示了一個簡單的 TCP 服務器(多線程阻塞模式),它不斷偵聽並接受客戶端的連接,然後將客戶端發送過來的文本按行讀取,全文轉換為大寫後返回給客戶端,直到客戶端發送文本行 bye:

  1. public class TcpServer implements Runnable {
  2. private ServerSocket serverSocket;
  3. public TcpServer(int port) throws IOException {
  4. // 創建綁定到某個端口的 TCP 服務器被動套接字。
  5. serverSocket = new ServerSocket(port);
  6. }
  7. @Override
  8. public void run() {
  9. while (true) {
  10. try {
  11. // 以阻塞的方式接受一個客戶端連接,返回代表該連接的主動套接字。
  12. Socket socket = serverSocket.accept();
  13. // 在新線程中處理客戶端連接。
  14. new Thread(new ClIEntHandler(socket)).start();
  15. } catch (IOException ex) {
  16. ex.printStackTrace();
  17. }
  18. }
  19. }
  20. }
  21. public class ClIEntHandler implements Runnable {
  22. private Socket socket;
  23. public ClIEntHandler(Socket socket) {
  24. this.socket = Objects.requireNonNull(socket);
  25. }
  26. @Override
  27. public void run() {
  28. try (Socket s = socket) { // 減少代碼量的花招……
  29. // 包裝套接字的輸入流以讀取客戶端發送的文本行。
  30. BufferedReader in = new BufferedReader(new InputStreamReader(
  31. s.getInputStream(), StandardCharsets.UTF_8));
  32. // 包裝套接字的輸出流以向客戶端發送轉換結果。
  33. PrintWriter out = new PrintWriter(new OutputStreamWriter(
  34. s.getOutputStream(), StandardCharsets.UTF_8), true);
  35. String line = null;
  36. while ((line = in.readLine()) != null) {
  37. if (line.equals("bye")) {
  38. break;
  39. }
  40. // 將轉換結果輸出給客戶端。
  41. out.println(line.toUpperCase(Locale.ENGLISH));
  42. }
  43. } catch (IOException ex) {
  44. ex.printStackTrace();
  45. }
  46. }
  47. }

阻塞模式的編程方式簡單,但存在性能問題,因為服務器線程會卡死在接受客戶端的 accept() 方法上,不能有效利用資源。套接字支持非阻塞模式,現在暫時略過。

一個簡單的 TCP 客戶端

JDK 提供了 Socket 類來代表 TCP 客戶端的主動套接字。下面的代碼演示了上述服務器的客戶端:

  1. public class TcpClIEnt implements Runnable {
  2. private Socket socket;
  3. public TcpClIEnt(String host, int port) throws IOException {
  4. // 創建連接到服務器的套接字。
  5. socket = new Socket(host, port);
  6. }
  7. @Override
  8. public void run() {
  9. try (Socket s = socket) { // 再次減少代碼量……
  10. // 包裝套接字的輸出流以向服務器發送文本行。
  11. PrintWriter out = new PrintWriter(new OutputStreamWriter(
  12. s.getOutputStream(), StandardCharsets.UTF_8), true);
  13. // 包裝套接字的輸入流以讀取服務器返回的文本行。
  14. BufferedReader in = new BufferedReader(new InputStreamReader(
  15. s.getInputStream(), StandardCharsets.UTF_8));
  16. Console console = System.console();
  17. String line = null;
  18. while ((line = console.readLine()) != null) {
  19. if (line.equals("bye")) {
  20. break;
  21. }
  22. // 將文本行發送給服務器。
  23. out.println(line);
  24. // 打印服務器返回的文本行。
  25. console.writer().println(in.readLine());
  26. }
  27. // 通知服務器關閉連接。
  28. out.println("bye");
  29. } catch (IOException ex) {
  30. ex.printStackTrace();
  31. }
  32. }
  33. }

從 JDK 文檔可以看到,ServerSocket 和 Socket 在初始化的時候,可以設定一些參數,還支持延遲綁定。這些東西對性能和行為都有所影響。後續兩篇文章將分別詳解這兩個類的初始化。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved