1. 程式人生 > >【Netty入門和實踐】1.傳統的socket分析

【Netty入門和實踐】1.傳統的socket分析

我們知道,使用Java進行TCP/UDP協議的網路通訊一般使用Java的Net包下的Socket服務進行編寫,有Server服務端和Client客戶端,服務端用於監聽客戶端的連線和接收客戶端發來的資訊,而客戶端可以接收服務端傳送的資訊,形成多個客戶端與一個服務端連線,各個客戶端可以相互通訊的機制。

下面我們就編寫一個簡單的基於Socket服務的TCP協議傳輸資訊的服務端,然後來進行分析。

在Eclipse中新建一個Java工程,名為“IOServer”即是服務端:

然後在src下面建立一個cn.com.io.old包,下面建立一個名為“Old_IO_Server”的類:

在該類中編寫服務端程式碼:
package cn.com.io.old;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/***
 * 傳統socket服務端
 */
public class Old_IO_Server {
    public static void main(String[] args) throws IOException {
		//建立Socket服務,監聽10010埠
    	ServerSocket server = new ServerSocket(10010);
    	System.out.println("服務端啟動!");
    	while(true){
    		//獲取一個套接字(阻塞)
    		final Socket socket = server.accept();
    		System.out.println("出現一個新客戶端!");
    		//業務處理
    		handler(socket);
    	}
	}

    /**
     * 讀取資料
     * @param socket
     * @throws Exception 
     * */
	private static void handler(Socket socket){
		try {
			byte [] bytes = new byte[1024];
			InputStream input = socket.getInputStream();
			
			int read = 0;
			while(read!=-1){
				//讀取資料(阻塞)
				read = input.read(bytes);
				System.out.println(new String(bytes,0,read));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			try {
				System.out.println("socket 關閉");
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
我們在mian函式的第一行打一個斷點,使用Debug模式執行該程式:

可以看到在客戶端沒有連線進來的時候,服務端會在server.accept();的位置阻塞等待。
然後使用Windows的CMD控制檯的telnet進行客戶端連線:

可以看到程式向下走了:

當程式走到read = input.read(bytes);發生了阻塞等待,等待客戶端傳輸資訊:

此時在阻塞過程中,意味著這條執行緒是被這個Socket一直佔用著的,其它的Socket不能進來。(再次開啟一個CMD控制檯使用telnet,可以發現進不來)


當我們在CMD中輸入資訊(Ctrl+]進入客戶端,send發信息),可以看到服務端收到了資訊:

想要服務端處理多個客戶端的資訊,就需要為每一個客戶端分配一個執行緒。下面我們修改一下服務端:
package cn.com.io.old;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/***
 * 傳統socket服務端
 */
public class Old_IO_Server {
    public static void main(String[] args) throws IOException {
    	//建立一個快取執行緒池
    	ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
		//建立Socket服務,監聽10010埠
    	ServerSocket server = new ServerSocket(10010);
    	System.out.println("服務端啟動!");
    	while(true){
    		//獲取一個套接字(阻塞)
    		final Socket socket = server.accept();
    		System.out.println("出現一個新客戶端!");
    		//線上程池為新客戶端開一個執行緒
    		newCachedThreadPool.execute(new Runnable() {
				@Override
				public void run() {
					//業務處理
		    		handler(socket);
				}
			});
    	}
    }


    /**
     * 讀取資料
     * @param socket
     * @throws Exception 
     * */
	private static void handler(Socket socket){
		try {
			byte [] bytes = new byte[1024];
			InputStream input = socket.getInputStream();
			
			int read = 0;
			while(read!=-1){
				//讀取資料(阻塞)
				read = input.read(bytes);
				System.out.println(new String(bytes,0,read));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			try {
				System.out.println("socket 關閉");
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
可以看到我們建立了一個快取執行緒池,當服務端新連線了一個客戶端的時候,就建立一個新的執行緒為客戶端進行服務。

現在使用兩個客戶端連線10010埠,可以就看到兩個客戶端的資訊都可以接收到:

那麼我們是不是就可以使用這種結構實現一個長連線伺服器呢?這裡還是有一個很大的弊端,因為目前我們每個客戶端都為其分配了一個執行緒去執行,如果有一萬個客戶端進來,我們就要分配一萬個執行緒給客戶端使用,這樣的資源消耗是十分巨大的。就好像一個飯店每進來一個客人,就為其分配一個服務員給他服務一樣,對於飯店來說服務員的開銷也是巨大的。

基於以上幾點,傳統的Socket服務實現長連線服務是不合適的,但是可以實現短連線的伺服器,如老版本的tomcat。

總結:
傳統IO傳輸的特點:
(1)至少兩處阻塞點(等待客戶端連線,等待客戶端傳送資訊)
(2)單執行緒下只能有一個客戶端連線
(3)使用執行緒池可以連線多個客戶端,但是十分消耗效能。

使用NIO就可以解決傳統IO的網路傳輸問題,我們下篇進行介紹。