前言
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 }
服務端代碼很簡單,這里沒有直接使用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 }
運行結果
客戶端運行結果:

服務端運行結果:

結果分析
客戶端和服務端都運行起來了,並且達到了天氣查詢的效果,但是服務端只服務了一次就停止了,這明顯不符合需求,服務端應該響應完客戶端之后,繼續監聽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 }
通過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 }
修改服務端,添加多線程功能
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 }
此時服務端已經擁有多線程處理能力了,運行結果如下圖:

弊端分析
盡管服務端現在已經有了多線程處理能力,但是通過運行結果,我們可以看到,服務端每次接收到客戶端的請求后,都會創建一個新的線程去處理,而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 }
Executor一共有4種線程池實現,這里使用了FixedThreadPool最多開啟3(參數)個線程,多余的線程會存儲在隊列中,等線程處理完了再從隊列中獲取,繼續處理。這樣的話無論並發量多大,服務端只會最多3個線程進行同時處理,使服務端的壓力不會那么大。
運行結果:

通過運行結果,可以看到線程只開了1,2,3三個線程。
到這里,socket的簡易教程便結束了。O(∩_∩)O~~
