nio socket聊天室

  io socket通过不断新线程的方式,这会导致占用大量资源。因此在jdk1.4时提出新的解决方案:NIO。

  java nio的几个核心部分:

  • Channel
  • Buffer
  • Selector

Channel:通道,是I/O操作的关系。表示与实体之间的打开连接。实体包含硬件设备、文件、网络连接或一组程序组件(能够执行一个或多个不同的I/O操作,如读/写)。常用于多线程访问是安全的

主要实现类:

  • DatagramChannel:用于面向数据报的套接字的可选通道
  • FileChannel:读取、写入、映射和操作文件的通道。
  • ServerSocketChannel:用于面向流的侦听插座的可选通道。
  • SocketChannel:用于面向流的连接套接字的可选通道。

Buffer:基于特定的原始类型的容器。缓冲区是特定原始类型元素的线性有限序列。

Selector:多路复用器。Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便

三者的关系图:

 

 

服务端:

public class NioServer {

    private static Map<String, SocketChannel> clientMap = new ConcurrentHashMap<>();

    public static void main(String[] args) {
        try {
            //open()创建一个server socket channel,不可能为任意的、预先存在的ServerSocket创建通道。
            //此只有创建,而未有bind, 调用accept()会有异常:NotYetBoundException
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //绑定server socket
            serverSocketChannel.bind(new InetSocketAddress("127.0.0.1",8989));
            serverSocketChannel.configureBlocking(false);

            //channel的一种多路复用器
            Selector selector = Selector.open();
            //注册一个连接监听事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("nio server start success ! ");

            while (true){
                //阻塞操作,等待事件
                selector.select();
                //返回所关注事件集合
                Set<SelectionKey> selectionKeys =  selector.selectedKeys();
                //开始只注册一个事件,当连接建立后,再进行其他事件注册
                selectionKeys.forEach(key -> {
                    try {
                        SocketChannel socketChannel ;
                        if (key.isAcceptable()){
                            //获取当前事件发生在哪个channel上
                            ServerSocketChannel channelServer = (ServerSocketChannel) key.channel();
                            //一直阻塞到有新连接到达
                            socketChannel = channelServer.accept();
                            //必须要先设置成非阻塞,否则异常:java.nio.channels.IllegalBlockingModeException
                            socketChannel.configureBlocking(false);
                            //channel上注册读事件
                            socketChannel.register(selector, SelectionKey.OP_READ);

                            String clientKey = UUID.randomUUID().toString();
                            clientMap.put(clientKey,socketChannel);

                        }else if (key.isReadable()){//接收
                            //创建读取缓冲区
                            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                            socketChannel = (SocketChannel) key.channel();
                            //连接channel的key
                            String readerKey = "" ;
                            for (Map.Entry<String,SocketChannel> entry : clientMap.entrySet()){
                                if (entry.getValue() == socketChannel){
                                    readerKey = entry.getKey();
                                    break;
                                }
                            }
                            int count = socketChannel.read(byteBuffer);
                            if (count > 0){
                                //读取客户端数据,进行写数据
                                byteBuffer.flip();
                                byte[] data = new byte[count];
                                System.arraycopy(byteBuffer.array(), 0, data, 0, count);
                                String message = String.format("%s:\t%s",readerKey,new String(data));
                                System.out.println(message);

                                //向所有connection发送消息
                                for (Map.Entry<String,SocketChannel> entry : clientMap.entrySet()){
                                    SocketChannel clientSocket = entry.getValue();
                                    ByteBuffer clientBuffer = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8));
                                    clientSocket.write(clientBuffer);
                                }
                            } else {
                                System.out.println("客户端关闭");
                                key.cancel();
                                clientMap.remove(readerKey);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });

                //使用完selectKey之后一定要删除掉
                //如果不清除,下次循环还是会进行处理,会报空指针异常
                selectionKeys.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

客户端:

try {
            SocketChannel client = SocketChannel.open();
            client.configureBlocking(false);

            Selector selector = Selector.open();
            client.register(selector, SelectionKey.OP_CONNECT);
            client.connect(new InetSocketAddress("127.0.0.1",8989));

            System.out.println("client start success!");

            while (true){
                //阻塞,直至通道连接
                selector.select();
                Set<SelectionKey> keys = selector.selectedKeys();
                for (SelectionKey key : keys){
                    if (key.isConnectable()){
                        System.out.println("connection is success");
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        //连接是否挂起
                        //检查此通道的连接操作是否正在进行
                        //当且仅当此通道上的连接操作已初始化,但尚未通过调用finishConnect方法完成时为true
                        if (socketChannel.isConnectionPending()){
                            //调用此方法来完成连接序列。
                            //如果此通道已经连接,则此方法将不会阻塞,并将立即返回true。
                            // 如果此通道处于非阻塞模式,则如果连接进程尚未完成,此方法将返回false。
                            // 如果此通道处于阻塞模式,则此方法将阻塞,直到连接完成或失败,并始终返回true或抛出描述失败的检查异常。
                            //这个方法可以在任何时候调用。如果在此方法的调用过程中调用了该通道上的读或写操作,则该操作将首先阻塞,直到该调用完成。
                            socketChannel.finishConnect();
                            String message = "connection success:\t"+ LocalDateTime.now();
                            ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8));
                            socketChannel.write(writeBuffer);

                            ExecutorService executorService = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory());
                            executorService.submit(()->{
                                while (true){
                                    //清除这个缓冲区。位置设置为零,限制设置为容量,标记被丢弃。实际上并没有擦除缓冲区中的数据
                                    writeBuffer.clear();
                                    //外部输入
                                    InputStreamReader streamReader = new InputStreamReader(System.in);
                                    BufferedReader bufferedReader = new BufferedReader(streamReader);
                                    String sendMessage = bufferedReader.readLine();

                                    //发送
                                    writeBuffer.put(sendMessage.getBytes(StandardCharsets.UTF_8));
                                    writeBuffer.flip();
                                    socketChannel.write(writeBuffer);

                                }
                            });

                            //注册读事件, 接收服务器消息
                            socketChannel.register(selector, SelectionKey.OP_READ);
                        }

                    } else if (key.isReadable()){
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        //创建读取缓冲区
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                        int count = socketChannel.read(byteBuffer);
                        if (count > 0){
//                            byteBuffer.flip();
//                            byte[] data = new byte[count];
//                            System.arraycopy(byteBuffer.array(), 0, data, 0, count);
                            String message = new String(byteBuffer.array(),0,count);
                            System.out.println(message);

                        }
                    }
                }

                keys.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

 

posted @ 2021-06-07 15:31  落孤秋叶  阅读(110)  评论(0编辑  收藏  举报