socket+執行緒池,寫服務端和客戶端進行互動
以下內容轉自:
https://www.cnblogs.com/gnoc/p/4866788.html
前言
socket(套接字),Socket和ServerSocket位於java.net包中,持續開啟服務端,接收來自客戶端的資訊,並響應。
最開始,咱們先來兩段最簡單的服務端和客戶端的程式碼
最簡單的服務端程式碼:
package com.socket;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class TestSocketService {
//自定義一個埠號
private static final int PORT = 8888;
public static void main(String[] args) {
ServerSocket server = null;
Socket socket = null;
DataInputStream dataInputStream = null;
DataOutputStream dataOutputStream = null ;
try {
server = new ServerSocket(PORT);
System.out.println("監聽埠:" + PORT);
socket = server.accept();
// 接受客戶端請求
dataInputStream = new DataInputStream(socket.getInputStream());
String request = dataInputStream.readUTF();
System.out.println("from client..." + request);
// 響應客戶端
dataOutputStream = new DataOutputStream(socket.getOutputStream());
String response = "收到";
dataOutputStream.writeUTF(response);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (dataInputStream != null) {
dataInputStream.close();
}
if (dataOutputStream != null) {
dataOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
最簡單的客戶端程式碼:
package com.socket;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
public class TestSocketClient {
private static final String HOST = "127.0.0.1";
private static final int PORT = 8888;
public static void main(String[] args) {
Socket socket = null;
DataInputStream dataInputStream = null;
DataOutputStream dataOutputStream = null;
try {
socket = new Socket(HOST, PORT);
//給服務端傳送請求
dataOutputStream = new DataOutputStream(socket.getOutputStream());
String request = "我是客戶1";
dataOutputStream.writeUTF(request);
dataInputStream = new DataInputStream(socket.getInputStream());
String response = dataInputStream.readUTF();
System.out.println(response);
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(dataInputStream != null){
dataInputStream.close();
}
if(dataOutputStream != null){
dataOutputStream.close();
}
if(socket != null){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客戶端和服務端分別執行,能收發資料,但是服務端只服務了一次就停止了,這明顯不符合需求,服務端應該響應完客戶端之後,繼續監聽埠,等待下一個客戶端的連線。
讓服務端一直提供服務
將服務端的程式碼寫入迴圈中持續迴圈,一直監聽來自客戶端的請求。修改服務端的程式碼:
public static void main(String[] args) throws IOException {
ServerSocket server = null;
Socket socket = null;
DataInputStream dataInputStream = null;
DataOutputStream dataOutputStream = null;
server = new ServerSocket(PORT);
System.out.println("監聽埠:" + PORT);
while(true){
try {
socket = server.accept();
// 接受客戶端請求
dataInputStream = new DataInputStream(socket.getInputStream());
String request = dataInputStream.readUTF();
System.out.println("from client..." + request);
// 響應客戶端
dataOutputStream = new DataOutputStream(socket.getOutputStream());
String response = "收到";
dataOutputStream.writeUTF(response);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (dataInputStream != null) {
dataInputStream.close();
}
if (dataOutputStream != null) {
dataOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
通過while(true)迴圈,服務端一直監聽埠,由於socket是阻塞的,只有服務端完成了當前客戶端的響應,才會繼續處理下一個客戶端的響應。這樣一直讓主線執行緒去處理socket請求不合適,因此需要為服務端加上多執行緒功能,同時處理多個socket請求。
給服務端加上多執行緒
修改程式碼,將服務端的socket處理抽取出來,並且封裝到Runnable介面的run方法中:
package com.socket;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
public class TestThread extends Thread {
private Socket socket;
public TestThread(Socket socket){
this.socket = socket;
}
public void run() {
DataInputStream dataInputStream = null;
DataOutputStream dataOutputStream = null;
try {
// 接受客戶端請求
dataInputStream = new DataInputStream(socket.getInputStream());
String request = dataInputStream.readUTF();
System.out.println("from client..." + request+" 當前執行緒:"+Thread.currentThread().getName());
// 響應客戶端
dataOutputStream = new DataOutputStream(socket.getOutputStream());
String response = "收到";
dataOutputStream.writeUTF(response);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (dataInputStream != null) {
dataInputStream.close();
}
if (dataOutputStream != null) {
dataOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
修改服務端,新增多執行緒功能:
package com.socket;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class TestSocketService {
private static final int PORT = 8888;
public static void main(String[] args) throws IOException {
ServerSocket server = null;
Socket socket = null;
server = new ServerSocket(PORT);
System.out.println("監聽埠:" + PORT);
while(true){
socket = server.accept();
new TestThread(socket).start();
}
}
}
弊端分析
儘管服務端現在已經有了多執行緒處理能力,但是服務端每次接收到客戶端的請求後,都會建立一個新的執行緒去處理,而jvm的執行緒數量過多是,服務端處理速度會變慢。而且如果併發較高的話,瞬間產生的執行緒數量也會比較大,因此,我們需要再給服務端加上執行緒池的功能。
使用java.util.concurrent.Executor類就可以建立一個簡單的執行緒池,程式碼如下:
package com.socket;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class TestSocketService {
private static final int PORT = 8888;
public static void main(String[] args) throws IOException {
ServerSocket server = null;
Socket socket = null;
server = new ServerSocket(PORT);
System.out.println("監聽埠:" + PORT);
//FixedThreadPool最多開啟3(引數)個執行緒,多餘的執行緒會儲存在佇列中,等執行緒處理完了
//再從佇列中獲取執行緒繼續處理
Executor executor = Executors.newFixedThreadPool(3);
while(true){
socket = server.accept();
executor.execute(new TestThread(socket));
}
}
}
Executor一共有4種執行緒池實現,這裡使用了FixedThreadPool最多開啟3(引數)個執行緒,多餘的執行緒會儲存在佇列中,等執行緒處理完了再從佇列中獲取,繼續處理。這樣的話無論併發量多大,服務端只會最多3個執行緒進行同時處理,使服務端的壓力不會那麼大。