一站式学习Java网络编程 全面理解BIO_NIO_AIO,学习手记(六)

大家好,我是方圆
把这篇肝完就休息!


1. NIO模型分析

在这里插入图片描述

  • 在服务器端创建一个Selector,将ServerSocketChannel注册到Selector上,被Selector监听的事件为Accept

在这里插入图片描述

  • Client1请求与服务器建立连接,Selector接收到Accept事件,服务器端对其进行处理(handles),服务器与客户端连接成功

在这里插入图片描述

  • 建立连接过程中,服务器通道(ServerSocketChannel)调用accept方法,获取到与客户端进行连接的通道(SocketChannel),也将其注册到Selector上,监听READ事件,这样,客户端向服务器发送消息,就能触发该READ事件进行响应,读取该消息。

Tips: 我们处理这个建立连接并接收从客户端传过来的消息,都是在一个线程内完成的。在bio中,则会为单个客户端单独开辟一个线程,用于处理消息,并且客户端在不发送消息的过程中,该线程一直是阻塞的。


在这里插入图片描述

  • 同样,两个客户连接过来也是一个线程在起作用,将客户端2的SocketChannel注册到服务器的Selector,并监听READ事件,随时响应随时处理。即一个客户端有一个SocketChannel,两个客户端就有两个SocketChannel,这个就是我们使用nio编程模型来用一个selector对象在一个线程里边监听以及处理多个通道的io的操作

各个Channel是被配置为非阻塞式的(configureBlocking(false)),但是Selector本身调用的select()方法,它是阻塞式的,当监听在Selector上的事件都没有触发时,那么它就会被阻塞,直到有事件对其进行响应

2. 聊天室项目代码重点知识

2.1 服务器端

2.1.1 字段

在这里插入图片描述

2.1.2 主方法

在这里插入图片描述

扫描二维码关注公众号,回复: 11824646 查看本文章

2.1.3 处理方法

在这里插入图片描述

2.1.4 转发消息方法

在这里插入图片描述

2.1.5 接收消息方法

在这里插入图片描述

2.2 客户端

2.2.1 字段

在这里插入图片描述

2.2.2 主方法

在这里插入图片描述

2.2.3 处理方法

在这里插入图片描述

2.2.4 接收方法

在这里插入图片描述

2.2.5 发送方法

在这里插入图片描述


3. 测试结果

  • 服务器端显示信息正确
    在这里插入图片描述

4. 完整代码

4.1 服务器端

package server;

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

public class ChatServer {
    
    
    private static final int DEFAULT_PORT = 8888;
    private static final String QUIT = "quit";
    private static final int BUFFER = 1024;
    private int port;

    private ServerSocketChannel serverSocketChannel;
    private Selector selector;
    private ByteBuffer rBuffer = ByteBuffer.allocate(BUFFER);
    private ByteBuffer wBuffer = ByteBuffer.allocate(BUFFER);
    private Charset charset = Charset.forName(String.valueOf(StandardCharsets.UTF_8));

    public ChatServer(){
    
    
        this(DEFAULT_PORT);
    }

    public ChatServer(int port) {
    
    
        this.port = port;
    }

    public boolean readyToQuit(String msg){
    
    
        return QUIT.equals(msg);
    }

    public void close(Closeable closeable){
    
    
        if(closeable != null) {
    
    
            try {
    
    
                closeable.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    public void start(){
    
    
        try {
    
    
            //创建ServerSocketChannel通道
            serverSocketChannel = ServerSocketChannel.open();
            //设置非阻塞模式,默认情况也是阻塞调用的
            serverSocketChannel.configureBlocking(false);
            //绑定端口号
            serverSocketChannel.bind(new InetSocketAddress(port));
            //创建selector
            selector = Selector.open();
            //将accept事件注册到selector上
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("服务器启动成功,监听端口号:" + port + "...");

            //开始进入监听模式
            while(true){
    
    
                //阻塞式调用
                selector.select();

                //获取所有的监听事件,
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                for (SelectionKey selectionKey : selectionKeys) {
    
    
                    //处理事件
                    handles(selectionKey);
                }
                //将已经处理完成的事件进行清空
                selectionKeys.clear();
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            close(selector);
        }
    }

    /**
     * 处理事件 处理accept事件 和 read事件
     * @param selectionKey 与selector绑定的channel的key
     */
    private void handles(SelectionKey selectionKey) throws IOException {
    
    
        if(selectionKey.isAcceptable()){
    
    
            //处理accept事件
            //先要获取ServerSocketChannel
            ServerSocketChannel server =(ServerSocketChannel) selectionKey.channel();
            // 我觉得这样写也行 直接调用全局变量 SocketChannel socketChannel = serverSocketChannel.accept();
            //获取对应的客户端的通道
            SocketChannel socketChannel = server.accept();
            socketChannel.configureBlocking(false);
            //将客户端通道绑定到selector上,监听read事件
            socketChannel.register(selector,SelectionKey.OP_READ);
            System.out.println("客户端" + socketChannel.socket().getPort() + ":已经连接");
        }else if(selectionKey.isReadable()){
    
    
            //处理read事件
            SocketChannel clientChannel = (SocketChannel) selectionKey.channel();
            String fwdMsg = receive(clientChannel);

            if(fwdMsg.isEmpty()){
    
    
                //接不到消息了,那么就把这个通道给他移除了
                selectionKey.cancel();
                //通知selector有注册的通道被移除了,更新状态
                selector.wakeup();
            }else {
    
    
                //转发消息
                forwardMessage(clientChannel,fwdMsg);
                if(readyToQuit(fwdMsg)){
    
    
                    selectionKey.cancel();
                    selector.wakeup();
                }
            }
        }
    }

    /**
     * 转发消息
     * @param clientChannel 客户端通道
     * @param fwdMsg 转发的消息
     */
    private void forwardMessage(SocketChannel clientChannel, String fwdMsg) throws IOException {
    
    
        //keys方法区别于selectedKeys,这个方法返回的是接下来需要被处理的通道key
        //而keys则返回与selector绑定的所有通道key
        //跳过ServerSocketChannel和本身
        for (SelectionKey selectionKey : selector.keys()) {
    
    
            SelectableChannel channel = selectionKey.channel();
            if(channel instanceof ServerSocketChannel)
                System.out.println("客户端" + clientChannel.socket().getPort() + ":" + fwdMsg);
            else if(selectionKey.isValid() && !channel.equals(clientChannel)){
    
    
                wBuffer.clear();
                //写入消息
                wBuffer.put(charset.encode("客户端" + clientChannel.socket().getPort() + ":" + fwdMsg));
                //转换为读模式
                wBuffer.flip();
                //有数据就一直读
                while(wBuffer.hasRemaining())
                    ((SocketChannel)channel).write(wBuffer);
            }
        }
    }

    /**
     * 从客户通道上读取消息
     * @param clientChannel 客户通道
     * @return 消息
     */
    private String receive(SocketChannel clientChannel) throws IOException {
    
    
        //将当前指针置于初始位置,覆盖已有的消息(清空消息)
        rBuffer.clear();
        //不停的向缓存中写
        while(clientChannel.read(rBuffer) > 0);
        //由写模式到读模式
        rBuffer.flip();
        return String.valueOf(charset.decode(rBuffer));
    }

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

4.2 客户端

package client;

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

public class ChatClient {
    
    

    private static final String DEFAULT_SERVER_HOST = "127.0.0.1";
    private static final int DEFAULT_SERVER_PORT = 8888;
    private static final String QUIT = "quit";

    private static final int BUFFER = 1024;
    private String host;
    private int port;
    private SocketChannel clientChannel;
    private Selector selector;
    private ByteBuffer rBuffer = ByteBuffer.allocate(BUFFER);
    private ByteBuffer wBuffer = ByteBuffer.allocate(BUFFER);
    private Charset charset = Charset.forName(String.valueOf(StandardCharsets.UTF_8));

    public ChatClient(String host, int port) {
    
    
        this.host = host;
        this.port = port;
    }

    public ChatClient() {
    
    
        this(DEFAULT_SERVER_HOST,DEFAULT_SERVER_PORT);
    }

    public boolean readyToQuit(String msg){
    
    
        return QUIT.equals(msg);
    }

    public void close(Closeable closeable){
    
    
        if(closeable != null){
    
    
            try {
    
    
                closeable.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    public void start(){
    
    
        try {
    
    
            //创建用户通道
            clientChannel = SocketChannel.open();
            clientChannel.configureBlocking(false);//这一步千万不能忘了
            //创建selector,并且将用户通道的connect请求注册上去
            selector = Selector.open();
            clientChannel.register(selector, SelectionKey.OP_CONNECT);
            //尝试与服务器创建连接
            clientChannel.connect(new InetSocketAddress(host,port));

            while (selector.isOpen()){
    
    
                //一直监听selector上注册的channel请求
                selector.select();

                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                for (SelectionKey selectionKey : selectionKeys) {
    
    
                    //响应请求
                    handles(selectionKey);
                }
                selectionKeys.clear();
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }catch (ClosedSelectorException e){
    
    

        } finally {
    
    
            close(selector);
        }
    }

    private void handles(SelectionKey selectionKey) throws IOException {
    
    
        //处理connect事件
        if(selectionKey.isConnectable()){
    
    
            //如果能够与服务器响应了
            SocketChannel channel = (SocketChannel) selectionKey.channel();
            if(channel.isConnectionPending()){
    
    
                channel.finishConnect(); //正式建立连接
                new Thread(new UserInputHandler(this)).start();
            }
            channel.register(selector,SelectionKey.OP_READ);
        }else if(selectionKey.isReadable()){
    
    
            String msg = receive(clientChannel);
            SocketChannel channel = (SocketChannel) selectionKey.channel();
            if(msg.isEmpty()){
    
    
                //服务端异常
                close(selector);
            }else {
    
    
                //TODO 看看这里信息对不对
                System.out.println(msg);
            }

        }
    }

    private String receive(SocketChannel clientChannel) throws IOException {
    
    
        rBuffer.clear();
        //一直读数据
        while (clientChannel.read(rBuffer) > 0);
        rBuffer.flip();
        return String.valueOf(charset.decode(rBuffer));
    }

    public void send(String msg) throws IOException {
    
    
        if(msg.isEmpty())
            return;

        wBuffer.clear();
        wBuffer.put(charset.encode(msg));
        wBuffer.flip();
        while(wBuffer.hasRemaining()){
    
    
            clientChannel.write(wBuffer);
        }

        if(QUIT.equals(msg))
            close(selector);
    }

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

4.3 客户端监听用户输入进程

package client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class UserInputHandler implements Runnable{
    
    

    private ChatClient chatClient;

    public UserInputHandler(ChatClient chatClient) {
    
    
        this.chatClient = chatClient;
    }

    @Override
    public void run() {
    
    
        //等待用户输入信息
        BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));

        while(true){
    
    
            try {
    
    
                String msg = consoleReader.readLine();
                chatClient.send(msg);
                if(chatClient.readyToQuit(msg)) break;
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

爽到!

猜你喜欢

转载自blog.csdn.net/qq_46225886/article/details/107525068