1. 程式人生 > >基於Socket的服務端多執行緒模式——服務端和客戶端程式碼

基於Socket的服務端多執行緒模式——服務端和客戶端程式碼

本文程式碼來源於《實戰java高併發程式設計》葛一鳴 郭超 著

學習這本書的過程中,感覺這一部分比較重要,自己做下總結,也希望能給大家提供些幫助。

      本程式碼模擬簡單的Echo伺服器,對於Echo伺服器,他會讀取客戶端的一個輸入,並將這個輸入原封不動地返回給客戶端。雖然例項很簡單,但麻雀雖小五臟俱全,需要一套完整的Socket處理機制。適合拿來學習。下面貼出本例程式碼。

 1、服務端程式碼:

       伺服器會為每一個客戶端連線啟動一個執行緒,這個新的執行緒會全心全意為這個客戶端服務。同時,為了接受客戶端連線,伺服器還會額外使用一個派發執行緒,如上圖:

package socket.demo;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 
 * @author FHY
 * 多執行緒應用的服務端程式碼
 *
 */
public class MultiThreadEchoServer {
	//建立一個執行緒池,不限制連線的執行緒數量
	public static ExecutorService tp = Executors.newCachedThreadPool();
	
	static class HandleMsg implements Runnable{
		Socket clientSocket;
		public HandleMsg(Socket clientSocket){
			this.clientSocket = clientSocket;
		}
		
		@Override
		public void run() {
			BufferedReader is = null;
			PrintWriter os = null;	
			try{
				//輸入流 (使用了裝飾模式)
				is = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
				//輸出流
				os = new PrintWriter(clientSocket.getOutputStream(), true);
				String inputLine = null;
				long b = System.currentTimeMillis();
				while ((inputLine = is.readLine())!= null ){
					os.println(inputLine);
				}
				long e = System.currentTimeMillis();
				System.out.println("Spend: "+(e-b));
			}catch(IOException e){
				e.printStackTrace();				
			}finally{
				try{
					if(is != null) is.close();
					if(os != null) os.close();
					clientSocket.close();
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		}
	}

	
	public static void main(String[] args) {
		ServerSocket echoSocket = null;
		Socket clientSocket = null;
		try{
			//設定服務端的埠號
			echoSocket = new ServerSocket(8000);
		}catch(IOException e){
			System.out.println(e);
		}
		
		while(true){
			try{
				//接受客戶端
				clientSocket = echoSocket.accept();
				System.out.println(clientSocket.getRemoteSocketAddress() + " connet!");
				tp.execute(new HandleMsg(clientSocket));
			}catch(IOException e){
				System.out.println(e);
			}
		}
	}

}

       這就是一個支援多執行緒的服務端的核心內容。它的特點是,在相同可支援的執行緒範圍內,可以儘量多地支援客戶端的數量,同時和單執行緒伺服器相比,它可以更好的使用多核CPU。

2、客戶端程式碼:

package socket.demo;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/**
 * 
 * @author FHY
 * 多執行緒應用的客戶端程式碼
 *
 */

public class ClientSocketDemo {
	public static void main(String[] args) {	
		Socket client = null;
		PrintWriter writer = null;
		BufferedReader reader = null;
		try{
			client = new Socket();
			//客戶端連線到服務端
			client.connect(new InetSocketAddress("localhost", 8000), 1000*6);
			writer = new PrintWriter(client.getOutputStream(), true);
			writer.println("Hello!");
			writer.flush();
			
			reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
			System.out.println("from server:" +reader.readLine());
		}catch(UnknownHostException e){
			e.printStackTrace();
		}catch (IOException e) {
			e.printStackTrace();
		}finally {
			try{
				if(writer != null) writer.close();
				if(reader != null) reader.close();
				if(client != null) client.close();
			}catch(IOException e){
				System.out.println(e);
			}
			
		}
	}	
}

總結:這種多執行緒的伺服器開發模式是及其常用的,對於絕大多數應用來說,這種模式可以很好地工作,但是如果你想讓程式工作地更加有效,就必須知道這種模式的一個重大弱點——那就是它傾向於讓CPU進行IO等待。

使用java的NIO就可以將上面的網路IO等待時間從業務處理執行緒中抽取出來。