基于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();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章