帶線程池的socket客戶端與服務端


前言  

socket(套接字),Socket和ServerSocket位於java.net包中,之前雖然對socket有過一些了解,但一直都是雲里霧里的,特意仔細的學習了一個socket,用socket模擬一個天氣查詢的功能,並且解決了幾個使用socket過程中比較嚴重的問題。

 

最簡單的客戶端和服務端

服務端代碼

 1 package cn.hucc.socket.server;
 2 
 3 import java.io.DataInputStream;
 4 import java.io.DataOutputStream;
 5 import java.io.IOException;
 6 import java.net.ServerSocket;
 7 import java.net.Socket;
 8 
 9 /**
10  * 
11  * @auth hucc
12  * @date 2015年10月10日
13  */
14 public class WeatherServer {
15 
16     private static final int PORT = 8888;
17 
18     public static void main(String[] args) {
19 
20         ServerSocket server = null;
21         Socket socket = null;
22         DataInputStream dataInputStream = null;
23         DataOutputStream dataOutputStream = null;
24         try {
25             server = new ServerSocket(PORT);
26             System.out.println("天氣服務端已經移動,監聽端口:" + PORT);
27             socket = server.accept();
28 
29             // 接受客戶端請求
30             dataInputStream = new DataInputStream(socket.getInputStream());
31             String request = dataInputStream.readUTF();
32             System.out.println("from client..." + request);
33 
34             // 響應客戶端
35             dataOutputStream = new DataOutputStream(socket.getOutputStream());
36             String response = "天氣:晴朗,溫度:36度";
37             dataOutputStream.writeUTF(response);
38 
39         } catch (IOException e) {
40             e.printStackTrace();
41         } finally {
42             try {
43                 if (dataInputStream != null) {
44                     dataInputStream.close();
45                 }
46                 if (dataOutputStream != null) {
47                     dataOutputStream.close();
48                 }
49             } catch (IOException e) {
50                 e.printStackTrace();
51             }
52         }
53     }
54 }
View Code

 服務端代碼很簡單,這里沒有直接使用InputStream和OutputStream兩個流,而是使用了DataInputStream和DataOutputStream兩個類,通過readUTF()和writeUTF()兩個方法免去轉碼的痛苦。

客戶端代碼

 1 package cn.hucc.socket.client;
 2 
 3 import java.io.DataInputStream;
 4 import java.io.DataOutputStream;
 5 import java.io.IOException;
 6 import java.net.Socket;
 7 
 8 /**
 9  * 
10  * @auth hucc
11  * @date 2015年10月10日
12  */
13 public class WeatherClient {
14     private static final String HOST = "127.0.0.1";
15     private static final int PORT = 8888;
16     
17     public static void main(String[] args) {
18         
19         Socket socket = null;
20         DataInputStream dataInputStream = null;
21         DataOutputStream dataOutputStream = null;
22         try {
23             socket = new Socket(HOST, PORT);
24 
25             //給服務端發送請求
26             dataOutputStream = new DataOutputStream(socket.getOutputStream());
27             String request = "北京";
28             dataOutputStream.writeUTF(request);
29             
30             dataInputStream = new DataInputStream(socket.getInputStream());
31             String response = dataInputStream.readUTF();
32             System.out.println(response);
33         
34         } catch (IOException e) {
35             e.printStackTrace();
36         }finally{
37             try {
38                 if(dataInputStream != null){
39                     dataInputStream.close();
40                 }
41                 if(dataOutputStream != null){
42                     dataOutputStream.close();
43                 }
44                 if(socket != null){
45                     socket.close();
46                 }
47             } catch (IOException e) {
48                 e.printStackTrace();
49             }
50             
51         }
52     }
53 }
View Code

 

運行結果

客戶端運行結果:

服務端運行結果:

 

結果分析

客戶端和服務端都運行起來了,並且達到了天氣查詢的效果,但是服務端只服務了一次就停止了,這明顯不符合需求,服務端應該響應完客戶端之后,繼續監聽8888端口,等待下一個客戶端的連接。

 

讓服務端一直提供服務

將服務端的代碼寫入死循環中,一直監聽客戶端的請求。修改服務端的代碼:

 1 public static void main(String[] args) throws IOException {
 2 
 3         ServerSocket server = null;
 4         Socket socket = null;
 5         DataInputStream dataInputStream = null;
 6         DataOutputStream dataOutputStream = null;
 7         server = new ServerSocket(PORT);
 8         System.out.println("天氣服務端已經移動,監聽端口:" + PORT);
 9         while(true){
10             try {
11                 socket = server.accept();
12                 
13                 // 接受客戶端請求
14                 dataInputStream = new DataInputStream(socket.getInputStream());
15                 String request = dataInputStream.readUTF();
16                 System.out.println("from client..." + request);
17                 
18                 // 響應客戶端
19                 dataOutputStream = new DataOutputStream(socket.getOutputStream());
20                 String response = "天氣:晴朗,溫度:36度";
21                 dataOutputStream.writeUTF(response);
22 
23             } catch (IOException e) {
24                 e.printStackTrace();
25             } finally {
26                 try {
27                     if (dataInputStream != null) {
28                         dataInputStream.close();
29                     }
30                     if (dataOutputStream != null) {
31                         dataOutputStream.close();
32                     }
33                 } catch (IOException e) {
34                     e.printStackTrace();
35                 }
36             }
37         }
38     }
View Code

 

通過while(true)死循環,服務端一直監聽8888端口,由於socket是阻塞的,只有服務端完成了當前客戶端的響應,才會繼續處理下一個客戶端的響應。這樣一直讓主線線程去處理socket請求不合適,因此需要為服務端加上多線程功能,同時處理多個socket請求。

 

給服務端加上多線程

修改代碼,將服務端的socket處理抽取出來,並且封裝到Runnable接口的run方法中。

 1 package cn.hucc.socket.server;
 2 
 3 import java.io.DataInputStream;
 4 import java.io.DataOutputStream;
 5 import java.io.IOException;
 6 import java.net.Socket;
 7 
 8 /**
 9  * 
10  * @auth hucc
11  * @date 2015年10月10日
12  */
13 public class WeatherThread extends Thread {
14     
15     private Socket socket;
16     
17     public WeatherThread(Socket socket){
18         this.socket = socket;
19     }
20     
21     public void run() {
22         
23         DataInputStream dataInputStream = null;
24         DataOutputStream dataOutputStream = null;
25         try {
26             // 接受客戶端請求
27             dataInputStream = new DataInputStream(socket.getInputStream());
28             String request = dataInputStream.readUTF();
29             System.out.println("from client..." + request+" 當前線程:"+Thread.currentThread().getName());
30             
31             // 響應客戶端
32             dataOutputStream = new DataOutputStream(socket.getOutputStream());
33             String response = "天氣:晴朗,溫度:36度";
34             dataOutputStream.writeUTF(response);
35 
36         } catch (IOException e) {
37             e.printStackTrace();
38         } finally {
39             try {
40                 if (dataInputStream != null) {
41                     dataInputStream.close();
42                 }
43                 if (dataOutputStream != null) {
44                     dataOutputStream.close();
45                 }
46             } catch (IOException e) {
47                 e.printStackTrace();
48             }
49         }
50     }
51 }
View Code

修改服務端,添加多線程功能

 1 package cn.hucc.socket.server;
 2 
 3 import java.io.IOException;
 4 import java.net.ServerSocket;
 5 import java.net.Socket;
 6 
 7 /**
 8  * 
 9  * @auth hucc
10  * @date 2015年10月10日
11  */
12 public class WeatherServer {
13 
14     private static final int PORT = 8888;
15 
16     public static void main(String[] args) throws IOException {
17 
18         ServerSocket server = null;
19         Socket socket = null;
20         server = new ServerSocket(PORT);
21         System.out.println("天氣服務端已經移動,監聽端口:" + PORT);
22         while(true){
23                 socket = server.accept();
24                 new WeatherThread(socket).start();
25         }
26     }
27 }
View Code

此時服務端已經擁有多線程處理能力了,運行結果如下圖:

 

弊端分析

盡管服務端現在已經有了多線程處理能力,但是通過運行結果,我們可以看到,服務端每次接收到客戶端的請求后,都會創建一個新的線程去處理,而jvm的線程數量過多是,服務端處理速度會變慢。而且如果並發較高的話,瞬間產生的線程數量也會比較大,因此,我們需要再給服務端加上線程池的功能。

 

給服務端加上線程池功能

使用java.util.concurrent.Executor類就可以創建一個簡單的線程池,代碼如下:

 1 package cn.hucc.socket.server;
 2 
 3 import java.io.IOException;
 4 import java.net.ServerSocket;
 5 import java.net.Socket;
 6 import java.util.concurrent.Executor;
 7 import java.util.concurrent.Executors;
 8 
 9 /**
10  * 
11  * @auth hucc
12  * @date 2015年10月10日
13  */
14 public class WeatherServer {
15 
16     private static final int PORT = 8888;
17 
18     public static void main(String[] args) throws IOException {
19 
20         ServerSocket server = null;
21         Socket socket = null;
22         server = new ServerSocket(PORT);
23         System.out.println("天氣服務端已經移動,監聽端口:" + PORT);
24         
25         //FixedThreadPool最多開啟3(參數)個線程,多余的線程會存儲在隊列中,等線程處理完了
26         //再從隊列中獲取線程繼續處理
27         Executor executor = Executors.newFixedThreadPool(3);
28         while(true){
29                 socket = server.accept();
30                 executor.execute(new WeatherThread(socket));
31         }
32     }
33 }
View Code

Executor一共有4種線程池實現,這里使用了FixedThreadPool最多開啟3(參數)個線程,多余的線程會存儲在隊列中,等線程處理完了再從隊列中獲取,繼續處理。這樣的話無論並發量多大,服務端只會最多3個線程進行同時處理,使服務端的壓力不會那么大。

 

運行結果:

通過運行結果,可以看到線程只開了1,2,3三個線程。

 

到這里,socket的簡易教程便結束了。O(∩_∩)O~~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM