1. 程式人生 > >NIO與Socket

NIO與Socket

一、Socket 的使用

  1、單執行緒Socket的使用    

/**
 * 單執行緒版本
 * 問題描述:只能服務單個客戶端
 * 解決方案:多執行緒版本
 */
public class Socket_V1 {
	public static void main(String[] args) throws Exception {
		ServerSocket serverSocket = new ServerSocket(1234);
		System.out.println("啟動伺服器:");
		while(true) {
			System.out.println("等待客戶端連結(阻塞)……");
			Socket socket = serverSocket.accept();
			System.out.println("有連結進來了……");
			handle(socket);
		}
	}
	private static void handle(Socket socket) {
		try {
			InputStream inputStream = socket.getInputStream();
			byte[] b = new byte[1024];
			String str = null;
			while(true) {
				str = null;
				System.out.println("等待輸入……");
				int read = inputStream.read(b);
				if(read == -1) {
					System.out.println("客戶端關閉。");
					break;
				}
				str = new String(b);
				System.out.println(str);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

  2、多執行緒版本

/**
 * 多執行緒版本
 * 問題描述:多執行緒開銷大
 * 解決方案:NIO
 */
public class Socket_V2 {

	public static void main(String[] args) throws Exception {
		ServerSocket serverSocket = new ServerSocket(1234);
		// 建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。
		ExecutorService threadPool = Executors.newFixedThreadPool(10);
		System.out.println("啟動伺服器:");
		while(true) {
			System.out.println("等待客戶端連結(阻塞)……");
			final Socket socket = serverSocket.accept();
			System.out.println("有連結進來了……");
			//使用執行緒池來,支援併發
			threadPool.execute(new Runnable() {
				public void run() {
					handle(socket);
				}
			});
		}
	}
	private static void handle(Socket socket) {
		try {
			InputStream inputStream = socket.getInputStream();
			byte[] b = new byte[1024];
			String str = null;
			while(true) {
				str = null;
				System.out.println("等待輸入……");
				int read = inputStream.read(b);
				if(read == -1) {
					System.out.println("客戶端關閉。");
					break;
				}
				str = new String(b);
				System.out.println(str);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

  3、NIO 

/**
 * NIO版本
 * 原理:IO多路複用
 * NIO與舊Socket對比:
 * 	ServerSocketChannel	<-等價於->	ServerSocket
 * 	SocketChannel		<-等價於->	Socket
 * 	Selector   		選擇器
 * 	SelectionKey 	事件型別
 */
public class Socket_NIO {
	public static void main(String[] args) throws Exception {
		//獲得Socket通道
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		//將通道設定為非阻塞模式
		serverSocketChannel.configureBlocking(false);
		//將通道繫結到指定的埠
		serverSocketChannel.socket().bind(new InetSocketAddress(1234));
		//獲取通道選擇器
		Selector selector = Selector.open();
		//將連線事件,註冊到選擇器內(步驟1)
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		System.out.println("伺服器啟動成功。埠已經監聽……");
		while(true) {
			selector.select();//阻塞等待已經註冊的事件
			Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
			while(iterator.hasNext()) {
				SelectionKey next = iterator.next();
				iterator.remove();
				if(next.isAcceptable()) { //如果是連線事件,註冊讀寫事件
					System.out.println("獲取到連線事件。");
					ServerSocketChannel sschannel = (ServerSocketChannel) next.channel();  //該強轉是步驟1記憶體放的值
					SocketChannel channel = sschannel.accept(); //獲取客戶端連線的通道
					channel.configureBlocking(false);//設定為非阻塞
					channel.register(selector, SelectionKey.OP_READ); //步驟2
				}else if(next.isReadable()) { //如果是可讀事件,則執行讀操作
					System.out.println("獲取到可讀事件。");
					SocketChannel channel = (SocketChannel) next.channel(); //獲取socket通道,該強轉是步驟2記憶體放的值
					ByteBuffer dst = ByteBuffer.allocate(1024); //緩衝區
					int read = channel.read(dst);
					if(read < 0 ) {
						System.out.println("客戶端關閉。");
						next.cancel();
						break;
					}
					System.out.println("客戶端輸入:"+new String(dst.array()));
				} //還有其他事件……略
			}
		}
	}
}