1. 程式人生 > >java網路程式設計(一):java傳統的阻塞IO以及多執行緒解決方案

java網路程式設計(一):java傳統的阻塞IO以及多執行緒解決方案

最近在看一些IO模型相關的東西,被同步IO、非同步IO、阻塞IO、非阻塞IO概念弄的有點暈,後面再慢慢學習和領悟。我們以socket IO程式設計為例子,我用的是JDK1.7.0_80,測試工具用的是SocketTest。我們先學習下最簡單、最原始的IO模型,在《Unix網路程式設計卷》中被稱為:blocking IO。


SingleThreadBlockingIO是我們用java socket程式設計實現的blocking IO。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class SingleThreadBlockingIO {

	public static void main(String[] args) throws Exception{
		
		ServerSocket serverSocket = new ServerSocket(8888);
		
		while (true)
        {
			// 阻塞直到有客戶端連線上
            Socket clientSocket = serverSocket.accept();
            try
            {
            	 process(clientSocket);
            }
            catch(Exception e)
            {
            	e.printStackTrace();
            	clientSocket.close();
            }
        }
	}
	
	private static void process( Socket clientSocket) throws Exception
	{
		System.out.println("client socket連線:" + clientSocket.getRemoteSocketAddress());
        BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
        while(true)
        {
        	// 阻塞直到客戶端傳送資料
            String readLine = in.readLine();
            System.out.println("來自客戶端的訊息:" + readLine);
            if("end".equals(readLine))
            {
                break;
            }
            else
            {
            	out.write("welcome from server.");
            	out.newLine();
            	out.flush();
            }
        }
	}
}

執行上面這段程式碼,用SocketTest做以下測試:

1.啟動一個SocketTest程序,連線到上面的服務端socket。


2.可以觀察到:服務端socket控制檯打印出了下面的資訊。


3.再啟動一個SocketTest程序,連線服務端socket。SocketTest工具並沒有提示我們不能連線。


4.發現服務端控制檯,並沒有打印出第二個SocketTest的連線資訊。也就是時候,第二個SocketTest客戶端在等待中。因為服務端socket已經被第一個SocketTest客戶端佔用,還沒有釋放,所以無法服務第二個客戶端。


5.第一個SocketTest客戶端傳送訊息,能夠成功收到服務端的訊息。


6.server端能夠收到和處理客戶端1的訊息。


7.在第二個SocketTest客戶端傳送訊息,可以看到並沒有收到服務端的回覆。


8.服務端控制檯也沒有列印客戶端2的訊息。


9.停掉第一個SocketTest客戶端,可以看到服務端控制檯輸出結果如下。


通過上面的測試步驟,我們可以看到:這種傳統的socket,一次只能服務一個客戶端,別的客戶端都必須排隊等候,效率很差,不能用於高併發的場景。

為了解決這個問題,我們可以引入多執行緒,為每個socket客戶端單獨開闢一個執行緒。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class MultithreadBlockingIO {

	public static void main(String[] args) throws Exception{
		ServerSocket serverSocket = new ServerSocket(8888);
		while (true)
        {
			final Socket clientSocket = serverSocket.accept();
			Thread t = new Thread(new Runnable() {
				
				@Override
				public void run() {
					try {
						process(clientSocket);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			});
			
			t.start();
        }
	}
	
	private static void process( Socket clientSocket) throws Exception
	{
        System.out.println("client socket連線:" + clientSocket.getRemoteSocketAddress());
        BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
        while(true)
        {
            String readLine = in.readLine();
            System.out.println("來自客戶端的訊息:" + readLine);
            if("end".equals(readLine))
            {
                break;
            }
            else
            {
            	out.write("welcome from server.");
            	out.newLine();
            	out.flush();
            }
        }
	}
}

可以像測試SingleThreadBlockingIO一樣進行測試,可以看到2個SocketTest客戶端之間沒有影響。也就是說服務能夠同時服務多個客戶端。


瞭解併發程式設計都知道:這種為每個客戶端分配一個執行緒的方案,在高併發的場景下仍然不行。因為建立執行緒是要消耗系統資源的,不能無限制的建立執行緒,而且執行緒太多會導致頻繁的上下文切換,這些都會影響效能。我們可以使用執行緒池,建立固定個數的執行緒,這樣不會導致系統因建立太多執行緒而崩潰,不過如果執行緒池被沾滿,那麼後續的客戶端socket還是需要排隊。正是由於使用了這種blocking IO模型,導致無論怎麼做,都不太好。後面我們會慢慢學習其他的IO模型。