天天看點

Day389.使用JavaNIO實作簡易線上多人聊天室 -NIO使用JavaNIO實作簡易線上多人聊天室

使用JavaNIO實作簡易線上多人聊天室

Day389.使用JavaNIO實作簡易線上多人聊天室 -NIO使用JavaNIO實作簡易線上多人聊天室

一、 服務端代碼

package chatroom.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

/******
 @author 阿昌
 @create 2021-09-13 20:23
 聊天室服務端
 *******
 */
public class ChatServer {

    //服務端啟動的方法
    public void startServer() throws IOException {
        //1、建立Selector選擇器
        Selector selector = Selector.open();

        //2、建立ServerSocketChannel通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //3、為channel通道綁定端口号
        serverSocketChannel.bind(new InetSocketAddress(9090));
        serverSocketChannel.configureBlocking(false);//設定非阻塞模式

        //4、把serverSocketChannel綁定到selector上
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("伺服器啟動.......");

        //5、循環監聽是否有連接配接連入
        while (true) {
            int select = selector.select();

            //如果為0,則為沒連接配接,沒有擷取到,就跳出循環
            if (select == 0) {
                continue;
            }

            //擷取可用channel
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            //周遊
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();

                //移除 set 集合目前 selectionKey
                iterator.remove();

                //6、根據就緒狀态,調用對應方法實作具體業務操作
                if (selectionKey.isAcceptable()) {
                    //6.1 如果 accept 狀态
                    acceptOperator(serverSocketChannel, selector);
                }
                if (selectionKey.isReadable()) {
                    //6.2 如果可讀狀态
                    readOperator(selector, selectionKey);
                }
            }
        }
    }

    //處理可讀狀态操作
    private void readOperator(Selector selector, SelectionKey selectionKey) throws IOException {
        //1 從 SelectionKey 擷取到已經就緒的通道
        SocketChannel channel = (SocketChannel) selectionKey.channel();

        //2 建立 buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        //3 循環讀取用戶端消息
        int readLength = channel.read(byteBuffer);
        String message = "";//用于接收解碼後的資訊

        //表示裡面有資料
        if (readLength > 0) {
            //切換讀模式
            byteBuffer.flip();
            //讀取内容
            message += Charset.forName("UTF-8").decode(byteBuffer);
        }

        //4 将 channel 再次注冊到選擇器上,監聽可讀狀态
        channel.register(selector, SelectionKey.OP_READ);

        //5 把用戶端發送消息,廣播到其他用戶端
        if (message.length() > 0) {
            //廣播給其他用戶端
            System.out.println(message);
            castOtherClient(message, selector, channel);
        }
    }

    //廣播到其他用戶端
    private void castOtherClient(String message, Selector selector, SocketChannel channel) throws IOException {
        //1 擷取所有已經接入 channel
        Set<SelectionKey> selectionKeySet = selector.keys();
        //2 循環想所有 channel 廣播消息
        for (SelectionKey selectionKey : selectionKeySet) {
            //擷取每個 channel
            Channel tarChannel = selectionKey.channel();
            //不需要給自己發送
            if (tarChannel instanceof SocketChannel && tarChannel != channel) {//不向自己廣播
                ((SocketChannel) tarChannel).write(Charset.forName("UTF-8").encode(message));
            }
        }
    }


    //處理接入狀态操作
    private void acceptOperator(ServerSocketChannel serverSocketChannel, Selector selector) throws IOException {
        //1 接入狀态,建立 socketChannel
        SocketChannel accept = serverSocketChannel.accept();
        //2 把 socketChannel 設定非阻塞模式
        accept.configureBlocking(false);
        //3 把 channel 注冊到 selector 選擇器上,監聽可讀狀态
        accept.register(selector, SelectionKey.OP_READ);
        //4 用戶端回複資訊
        accept.write(Charset.forName("UTF-8").encode("歡迎進入聊天室,請注意隐私安全"));
    }

    public static void main(String[] args) throws IOException {
        new ChatServer().startServer();
    }

}
           

二、用戶端代碼

package chatroom.client;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;

/******
 @author 阿昌
 @create 2021-09-13 20:45
 聊天室用戶端
 *******
 */
public class ChatClient {

    //啟動方法
    public void startClient(String name) throws IOException {
        //連接配接服務端
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9090));
        //接收服務端響應資料
        Selector selector = Selector.open();
        socketChannel.configureBlocking(false);//設定非阻塞連接配接
        socketChannel.register(selector, SelectionKey.OP_READ);//将通道注冊到selector上
        //建立線程,來接收服務端的響應資訊
        new Thread(new ClientThread(selector)).start();

        //向服務端發送資訊
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()){
            String msg = scanner.nextLine();
            if (msg.length()>0){
                //寫入通道消息,讓他發送給服務端
                socketChannel.write(Charset.forName("UTF-8").encode(name+": "+msg));
            }
        }
    }

}
           

三、用戶端異步監聽服務端響應Runnable類

package chatroom.client;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

/******
 @author 阿昌
 @create 2021-09-13 20:57
 用戶端異步監聽服務端響應Runnable類
 *******
 */
public class ClientThread implements Runnable {

    private Selector selector;

    public ClientThread(Selector selector) {
        this.selector = selector;
    }

    @Override
    public void run() {
        try {
            while (true) {
                //擷取 channel 數量
                int readChannels = selector.select();
                if (readChannels == 0) {
                    continue;
                }
                //擷取可用的 channel
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                //周遊集合
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    //移除 set 集合目前 selectionKey
                    iterator.remove();
                    //如果可讀狀态
                    if (selectionKey.isReadable()) {
                        //處理可讀狀态操作
                        readOperator(selector, selectionKey);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    //處理可讀狀态操作
    private void readOperator(Selector selector, SelectionKey selectionKey) throws IOException {
        //1 從 SelectionKey 擷取到已經就緒的通道
        SocketChannel socketChannel =(SocketChannel) selectionKey.channel();
        //2 建立 buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //3 循環讀取用戶端消息
        int readLength = socketChannel.read(byteBuffer);
        String message = "";//用于存儲解碼後的消息
        if (readLength > 0) {
            //切換讀模式
            byteBuffer.flip();
            //讀取内容
            message += Charset.forName("UTF-8").decode(byteBuffer);
        }
        //4 将 channel 再次注冊到選擇器上,監聽可讀狀态
        socketChannel.register(selector, SelectionKey.OP_READ);
        //5 把用戶端發送消息,廣播到其他用戶端
        if (message.length() > 0) {
            //廣播給其他用戶端
            System.out.println(message);
        }
    }
    
}
           

四、A/B用戶端

  • AClient
public class AClient {
    public static void main(String[] args) {
        try {
            new ChatClient().startClient("阿昌一号");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
           
  • BClient
public class BClient {
    public static void main(String[] args) {
        try {
            new ChatClient().startClient("阿昌二号");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
           

五、示範

服務端啟動後,AClient和BClient都發送一條消息後的效果

  • 服務端控制台
Day389.使用JavaNIO實作簡易線上多人聊天室 -NIO使用JavaNIO實作簡易線上多人聊天室
  • AClient用戶端控制台
Day389.使用JavaNIO實作簡易線上多人聊天室 -NIO使用JavaNIO實作簡易線上多人聊天室
  • BClient用戶端控制台
Day389.使用JavaNIO實作簡易線上多人聊天室 -NIO使用JavaNIO實作簡易線上多人聊天室