Java中的TCP、UDP通訊
文章目錄
TCP
TCP 協議提供面向連線的服務,通過它建立的是可靠地連線。Java 為 TCP 協議提供了兩個類:Socke 類和 ServerSocket 類。一個 Socket 例項代表了 TCP 連線的一個客戶端,而一個 ServerSocket 例項代表了 TCP 連線的一個伺服器端,一般在 TCP Socket 程式設計中,客戶端有多個,而伺服器端只有一個,客戶端 TCP 向伺服器端 TCP 傳送連線請求,伺服器端的 ServerSocket 例項則監聽來自客戶端的 TCP 連線請求,併為每個請求建立新的 Socket 例項,由於服務端在呼叫 accept()等待客戶端的連線請求時會阻塞,直到收到客戶端傳送的連線請求才會繼續往下執行程式碼,因此要為每個 Socket 連線開啟一個執行緒。伺服器端要同時處理 ServerSocket 例項和 Socket 例項,而客戶端只需要使用 Socket 例項。另外,每個 Socket 例項會關聯一個 InputStream 和 OutputStream 物件,我們通過將位元組寫入套接字的 OutputStream 來發送資料,並通過從 InputStream 來接收資料。
From:極客wiki
建立伺服器
- 通過
ServerSocket
監聽本地埠(以22333埠為例)
//:TCPServer.java
ServerSocket serverSocket = new ServerSocket(22333);
while(true){
Socket conn = serverSocket.accept();
onClientArrived(conn);
}
//deal with the request connection
public void abstract onClientArrived(Socket conn);
在新的連線到達前ServerSocket#accept()
accept
函式都將返回一個Socket
例項;
2) 在onClientArrived
中處理新連線
@Override
public void onConnArrived(Socket conn) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String tmpLine = null;
while ((tmpLine = reader.readLine()) != null){
System. out.println(tmpLine);
}
reader.close();
conn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Reader#readLine()
為一個阻塞讀取過程,因此需要在客戶端使用(啟用auto flush)PrintWriter
的println
等含有自動newLine
的 函式與之配合使用。這裡為了簡便在讀取完成後,直接關閉連線,如果想進行持續會話,則可根據conn
的內容來控制時候結束會話,並且需要保證客戶端並沒有主動關閉連線。另外,通常當ServerSocket#accept()
返回新Socket
物件後,應該為該物件建立一個任務,並將該任務交由執行緒池處理。
建立客戶端
- 客戶端套接字 連線本地22333埠
Socket client = new Socket("127.0.0.1", 22333);
- 向套接字中寫入資料
//使用PrintWriter向套接字的OutputStream中寫入資料
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(client.getOutputStream()), true);
printWriter.println("hello ! this msg is send by client");
printWriter.close();
client.close();
此處在寫入完成直接關閉連線。若希望持續會話,則可在服務端的連線沒有關閉的情況下,此處可以通過對Socket
輸入流的read
操作和輸出流的write
操作實現持續會話
3) 完整程式碼
//:TCPTest.java
public class TCPTest {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
TCPServer tcpServer = new TCPServer() {
@Override
public void onConnArrived(Socket conn) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String tmpLine = null;
while ((tmpLine = reader.readLine()) != null){
System.out.println("receive msg from client" + tmpLine);
}
reader.close();
conn.close();
System.out.println("conn shutdown");
} catch (IOException e) {
e.printStackTrace();
}
}
};
tcpServer.startServer();
}
}).start();
try {
Socket socket = new Socket("127.0.0.1",TCPServer.PORT);
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
printWriter.println("hello ! this msg is send by client");
printWriter.close();
socket.close();
System.out.println("client close");
} catch (IOException e) {
e.printStackTrace();
}
}
}
//:TCPServer.java
public abstract class TCPServer {
public static final int PORT = 22333;
private static ServerSocket serverSocket;
private volatile boolean ifServerIsRunning = false;
public TCPServer(){
synchronized (TCPServer.class){
if (serverSocket == null){
try {
serverSocket = new ServerSocket(PORT);
}catch (IOException e){
e.printStackTrace();
}
}
}
}
public void startServer(){
if (!ifServerIsRunning){
ifServerIsRunning = true;
while (true){
try {
Socket conn = serverSocket.accept();
onConnArrived(conn);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public abstract void onConnArrived(Socket conn);
}
由以上的程式可以看出,Socket
就是程序與程序之間的一種通訊方式,一個位於服務端,一個位於客戶端。Socket
並不代表運輸層,它只是一個網路程式設計API,當選用Socket
時,代表我們希望使用TCP
協議通訊,當選用DatagramSocket
時代表我們希望使用UDP
協議進行通訊。
UDP
Java 通過 DatagramPacket 類和 DatagramSocket 類來使用 UDP 套接字,客戶端和伺服器端都通過DatagramSocket 的 send()方法和 receive()方法來發送和接收資料,用 DatagramPacket 來包裝需要傳送或者接收到的資料。傳送資訊時,Java 建立一個包含待發送資訊的 DatagramPacket 例項,並將其作為引數傳遞給DatagramSocket例項的send()方法;接收資訊時,Java 程式首先建立一個 DatagramPacket 例項,該例項預先分配了一些空間,並將接收到的資訊存放在該空間中,然後把該例項作為引數傳遞給 DatagramSocket 例項的 receive()方法。在建立 DatagramPacket 例項時,要注意:如果該例項用來包裝待接收的資料,則不指定資料來源的遠端主機和埠,只需指定一個快取資料的 byte 陣列即可(在呼叫 receive()方法接收到資料後,源地址和埠等資訊會自動包含在 DatagramPacket 例項中),而如果該例項用來包裝待發送的資料,則要指定要傳送到的目的主機和埠。 From:極客wiki
小結
- 在讀取未關閉的socket的過程中,任何的
read()
、readline()
操作都是一個阻塞過程直到讀取到內容或者socket關閉,可對比new Scanner(System.in)
的類next
操作。 ServerSocket
的accept()
操作是同樣是一個阻塞過程,直到收到一個連線請求,每次客戶端成功發起請求後,accept()
都返回一個Socket
物件,這個Socket
物件將負責與發起請求的本次客戶端進行通訊。Reader
的readLine()
需要使用'\n'
作為此次讀取結束標記,否則將一直阻塞在讀取狀態。- “一切皆檔案”