基於NIO的羣聊系統簡單實現

實例要求:
(1)編寫一個NIO多人羣聊系統,實現服務器端和客戶端之間的數據簡單通訊(非阻塞)
(2)服務器端:可以監測用戶上線、離線,並實現消息轉發功能
(3)客戶端:通過channel可以無阻塞發送消息給其它所有用戶,同時可以接受其它用戶發送的消息(都是由服務器轉發得到)
(4)目的:進一步理解NIO非阻塞網絡編程機制

  • 服務器
package nio.groupchat;

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

/**
 * 羣聊系統---服務器
 * @author Mushroom
 * @date 2020-03-15 20:52
 */
public class GroupChatServer {

    //定義相關屬性
    private Selector selector; //多路複用選擇器
    private ServerSocketChannel listenChannel;//服務器監聽
    private static final int PORT = 6666;//端口

    /**
     * 初始化工作
     */
    public GroupChatServer() throws IOException {
        //實例化多路複用選擇器
        selector = Selector.open();
        //實例化服務器監聽
        listenChannel = ServerSocketChannel.open();
        //綁定服務器端口
        listenChannel.socket().bind(new InetSocketAddress(PORT));
        //設置非阻塞
        listenChannel.configureBlocking(false);
        //註冊ServerSocketChannel到Selector,並監聽連接事件
        listenChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服務器已啓動.....");
    }

    /**
     * 監聽
     */
    private void listen() throws IOException {
        while (true) {
            //阻塞,直到通道中有事件發生
            selector.select();
            //通道中有事件發生,得到SelectionKey集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            //遍歷SelectionKey集合,根據事件的類型,執行相應的操作
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                //通道發生連接事件
                if (selectionKey.isAcceptable()) {
                    //得到SocketChannel
                    SocketChannel socketChannel = listenChannel.accept();
                    //設置非阻塞
                    socketChannel.configureBlocking(false);
                    //註冊SocketChannel到Selector,監聽讀取事件
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    //展示用戶上線信息
                    System.out.println(socketChannel.getRemoteAddress() + "用戶上線啦......");
                }
                //通道發生讀取事件
                if (selectionKey.isReadable()) {
                    //讀取客戶端消息
                    readData(selectionKey);
                }
                //刪除當前處理完SelectionKey,防止多線程下重複操作
                iterator.remove();
            }
        }
    }

    /**
     * 讀取客戶端消息
     */
    private void readData(SelectionKey selectionKey) {
        SocketChannel socketChannel = null;
        try {
            //通過SelectionKey反向得到對應的SocketChannel
            socketChannel = (SocketChannel) selectionKey.channel();
            //創建緩衝區Buffer
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            //將通道中的數據讀入緩衝區
            int readCount = socketChannel.read(byteBuffer);
            //有讀取到字節,如果沒有讀取到字節可能是客戶端下線或者未發送消息,需要處理客戶端下線的異常
            if (readCount > 0) {
                //將緩衝區的數據轉換成字符串
                String msg = new String(byteBuffer.array());
                //顯示讀取到的數據
                System.out.println(msg);
                //轉發消息
                sendInfoToOtherClients(msg, socketChannel);
            }
            //用戶下線的相應處理
        } catch (IOException e) {
            try {
                //顯示用戶下線的信息
                System.out.println(socketChannel.getRemoteAddress() + "已下線.....");
                //取消註冊
                selectionKey.cancel();
                //關閉通道
                socketChannel.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    /**
     * 向客戶端(通道)轉發消息
     * 實際上就是向通道寫入信息
     */
    private void sendInfoToOtherClients(String msg, SocketChannel currentChannel) throws IOException {
        System.out.println("服務器開始轉發消息.....");
        //得到所有註冊到Selector上的通道,注意selector.selectedKeys()區分開,後者是得到所有有事件發生的SelectionKey
        Set<SelectionKey> keys = selector.keys();
        //遍歷SelectionKey集合
        for (SelectionKey selectionKey : keys) {
            //得到對應的Channel,這裏同時含有ServerSocketChannel,因此不能直接強轉爲SocketChannel
            Channel targetChannel = selectionKey.channel();
            //排除掉髮送該消息的客戶端,排除掉ServerSocketChannel
            if (targetChannel instanceof SocketChannel && targetChannel != currentChannel) {
                //創建緩衝區並寫入數據
                ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
                //強轉爲SocketChannel
                SocketChannel socketChannel = (SocketChannel) targetChannel;
                //把緩衝區的數據寫入通道
                socketChannel.write(byteBuffer);
            }
        }
    }

    public static void main(String[] args) {
        try {
            //啓動服務器
            GroupChatServer groupChatServer = new GroupChatServer();
            //開啓監聽,當存在客戶端的消息就讀取並轉發
            groupChatServer.listen();
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("服務器啓動異常......");
        }
    }
}
  • 客戶端
package nio.groupchat;

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;
import java.util.Scanner;
import java.util.Set;

/**
 * 羣聊系統---客戶端
 * @author Mushroom
 * @date 2020-03-16 10:48
 */
public class GroupChatClient {
    //定義相關屬性
    private final String HOST = "127.0.0.1";//服務器ip地址
    private final int PORT = 6666;//服務器端口號
    private Selector selector;//多路複用選擇器
    private SocketChannel socketChannel;//客戶端通道
    private String username;//客戶端名稱

    /**
     * 初始化工作
     */
    public GroupChatClient() throws IOException {
        //實例化多路複用選擇器
        selector = Selector.open();
        //實例化客戶端通道並綁定服務器地址和端口
        socketChannel = SocketChannel.open(new InetSocketAddress(HOST, PORT));
        //設置非阻塞
        socketChannel.configureBlocking(false);
        //註冊socketChannel到Selector,並監聽讀取事件
        socketChannel.register(selector, SelectionKey.OP_READ);
        //設置客戶端名稱
        username = socketChannel.getLocalAddress().toString().substring(1);
        System.out.println("客戶端已啓動......");
    }


    /**
     * 向服務器發送消息
     */
    private void sendInfo(String msg) throws IOException {
        msg = username + "說:" + msg;
        //把緩衝區的數據寫入通道
        socketChannel.write(ByteBuffer.wrap(msg.getBytes()));

    }

    /**
     * 讀取向服務器轉發的消息
     */
    private void readInfo() throws IOException {
        //一直阻塞,直到有事件發生後往下執行
        selector.select();
        //得到SelectionKey集合
        Set<SelectionKey> selectionKeys = selector.selectedKeys();
        //遍歷SelectionKey集合
        Iterator<SelectionKey> iterator = selectionKeys.iterator();
        while (iterator.hasNext()) {
            //得到含有事件的SelectionKey
            SelectionKey selectionKey = iterator.next();
            //對讀取事件作出相應處理
            if (selectionKey.isReadable()) {
                //反向獲得通道
                SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                //設置非阻塞
                socketChannel.configureBlocking(false);
                //創建緩衝區
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                //將通道的數據讀到緩衝區
                socketChannel.read(byteBuffer);
                //輸出緩衝區中得到的消息
                System.out.println(new String(byteBuffer.array()));
            }
            //刪除當前處理完的SelectionKey
            iterator.remove();
        }
    }


    public static void main(String[] args) {
        try {
            //啓動客戶端
            final GroupChatClient groupChatClient = new GroupChatClient();
            //啓動一個線程,循環讀取服務器轉發的消息
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        try {
                            //讀取服務器轉發的消息
                            groupChatClient.readInfo();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();

            //用於向服務器發送信息
            Scanner scanner = new Scanner(System.in);
            while (true) {
                System.out.print("請輸入發送的信息:");
                //獲得用戶發送的消息
                String msg = scanner.nextLine();
                //將消息發送給服務器
                groupChatClient.sendInfo(msg);
                System.out.println();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章