NIO实例:Selector+SocketChannel实现多人聊天

1. 简介

1.1 Channel

Channel 称为通道,可以异步读写数据,可以从 Buffer(缓冲区)读写数据
在这里插入图片描述

1.1 Selector

Selector 能够检测多个 Channel(通道)是否有事件发生。如果有事件发生,可以取到事件的Channel进行处理。如果没有事件,就持续阻塞等待。

在这里插入图片描述
NIO基础知识这里就不过多介绍了,下面直接上实例

2. 群聊通讯

2.1 实现目标

  • 搭建服务端,接收客户端连接,接收客户端消息并进行消息转发
  • 搭建客户端,启动后连接服务端,可以发送消息,也可以接受其他客户端的消息(服务端转发的消息)

2.2 服务端Server

2.2.1 流程图

在这里插入图片描述

2.2.2 服务端源码

public class MultiChatServer {
    
    

    private Selector selector;
    private ServerSocketChannel serverSocketChannel;
    private static final int PORT = 6666;

    public static void main(String[] args) {
    
    
        MultiChatServer server = new MultiChatServer();
        server.start();
    }

    public MultiChatServer(){
    
    
        try {
    
    
        	//创建Selector
            selector = Selector.open();

			//创建ServerSocketChannel
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
            serverSocketChannel.configureBlocking(false);
            
            //ServerSocketChannel注册到Selector,并监听连接事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

	//服务开始
    public void start(){
    
    
        System.out.println("服务已启动,等待连接...");
        try {
    
    
            while (true) {
    
    
            	//Selector获取连接,如果当前没连接,会一直阻塞等待
                selector.select();
                //获取触发事件的SelectionKey,遍历KEY,其实也相当于遍历事件
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()){
    
    
                    SelectionKey selKey = iterator.next();
                    //处理事件
                    handleEvent(selKey);
                    //此处要将SelectionKey移除,否则事件会一直存在
                    iterator.remove();
                }
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    //处理事件
    public void handleEvent(SelectionKey selKey){
    
    
       try {
    
    
           //连接事件
           if (selKey.isAcceptable()){
    
    
           	   //获取连接的客户端SocketChannel,并注册到Selector,监听读取数据事件
               SocketChannel socketChannel = serverSocketChannel.accept();
               socketChannel.configureBlocking(false);
               socketChannel.register(selector,  SelectionKey.OP_READ);
               System.out.println(socketChannel.getRemoteAddress() + " 已连接");
           }
           //读取数据事件
           if (selKey.isReadable()){
    
    
           		//通过SelectionKey可以取到对应的SocketChannel,然后也就可以取到ByteBuffer的数据
               SocketChannel socketChannel = (SocketChannel) selKey.channel();
               try {
    
    
                   ByteBuffer buffer = ByteBuffer.allocate(1024);
                   while (socketChannel.read(buffer) > 0){
    
    
                       String message = new String(buffer.array());
                       System.out.println("服务器接收到消息:" + message);

                       transferToOthers(message, selKey);  //消息转发
                   }
               }
               catch (IOException e){
    
    
                   selKey.cancel();
                   socketChannel.close();
               }
           }
       }
       catch (Exception e){
    
    
           e.printStackTrace();
       }
    }

    //转发消息给其他客户端
    public void transferToOthers(String message, SelectionKey srcKey) throws IOException {
    
    
        Set<SelectionKey> allClients = selector.keys();
        for (SelectionKey clientKey : allClients) {
    
    
            SelectableChannel channel = clientKey.channel();
            //排除服务端(ServerSocketChannel)
            if (!(channel instanceof SocketChannel)){
    
    
                continue;
            }
            //排除发送该消息的客户端自己
            if (clientKey == srcKey){
    
    
                continue;
            }
            SocketChannel socketChannel = (SocketChannel) channel;
            socketChannel.write(ByteBuffer.wrap(message.getBytes()));

        }
    }
}

2.3 客户端Client

2.3.1 客户端源码

public class MultiChatClient {
    
    

    private Selector selector;
    private SocketChannel socketChannel;
    private static final String SERVER = "127.0.0.1";
    private static final int PORT = 6666;

    public MultiChatClient(){
    
    
        try {
    
    
        	//创建Selector
            selector = Selector.open();

			//创建SocketChannel,连接ServerSocketChannel,并注册到Selector
            socketChannel = SocketChannel.open(new InetSocketAddress(SERVER, PORT));
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
    
    
        MultiChatClient client = new MultiChatClient();
        new Thread(() -> {
    
    
            client.start();
        }, "ReceiveMsgThread").start();

        try {
    
    
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNextLine()){
    
    
                String message = scanner.nextLine();
                client.sendMessage(message);
            }
        }
        catch (Exception e){
    
    
            e.printStackTrace();
        }
    }

    public void start(){
    
    
        try {
    
    
            //selector 不断监听事件
            while (selector.select() > 0){
    
    
                //遍历事件
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()){
    
    
                    SelectionKey selKey = iterator.next();
                    handle(selKey);
                    iterator.remove();
                }
            }
        }
        catch (Exception e){
    
    
            e.printStackTrace();
        }
    }

    //处理事件
    public void handle(SelectionKey selKey) throws IOException {
    
    
        if (selKey.isReadable()){
    
    
            SocketChannel channel = (SocketChannel) selKey.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            channel.read(buffer);
            String message = new String(buffer.array());
            System.out.println(message);
        }
    }

	//发送消息
    public void sendMessage(String message) throws IOException {
    
    
        if (message.equals("quit")){
    
    
            socketChannel.close();
            socketChannel = null;
            return;
        }
        String sendMsg = socketChannel.getLocalAddress() + ":" + message;
        socketChannel.write(ByteBuffer.wrap(sendMsg.getBytes()));
    }
}

2.4 测试

  • 启动服务端Server
    在这里插入图片描述

  • 修改客户端启动配置,把这里勾起,表示允许多个同时运行
    在这里插入图片描述

  • 启动2个客户端
    在这里插入图片描述

  • 客户端A在控制台输入一条消息 hello everyone,并回车

  • 服务端成功收到消息
    在这里插入图片描述

  • 客户端B也收到消息
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_28834355/article/details/113610253
今日推荐