概述
TCP通信能實現兩台計算機之間的數據交互,通信的兩端,要嚴格區分為客戶端(Client)與服務端(Server)。
兩端通信的步驟
- 服務端程序,需要事先啟動,等待客戶端的連接。
- 客戶端主動連接服務器端,連接成功才能通信。服務端不可以主動連接客戶端。
在Java中,提供了兩個類用於實現TCP通信程序:
- 客戶端:java.net.Socket類表示。創建Socket對象,向服務端發出連接請求,服務端響應請求,兩者建立連接開始通信。
- 服務端:java.net.ServerSocket類表示。創建ServerSocket對象,相當於開啟一個服務,並等待客戶端的連接。
如下圖:
客戶端1和服務端進行一個通訊的交互①②③④,需要四個IO流對象,客戶端2也是如此。那么兩個客戶端都和同一個服務端進行交互,服務端怎么區分呢?在服務器端有一個accept方法,可以獲取到請求的客戶端對象。
多個客戶端同時和服務器進行交互,就需要使用多個IO流對象。服務器是沒有IO流的,服務器可以獲取到請求的客戶端Socket對象t,使用每個客戶端Socket中提供的IO流和客戶端進行交互。服務器使用客戶端的字節輸入流讀取客戶端發送的數據,使用客戶端的字節輸出流給客戶端回寫數據。簡單的說,就是服務器使用客戶端的流和客戶端交互。
Socket類
Socket類:該類實現客戶端套接字,套接字指的是兩台設備之間通訊的端點。
構造方法
public Socket(String host, int port)
// 創建套接字對象(兩台設備之間通訊的端點對象)並將其連接到指定主機上的指定端
// 口號。如果指定的host是null,則相當於指定地址為回送地址。
-
套接字:包含了IP地址(host)和端口號(port)的網絡單位。
-
回送地址:回送地址(127.x.x.x)是本機回送地址(Loopback Address),主要用於網絡軟件測試以及本地機進程間通信,無論什么程序,一旦使用回送地址發送數據,立即返回,不進行任何網絡傳輸。
成員方法
OutputStream getOutputStream()
// 返回此套接字的輸出流。
InputStream getInputStream()
// 返回此套接字的輸入流
void close()
// 關閉此套接字。
舉例
使用Java程序,進行客戶端和服務端之間的通信。
TCP通訊的客戶端
向服務器發送連接請求,給服務器發送數據,讀取服務器回寫的數據。
實現步驟:
1.創建一個客戶端對象Socket,構造方法中綁定服務器的IP地址和端口號。
2.使用Socket對象中的方法getOutputStream(),獲取網絡字節輸出流OutputStream對象。
3.使用網絡字節輸出流OutputStream對象中的方法write,給服務器發送數據。
4.使用Socket對象中的方法getInputStream()獲取網絡字節輸入流InputStream對象。
5.使用網絡字節輸入流InputStream對象中的方法read,讀取服務器回寫的數據。
6.釋放資源(Socket)。
注意:
1.客戶端和服務器端進行交互,必須使用Socket中提供的網絡流,不能使用自己創建的流對象。
2.當我們創建客戶端對象Socket的時候,就會去請求服務器和服務器經過3次握手建立連接通路。
這時如果服務器沒有啟動,那么就會拋出異常(ConnectException: Connection refused: connect)。
如果服務器已經啟動,那么就可以進行交互了。
客戶端代碼實現:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TcpClient {
public static void main(String[] args) throws IOException {
method();
}
private static void method() throws IOException {
// 創建一個客戶端對象Socket,構造方法中綁定服務器的IP地址和端口號。
Socket socket = new Socket("127.0.0.1", 8888);
// 使用Socket對象中的方法getOutputStream(),獲取網絡字節輸出流OutputStream對象。
OutputStream outputStream = socket.getOutputStream();
// 使用網絡字節輸出流OutputStream對象中的方法write,給服務器發送數據。
outputStream.write("你好吖!服務器。".getBytes());
// 使用Socket對象中的方法getInputStream()獲取網絡字節輸入流InputStream對象。
InputStream inputStream = socket.getInputStream();
// 使用網絡字節輸入流InputStream對象中的方法read,讀取服務器回寫的數據。
byte[] bytes = new byte[1024];
int len = inputStream.read(bytes);
System.out.println(new String(bytes, 0, len));
// 釋放資源(Socket)。
socket.close();
}
}
完成了客戶端代碼的編寫后,先了解一下ServerSocket類,然后編寫服務端Java代碼。
ServerSocket類
ServerSocket類:此類現服務器套接字。服務器等待請求通過網絡傳入。它基於該請求執行某些操作,然后可能向請求者返回結果。
構造方法
ServerSocket(int port)
// 創建綁定到特定端口的服務器套接字。
成員方法
Socket accept()
// 偵聽並接受到此套接字的連接。
服務器端必須明確一件事情,必須的知道是哪個客戶端請求的服務器,所以可以使用accept方法獲取到請求的客戶端對象Socket。
舉例
接收客戶端的請求,讀取客戶端發送的數據,給客戶端回寫數據。
TCP通信的服務端
實現步驟:
1.創建服務器ServerSocket對象,對象中傳遞系統要指定的端口號。
2.使用ServerSocket對象中的方法accept,獲取到請求的客戶端對象Socket。
3.使用Socket對象中的方法getInputStream()獲取網絡字節輸入流InputStream對象。
4.使用網絡字節輸入流InputStream對象中的方法read,讀取客戶端發送的數據。
5.使用Socket對象中的方法getOutputStream()獲取網絡字節輸出流OutputStream對象。
6.使用網絡字節輸出流OutputStream對象中的方法write,給客戶端回寫數據。
7.釋放資源(Socket,ServerSocket)。
服務端代碼實現:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer {
public static void main(String[] args) throws IOException {
method();
}
private static void method() throws IOException {
// 創建服務器ServerSocket對象,對象中傳遞系統要指定的端口號。
ServerSocket serverSocket = new ServerSocket(8888);
// 使用ServerSocket對象中的方法accept,獲取到請求的客戶端對象Socket。
Socket socket = serverSocket.accept();
// 使用Socket對象中的方法getInputStream()獲取網絡字節輸入流InputStream對象。
InputStream inputStream = socket.getInputStream();
// 使用網絡字節輸入流InputStream對象中的方法read,讀取客戶端發送的數據。
byte[] bytes = new byte[1024];
int len = inputStream.read(bytes);
System.out.println(new String(bytes, 0, len));
// 使用Socket對象中的方法getOutputStream()獲取網絡字節輸出流OutputStream對象。
OutputStream outputStream = socket.getOutputStream();
// 使用網絡字節輸出流OutputStream對象中的方法write,給客戶端回寫數據。
outputStream.write("收到,謝謝!".getBytes());
// 釋放資源(Socket,ServerSocket)。
socket.close();
serverSocket.close();
}
}
客戶端程序和服務端程序都已經編寫完成,先運行服務端程序,然后再運行客戶端程序。
客戶端程序:控制台輸出(收到來自服務端回寫的數據)
收到,謝謝您!客戶端。
服務端程序:控制台輸出(接收到客戶端發來的請求數據)
你好吖!服務器。
輸出截圖如下:
客戶端程序和服務端程序對比(4K圖,可放大):
代碼實現步驟:
-
第一步
服務器端: // 創建服務器ServerSocket對象,對象中傳遞系統要指定的端口號。 ServerSocket serverSocket = new ServerSocket(8888); // 使用ServerSocket對象中的方法accept,獲取到請求的客戶端對象Socket。 Socket socket = serverSocket.accept();
-
第二步
客戶端: // 創建一個客戶端對象Socket,構造方法中綁定服務器的IP地址和端口號。 Socket socket = new Socket("127.0.0.1", 8888); // 使用Socket對象中的方法getOutputStream(),獲取網絡字節輸出流OutputStream對象。 OutputStream outputStream = socket.getOutputStream(); // 使用網絡字節輸出流OutputStream對象中的方法write,給服務器發送數據。 outputStream.write("你好吖!服務器。".getBytes());
-
第三步
服務器端: // 使用Socket對象中的方法getInputStream()獲取網絡字節輸入流InputStream對象。 InputStream inputStream = socket.getInputStream(); // 使用網絡字節輸入流InputStream對象中的方法read,讀取客戶端發送的數據。 byte[] bytes = new byte[1024]; int len = inputStream.read(bytes); System.out.println(new String(bytes, 0, len)); // 使用Socket對象中的方法getOutputStream()獲取網絡字節輸出流OutputStream對象。 OutputStream outputStream = socket.getOutputStream(); // 使用網絡字節輸出流OutputStream對象中的方法write,給客戶端回寫數據。 outputStream.write("收到,謝謝您!客戶端。".getBytes());
-
第四步
客戶端: // 使用Socket對象中的方法getInputStream()獲取網絡字節輸入流InputStream對象。 InputStream inputStream = socket.getInputStream(); // 使用網絡字節輸入流InputStream對象中的方法read,讀取服務器回寫的數據。 byte[] bytes = new byte[1024]; int len = inputStream.read(bytes); System.out.println(new String(bytes, 0, len));
-
第五步
客戶端: // 釋放資源(Socket)。 socket.close();
-
第六步
服務器端: // 釋放資源(Socket,ServerSocket)。 socket.close(); serverSocket.close();