• 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:	%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:	"+ 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();
            }
  • 相关阅读:
    console.dir()和console.log()的区别
    md5
    sorket is closed
    箱形图和小提琴图
    PCA降维
    模式识别与机器学习(二)
    模式识别与机器学习(一)
    论文研读Unet++
    分类中使用的一些评价指标
    前列腺分割论文
  • 原文地址:https://www.cnblogs.com/song27/p/14859026.html
Copyright © 2020-2023  润新知