NIO编程

1、流

1.1、 java.io下的字符流和字节流

在这里插入图片描述

1.1.1、字符流

字符流更加的方便我们使用,一般字符都是由多个字节来形成的,若我们使用字节流传输,则还需要我们自己将其转换为字符,若我们直接使用字符流的话,这样就能直接读取与输出字符。

在这里插入图片描述
在这里插入图片描述

1.1.2、字节流

字节流则是对一个个字节进行读取
在这里插入图片描述
在这里插入图片描述

1.2、装饰器模式

在这里插入图片描述

在字符流中BufferedReader、InputStreamReader和FilterReader,字节流中的BufferedInputStream、DataInputStreamReader与DataOutputStream都运用到了装饰器模式因为它们本身不能进行实例化,都需要传入基本的Reader或InputStream来进行升级,这里便体现的是装饰器模式。

2、Socket

Socket认为是网络传输的端点,它也是一种数据源,将特定的ip地址与端口号与其进行绑定,这样它就能够实现通信的功能,如下图中,服务器中的socket绑定了对应的ip与端口号,与客户端间进行通信。

2.1、通过Socket发送数据

在这里插入图片描述

  1. 创建Socket,绑定特定的ip地址与端口号
  2. 将Socket与网卡驱动程序绑定
  3. 通过Socket我们就能够发送数据了
  4. 网卡驱动程序对Socket数据进行读取

2.2、通过Socket读取数据

在这里插入图片描述

  1. 我们还是要先创建Socket
  2. 将Socket与网卡驱动程序进行绑定
  3. Socket接收来自网卡驱动程序的数据
  4. 从Socket中读取数据

3、BIO模型

在这里插入图片描述
简述BIO模型中服务端与客户端的响应过程

  1. 服务器serverSocket先要和端口进行绑定
  2. 绑定完成后,执行accept方法,等待客户端的连接,这个方法是阻塞式调用,也就是说,要一直等待客户端的连接响应,不做其他事情,一直等,(被阻塞的还有InputStream.read()、OutputStream.write(),这两个也会一直等待客户端的响应)
  3. 客户端创建Socket对象,绑定服务器的ip地址与端口号,与服务器进行连接
  4. 服务器接收到客户端的连接请求,accept方法获取到客户端的socket信息,连接成功
  5. 服务器与客户端创建各自的io流,实现全双工通信
  6. 之后便可以随时结束连接

在这里插入图片描述

4、NIO模型

NIO:非阻塞IO
NIO和BIO有以下的区别:

  1. 使用Channel代替Stream
  2. 使用Selector监控多条Channel
  3. 在一个线程里处理多个Channel I/O

Channel是双向的,即可以读又可以写,相比于Stream,它并不区分出输入流和输出流,而且Channel可以完成非阻塞的读写,也可以完成阻塞的读写。
在这里插入图片描述

Channel间的数据交换,都需要依赖Buffer,Buffer实际上内存上一块用来读写的区域。

NIO模型分析:

  1. 在服务器端创建一个Selector,将ServerSocketChannel注册到Selector上,被Selector监听的事件为Accept。
  2. Client1请求与服务器建立连接,Selector接收到Accept事件,服务器端对其进行处理(handles),服务器与客户端连接成功。
  3. 建立连接过程中,服务器通道(ServerSocketChannel)调用accept方法,获取到与客户端进行连接的通道(SocketChannel),也将其注册到Selector上,监听READ事件,这样,客户端向服务器发送消息,就能触发该READ事件进行响应,读取该消息。
  4. 同样,两个客户连接过来也是一个线程在起作用,将Client2的SocketChannel注册到服务器的Selector,并监听READ事件,随时响应随时处理。即一个客户端有一个SocketChannel,两个客户端就有两个SocketChannel,这个就是我们使用nio编程模型来用一个selector对象在一个线程里边监听以及处理多个通道的io的操作

5、NIO多人聊天室

服务端:

package chatroom.nio.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.util.Set;

public class ChatServer {

    //默认监听端口
    private final int DEFAULT_SERVER_PORT = 8888;
    private final String QUIT = "quit";
    private static final int BUFFER = 1024;

    //服务端的channel和selector
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;
    private ByteBuffer rBuffer = ByteBuffer.allocate(BUFFER);
    private ByteBuffer wBuffer = ByteBuffer.allocate(BUFFER);
    private Charset charset = Charset.forName("UTF-8");

    private int 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();
            }
        }
    }


    private void start() {
        try {
            //创建ServerSocketChannel通道
            serverSocketChannel = ServerSocketChannel.open();
           //默认是阻塞模式,需要改为非阻塞  *** 重点 ***
            serverSocketChannel.configureBlocking(false);
           //绑定监听端口
            serverSocketChannel.socket().bind(new InetSocketAddress(port));

            //创建selector
            selector = Selector.open();
            //将accept事件注册到selector上   SelectionKey对象可以取出监听事件的相关信息。
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("服务器启动成功,监听端口号:" + port + "...");

            //不断的监听响应
            while(true) {
                //阻塞式调用,需要不停的调用select,故放入while循环中
                selector.select();

                //获取监听到触发的所有事件的SelectionKey
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                for(SelectionKey selectionKey : selectionKeys) {
                    //处理事件
                    handlers(selectionKey);
                }
                //注意要清空事件,否则会重复响应
                selectionKeys.clear();
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(selector);
        }

    }


    //处理事件
    private void handlers(SelectionKey selectionKey) throws IOException {
        // ACCEPT事件 - 和客户端建立了连接
        if(selectionKey.isAcceptable()) {
            //处理accpet事件
            //先获取ServerSocketChannel
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel)selectionKey.channel();

            //获取对应的客户端通道
            SocketChannel clientSocketChannel = serverSocketChannel.accept();
            //默认是阻塞模式,需要改为非阻塞
            clientSocketChannel.configureBlocking(false);

            //将客户端通道绑定到selector上,监听read事件
            clientSocketChannel.register(selector, SelectionKey.OP_READ);

            System.out.println(getClientName(clientSocketChannel) + "已连接");

        }
        //READ事件 - 客户端发送了消息给服务端就会触发read事件
         else if(selectionKey.isReadable()) {
            //处理read事件
            //通过selection获取对应的客户端通道
            SocketChannel clientSocketChannel = (SocketChannel)selectionKey.channel();
            //获取通道中的消息
            String fwdMsg = receive(clientSocketChannel);

            if(fwdMsg.isEmpty()) {
                //接受不到消息,那么把该通道移除
                selectionKey.channel();
                //通知selector有注册的通道被移除了,更新状态
                selector.wakeup();

            } else {
                //转发消息
                forwardMessage(clientSocketChannel, fwdMsg);
                System.out.println(getClientName(clientSocketChannel) + ":" + fwdMsg);

                if(readyToQuit(fwdMsg)) {
                    selectionKey.channel();
                    selector.wakeup();
                    System.out.println(getClientName(clientSocketChannel) + "已断开");
                }
            }
        }

    }

    private String getClientName(SocketChannel client) {
        return "客户端[" + client.socket().getPort() + "]";
    }


    /**
     * 接收方法
     */
    private String receive(SocketChannel clientSocketChannel) throws IOException {
        rBuffer.clear();

        //一直读数据
        while(clientSocketChannel.read(rBuffer) > 0);
        //flip将buffer的读模式转换为写模式
        rBuffer.flip();
        return String.valueOf(charset.decode(rBuffer));

    }

    /**
     * 转发消息
     */
    private void forwardMessage(SocketChannel clientSocketChannel, String fwdMsg) throws IOException {
        //keys()方法区别于selectedKeys(),selectedKeys()返回的是接下来需要被处理的通道key
        //而keys()返回的是与selector绑定的所有通道key
        for (SelectionKey selectionKey : selector.keys()) {
            Channel connectedClient = selectionKey.channel();
            if (selectionKey.channel() instanceof ServerSocketChannel) {
                continue;
            }
            //有效且不是发送者
            if (selectionKey.isValid() && !clientSocketChannel.equals(connectedClient)) {
                wBuffer.clear();
                //写入消息
                wBuffer.put(charset.encode(getClientName(clientSocketChannel) + ":" + fwdMsg));
                //wBuffer由写状态转换为读状态
                wBuffer.flip();
                //有数据有一直读
                while (wBuffer.hasRemaining()) {
                    ((SocketChannel)connectedClient).write(wBuffer);
                }
            }
        }
    }

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

客户端:

package chatroom.nio.client;

import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
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_SIZE = 1024;

    private String host;
    private int port;
    private SocketChannel clientSocketChannel;
    private ByteBuffer rBuffer = ByteBuffer.allocate(BUFFER_SIZE);
    private ByteBuffer wBuffer = ByteBuffer.allocate(BUFFER_SIZE);
    private Selector selector;
    private Charset charset = Charset.forName("UTF-8");

    public ChatClient() {
        this.host = DEFAULT_SERVER_HOST;
        this.port = DEFAULT_SERVER_PORT;
    }

    public ChatClient(String host, int port) {
        this.host = host;
        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();
            }
        }
    }


    private void start() {
        try {
            // 创建用户通道
            clientSocketChannel = SocketChannel.open();
            clientSocketChannel.configureBlocking(false);

            //创建selector,并且将用户通道的connect请求注册上去
            selector = Selector.open();
            clientSocketChannel.register(selector, SelectionKey.OP_CONNECT);

            //尝试与服务器建立连接
            clientSocketChannel.connect(new InetSocketAddress(host, port));

            while(selector.isOpen()) {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                for (SelectionKey selectionKey : selectionKeys) {
                    //响应请求
                    handler(selectionKey);
                }
                selectionKeys.clear();
            }

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClosedSelectorException e) {
            // 用户正常退出
        } finally {
            close(selector);
        }

    }

    private void handler(SelectionKey key) throws IOException {
        // connect事件 - 连接就绪事件
        if (key.isConnectable()) {
            SocketChannel client = (SocketChannel) key.channel();
            // 如果能够与服务器响应
            if(client.isConnectionPending()) {
                // 正式建立连接
                client.finishConnect();

                // 处理用户输入的信息,由于该线程会被用户输入所阻塞,故需要新建一个线程
                new Thread(new UserInputHandler(this)).start();
            }
            client.register(selector, SelectionKey.OP_READ);
        }
        // read事件 - 服务器转发消息
        else if (key.isReadable()) {
            SocketChannel client =  (SocketChannel) key.channel();
            String msg = receive(client);
            if (msg.isEmpty()) {
                // 服务端异常
                close(selector);
            } else {
                // TODO
                System.out.println(msg);
            }

        }
    }

    private String receive(SocketChannel client) throws IOException {
        rBuffer.clear();
        while (client.read(rBuffer) > 0);
        rBuffer.flip();
        return String.valueOf(charset.decode(rBuffer));
    }

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

        wBuffer.clear();
        // put方法写入缓冲区
        wBuffer.put(charset.encode(msg));
        // 转换为读模式
        wBuffer.flip();
        while (wBuffer.hasRemaining()) {
            clientSocketChannel.write(wBuffer);
        }

        // 检查用户是否退出
        if (readyToQuit(msg)) {
            close(selector);
        }
    }

    public static void main(String[] args) {
        ChatClient client = new ChatClient(DEFAULT_SERVER_HOST, 7777);
        client.start();
    }

}

客户端UserInputHandler:

package chatroom.nio.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() {
        try {
            //等待用户输入消息
            BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                String input = consoleReader.readLine();
                // 向服务器发送消息
                chatClient.send(input);
                //检查用户是否准备退出
                if (chatClient.readyToQuit(input)) {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值