更新時間:2020-10-29 來源:黑馬程序員 瀏覽量:
1.NIO群聊實現步驟
·構建Selector以及服務端監聽通
·道啟動監聽并處理建立連接請求
·處理讀數據
·群發數據實現
·客戶端測試實現
2. 服務端實現
2.0 服務端完整代碼服務端的主要功能如下
(1)開放監聽端口,方法ChatServer構造方法
(2)處理鏈接請求,方法listener實現連接的建立
(2)讀取消息內容,方法readData
(4)轉發消息給當前所有在線的人,方法sendData2All
package com.hgy.chat; /** * 群聊服務器 */ public class ChatServer { private ServerSocketChannel serverSocketChannel; private Selector selector; /** * 初始化服務端 */ public ChatServer() { try { // 創建Selector以及ServerSocketChannel selector = Selector.open(); serverSocketChannel = serverSocketChannel.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.socket().bind(new InetSocketAddress(8888)); //將服務端監聽通道注冊到Selector中 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); } catch (Exception e) { e.printStackTrace(); } } /** * 監聽客戶端操作 */ public void listener() { while (true) { try { if (selector.select(1000) == 0) { continue; } //獲得所有有事件的key Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); //如果當前key是處理鏈接類型 if (key.isAcceptable()) { SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); } // 當前鏈接是讀數據類型 if (key.isReadable()) { readData(key); } iterator.remove(); } } catch (IOException e) { e.printStackTrace(); } } } /** * 讀取數據并群發給所有的用戶 * @param key */ private void readData(SelectionKey key) { try { if (key.isReadable()) { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); channel.read(byteBuffer); String s = new String(byteBuffer.array()); // 寫到其他所有客戶端 sendData2All(s); } } catch (IOException e) { e.printStackTrace(); } } /** * 群發給所有的用戶 * @param msg 需要發送的消息 */ private void sendData2All(String msg) { try { // 當前在selector上注冊的所有key就是所有用戶 Set<SelectionKey> keys = selector.keys(); for (SelectionKey key : keys) { // 獲取每個用戶的通道 SelectableChannel channel = key.channel(); // 實現數據發送 if (channel instanceof SocketChannel) { System.out.println(":::" + msg); ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes()); SocketChannel socketChannel = (SocketChannel) channel; socketChannel.write(byteBuffer); } } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { ChatServer chatServer = new ChatServer(); chatServer.listener(); } }
2.1 構建Selector以及服務端監聽通道
當ChatServer對象被創建時具體實現步驟如下
(1)創建serverSocketChannel對象
(2)設置處理模式為非阻塞模式
(3)綁定監聽端口
(4)將channel注冊到selector中
public class ChatServer { private ServerSocketChannel serverSocketChannel; private Selector selector; /** * 初始化服務端 */ public ChatServer() { try { // 創建Selector以及ServerSocketChannel selector = Selector.open(); serverSocketChannel = serverSocketChannel.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.socket().bind(new InetSocketAddress(8888)); //將服務端監聽通道注冊到Selector中 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); } catch (Exception e) { e.printStackTrace(); } } }
2.2 實現監聽并處理建立連接請求
連接請求處理實現步驟
(1)獲得所有有事件的key,通過key就可以拿到用戶的SocketChannel
(2)循環遍歷每一個key,判斷當前是讀事件,還是建立連接事件
(3)如果是建立連接事件則直接將該通道注冊到selector中
(4)如果是讀數據事件就交給具體的讀數據方法處理數據
2.3 處理讀數據數據
處理的具體實現步驟
(1)通過key獲取和用戶連接的通道(相當于輸入流)
(2)獲取通道的數據并打印
(3)將數據轉發給其他在線用戶
public void listener() { while (true) { try { if (selector.select(1000) == 0) { continue; } //獲得所有有事件的key Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); //如果當前key是處理鏈接類型 if (key.isAcceptable()) { SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); } // 當前鏈接是讀數據類型 if (key.isReadable()) { readData(key); } iterator.remove(); } } catch (IOException e) { e.printStackTrace(); } } }
2.4 群發數據實現
數據群發實現步驟
(1)當前在線用戶實際上就是selector中所有注冊的key,也就是在線的用戶
(2)通過key拿到和用戶的鏈接講消息轉發出去
/** * 監聽客戶端操作 */ /** * 讀取數據并群發給所有的用戶 * @param key */ private void readData(SelectionKey key) { try { if (key.isReadable()) { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); channel.read(byteBuffer); String s = new String(byteBuffer.array()); // 寫到其他所有客戶端 sendData2All(s); } } catch (IOException e) { e.printStackTrace(); } }
2.5 啟動服務端
public static void main(String[] args) { ChatServer chatServer = new ChatServer(); chatServer.listener(); }
3. 客戶端實現
客戶端實現
(1)首先創建SocketChannel對象并鏈接到具體的服務器
(2)將通道注冊到selector中
(3)開啟一個新的線程監聽selector中所有key的事件
(4)在主線程中循環阻塞獲取用戶的輸入
public class ChatClient { public static void main(String[] args) throws Exception { // 客戶端代碼, 建立連接 Selector selector = Selector.open(); SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888)); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); // 開啟一個新的線程輪詢當前客戶是否有可讀消息 new Thread(() -> { while (true) { try { int select = selector.select(1000); // 有可讀消息進行解析打印 if (select > 0) { for (SelectionKey key : selector.selectedKeys()) { if (key.isReadable()) { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); channel.read(byteBuffer); System.out.println(":==:" + new String(byteBuffer.array())); // 寫到其他所有客戶端 System.out.println(new String(byteBuffer.array())); } } } } catch (Exception e) { e.printStackTrace(); } } }).start(); // 主線程中循環獲取用戶輸入的聊天消息 while(true) { Scanner scanner = new Scanner(System.in); //發送用戶的消息 socketChannel.write(ByteBuffer.wrap(scanner.nextLine().getBytes())); } } }
猜你喜歡: