基於NIO的同步非阻塞編程完整案例,客戶端發送請求,服務端獲取數據並返回給客戶端數據,客戶端獲取返回數據


這塊還是挺復雜的,挺難理解,但是多練幾遍,多看看研究研究其實也就那樣,就是一個Selector輪詢的過程,這里想要雙向通信,客戶端和服務端都需要一個Selector,並一直輪詢,

直接貼代碼:

Server:服務端:

package cn.hou.socket01._03nio01;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

//nio 服務端
public class Server implements Runnable {
	//1 多路復用器
	private Selector selector;
	//2 建立緩沖區
	private ByteBuffer readBuf=ByteBuffer.allocate(1024);
	private ByteBuffer writeBuf=ByteBuffer.allocate(1024);
	//構造函數
	public Server(int port){
		try {
			//1 打開多路復用器
			this.selector=Selector.open();
			//2 打開服務器通道
			ServerSocketChannel ssc = ServerSocketChannel.open();
			//3 設置服務器通道為非阻塞方式
			ssc.configureBlocking(false);
			//4 綁定ip
			ssc.bind(new InetSocketAddress(port));
			//5 把服務器通道注冊到多路復用器上,只有非阻塞信道才可以注冊選擇器.並在注冊過程中指出該信道可以進行Accept操作
			ssc.register(this.selector, SelectionKey.OP_ACCEPT);
			System.out.println("服務器已經啟動.....");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	@Override
	public void run() {
		while(true){//一直循環
			try {
				this.selector.select();//多路復用器開始監聽
				//獲取已經注冊在多了復用器上的key通道集
				Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
				//遍歷
				while (keys.hasNext()) {
					SelectionKey key = keys.next();//獲取key
					//如果是有效的
					if(key.isValid()){
						// 如果為阻塞狀態,一般是服務端通道
						if(key.isAcceptable()){
							this.accept(key);
						}
						// 如果為可讀狀態,一般是客戶端通道
						if(key.isReadable()){
							this.read(key);
						}
					}
					//從容器中移除處理過的key
					keys.remove();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
	}
	//從客戶端通道獲取數據並進行處理
	private void read(SelectionKey key) {
		try {
			//1 清空緩沖區舊的數據
			this.readBuf.clear();
			//2 獲取之前注冊的socket通道對象
			SocketChannel sc = (SocketChannel) key.channel();
			//3 讀取數據
			int count = sc.read(this.readBuf);
			//4 如果沒有數據
			if(count == -1){
				key.channel().close();
				key.cancel();
				return;
			}
			//5 有數據則進行讀取 讀取之前需要進行復位方法(把position 和limit進行復位)
			this.readBuf.flip();
			//6 根據緩沖區的數據長度創建相應大小的byte數組,接收緩沖區的數據
			byte[] bytes = new byte[this.readBuf.remaining()];
			//7 接收緩沖區數據
			this.readBuf.get(bytes);
			//8 打印結果
			String body = new String(bytes).trim();
			System.out.println("服務端接受到客戶端請求的數據: " + body);
			//9 告訴客戶端已收到數據
			writeBuf.put("你好,客戶端,我已收到數據".getBytes());
			//對緩沖區進行復位
			writeBuf.flip();
			//寫出數據到服務端
			sc.write(writeBuf);
			//清空緩沖區數據
			writeBuf.clear();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	//接受一個客戶端socket進行處理
	private void accept(SelectionKey key) {
		try {
			//1 獲取服務通道
			ServerSocketChannel ssc =  (ServerSocketChannel) key.channel();
			//2 執行阻塞方法,當有客戶端請求時,返回客戶端通信通道
			SocketChannel sc = ssc.accept();
			//3 設置阻塞模式
			sc.configureBlocking(false);
			//4 注冊到多路復用器上,並設置可讀標識
			sc.register(this.selector, SelectionKey.OP_READ);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	public static void main(String[] args) {
		//啟動服務器
		new Thread(new Server(9527)).start();
	}
	

}

  Client客戶端:

package cn.hou.socket01._03nio01;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

//nio 客戶端
public class Client{
	//客戶端信道選擇器,輪詢讀取服務端返回數據
	private Selector selector;
	//連接信道
	private SocketChannel sc;
	public Client(){
		try {
			this.sc=SocketChannel.open();//打開信道
			sc.connect(new InetSocketAddress("127.0.0.1",9527));////連接服務端
			sc.configureBlocking(false);//設置非阻塞
			selector = Selector.open();//必須打開
			//將當前客戶端注冊到多路復用器上,並設置為可讀狀態
			sc.register(this.selector, SelectionKey.OP_READ);
			//開啟線程,一直輪詢
			new Thread(()->{
				while(true){//一直循環
					try {
						this.selector.select();//多路復用器開始監聽
						//獲取已經注冊在多了復用器上的key通道集
						Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
						//遍歷
						while (keys.hasNext()) {
							SelectionKey key = keys.next();//獲取key
							//如果是有效的
							if(key.isValid()){
								// 如果為可讀狀態,讀取服務端返回的數據
								if(key.isReadable()){
									this.read(key);
								}
							}
							//從容器中移除處理過的key
							keys.remove();
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}).start();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	//客戶端獲取服務端返回的數據
	private void read(SelectionKey key) {
		try {
			//建立寫緩沖區
			ByteBuffer readBuf = ByteBuffer.allocate(1024);
			//2 獲取之前注冊的socket通道對象
			SocketChannel sc = (SocketChannel) key.channel();
			//3 讀取數據
			int count = sc.read(readBuf);
			//4 如果沒有數據
			if(count == -1){
				key.channel().close();
				key.cancel();
				return;
			}
			//5 有數據則進行讀取 讀取之前需要進行復位方法(把position 和limit進行復位)
			readBuf.flip();
			//6 根據緩沖區的數據長度創建相應大小的byte數組,接收緩沖區的數據
			byte[] bytes = new byte[readBuf.remaining()];
			//7 接收緩沖區數據
			readBuf.get(bytes);
			//8 打印結果
			String body = new String(bytes).trim();
			System.out.println("客戶端已接受到服務端返回的數據: " + body);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		//建立寫緩沖區
		ByteBuffer writebuf = ByteBuffer.allocate(1024);
		Client client = new Client();
		try {
			while(true){
				//定義一個字節數組,然后使用系統錄入功能:
				byte[] bytes = new byte[1024];
				System.in.read(bytes);
				//把數據放到緩沖區中
				writebuf.put(bytes);
				//對緩沖區進行復位
				writebuf.flip();
				//寫出數據到服務端
				client.sc.write(writebuf);
				//清空緩沖區數據
				writebuf.clear();
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(client.sc != null){
				try {
					client.sc.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

  ,先啟動服務端,然后再啟動客戶端:

效果如下:

Server:

 

Client:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM