网络NIO深度剖析(三)

本文探讨了传统阻塞IO在网络编程中的问题,如每个客户端连接需创建新线程可能导致资源消耗过大。然后介绍了NIO如何通过Selector实现单线程处理多个客户端连接,解决了这一问题。NIO的SocketChannel注册到Selector,当数据准备好时,Selector会通知并允许处理。最后提到了AIO(异步IO),在IO操作完成后才通知线程,实现完全非阻塞的通信方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传统的基于IO的网络编程(阻塞IO):(伪代码)

ServerSocket serverSocket  = …….

serverSocket.bind(8899);//用于发起连接的端口号,并不是建立好连接后数据传输的端口号

while(true){

    Socket socket = severSocket.accept(); //是一个阻塞方法,如果没有客户端与服务器端发起连接,
                                              //服务器端就停在这里直到有客户端请求建立连接

    New Tread(socket);//起一个新的线程的一个好处,流程执行到这里还可以继续往下执行

    //在while循环中,一个客户端连接好后,用线程处理这个连接;继续等待下一个客户端的连接

    Run(){
        Socket.getInputStream().
    }
}

    //客户端服务器端建立好连接后,服务端会根据当前的操作系统所空闲的端口号来随机进行指派。
    //(每一个客户端-服务器连接都会有一个端口号)

客户端代码:

Socket socket = new Socket(“location”,8899);

Socket.connect();

telnet与nc 命令行客户端监听服务器命令程序:Telnet:打开telent客户端;set localecho:打开本地回显;telent localhost 5000:建立连接

每一个服务端向服务端发起连接请求的时候,服务器端都会起一个新的线程;问题:线程占用系统资源,且在操作系统中线程有最大值;

NIO

一个线程可以处理很多个客户端;Selector;事件:当某一个动作发生的时候。

已经和客户端发起连接的socketchannel,任何一个selectableChannel都可以将自己注册到一个Selector中,这样,这个channel就能被Selector所管理。每一个事件一旦产生之后,得到相应的selectionKey,其携带了与某一个channel关联的对象。就可以通过channe不断地获取到数据。以事件为模型;SocketChannel就是SelectableChannel的一种。

由此构成了由一个或极少数线程,来处理大量客户端连接的结构;当与客户端连接的数据一旦准备好时,selector就能立即得到通知,获取数据进行处理。

    

    public static void main(String[] args) throws Exception {
        int[] ports = new int[4];
        ports[0] = 5000;
        ports[1] = 5001;
        ports[2] = 5002;
        ports[3] = 5003;

        Selector selector = Selector.open();
        System.out.println(SelectorProvider.provider().getClass());//class sun.nio.ch.WindowsSelectorProvider
        System.out.println(sun.nio.ch.DefaultSelectorProvider.create().getClass());//class sun.nio.ch.WindowsSelectorProvider

        for (int i = 0; i < ports.length; ++i) {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//关注的是连接事件
            serverSocketChannel.configureBlocking(false);
            ServerSocket serverSocket = serverSocketChannel.socket();
            InetSocketAddress address = new InetSocketAddress(ports[i]);
            serverSocket.bind(address);//将所有的channel绑定完成

            //进行注册,到selector
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("监听端口" + ports[i]);

        }
        //连接好后等待客户端给我们传输数据
        while (true) {
            int numbers = selector.select();//key的数量
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            System.out.println("selectorKeys:" + selectionKeys);
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                if (selectionKey.isAcceptable()) {
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();//关注的是连接事件
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);//建立好连接

                    socketChannel.register(selector, SelectionKey.OP_READ);//关注的是读取事件
                    iterator.remove();
                    System.out.println("获得客户端连接:" + socketChannel);
                } else if (selectionKey.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    int byteRead = 0;
                    while (true) {
                        ByteBuffer buffer = ByteBuffer.allocate(512);
                        buffer.clear();
                        int read = socketChannel.read(buffer);//
                        if (read <= 0) {
                            break;//已经读完了
                        }
                        buffer.flip();
                        socketChannel.write(buffer);
                        byteRead += read;
                    }
                    System.out.println("读取:" + byteRead + ",来自于:" + socketChannel);
                    iterator.remove();
                }
            }
        }
    }

实现一个简单的通讯服务,包含多个客户端与一个服务端;客户端发送的消息可以被服务端接收且同步到其他的客户端显示。

public class NIOClient {
    public static void main(String[] args) {
        try {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            Selector selector = Selector.open();
            socketChannel.register(selector,SelectionKey.OP_CONNECT);
            socketChannel.connect(new InetSocketAddress("127.0.0.1", 8899));
//            ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue();
//            queue.offer("liu");
            while (true) {
                selector.select();
                Set<SelectionKey> keySet = selector.selectedKeys();
                for (SelectionKey selectionKey : keySet) {
                    if (selectionKey.isConnectable()) {
                        SocketChannel client = (SocketChannel) selectionKey.channel();
                        if (client.isConnectionPending()) {
                            client.finishConnect();

                            ByteBuffer buffer = ByteBuffer.allocate(1024);

                            buffer.put((LocalDateTime.now() + " 连接成功").getBytes());
                            buffer.flip();
                            client.write(buffer);//发送给服务端

                            ExecutorService executorService = Executors.
                                    newSingleThreadExecutor(Executors.defaultThreadFactory());
                            executorService.submit(() -> {
                                        while (true) {
                                            try {
                                                buffer.clear();

                                                InputStreamReader input = new InputStreamReader(System.in);//输入流->字符输入流
                                                BufferedReader br = new BufferedReader(input);//创建一个缓冲字符输入流

                                                String sendMessage = br.readLine();
                                                buffer.put(sendMessage.getBytes());//转化为字节数组,并给buffer
                                                buffer.flip();
                                                client.write(buffer);//写出到buffer

                                            } catch (Exception e) {
                                                e.printStackTrace();
                                            }
                                        }}
                                        );
                        }
                        client.register(selector, SelectionKey.OP_READ);//将读事件注册到client,服务端所发到客户端的消息才能被接受
                    }else if(selectionKey.isReadable()){
                        SocketChannel client = (SocketChannel) selectionKey.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        int count  = client.read(buffer);
                        if(count > 0){
                            String receiveMessage = new String(buffer.array(),0,count);
                            System.out.println(receiveMessage);
                        }
                    }
                }
                keySet.clear();//最后清空
            }


        } catch (Exception x) {
            x.printStackTrace();
        }
    }
}
public class NIOSever {
    private static Map<String, SocketChannel> clientMap = new HashMap<>();

    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        ServerSocket serverSocket = serverSocketChannel.socket();
        serverSocket.bind(new InetSocketAddress(8899));


        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while(true){
            try{
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                selectionKeys.forEach(selectionKey -> {
                    final SocketChannel client;
                    try {
                        if(selectionKey.isAcceptable()){//是否有新进来的连接
                            ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
                            client = server.accept();
                            client.configureBlocking(false);
                            client.register(selector, SelectionKey.OP_READ);

                            String key = "【"+ UUID.randomUUID().toString() + "】";
                            clientMap.put(key, client);//每一个客户端,对应的值放到Map中
                        }else if(selectionKey.isReadable()){//是否有新到来的数据
                            client = (SocketChannel)selectionKey.channel();
                            ByteBuffer readbuffer = ByteBuffer.allocate(1024);
                            int count = client.read(readbuffer);
                            if(count > 0){
                                readbuffer.flip();

                                Charset charset = Charset.forName("utf-8");
                                String receivedMessage = String.valueOf(charset.decode(readbuffer).array());//向服务端发送的内容

                                System.out.println(client + ":" + receivedMessage);//得到了一个客户端向服务器发出的信息

                                String senderkey = null;
                                for(Map.Entry<String, SocketChannel> entry : clientMap.entrySet()){
                                    if(client == entry.getValue()){
                                        senderkey = entry.getKey();//拿到发送者的key值
                                        break;
                                    }
                                }
                                for(Map.Entry<String, SocketChannel> entry : clientMap.entrySet()){
                                    SocketChannel value = entry.getValue();//对于每一个客户端
                                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                                    buffer.put((senderkey + ":" + receivedMessage).getBytes());//信息变为一个字节数组,放到buffer中
                                    buffer.flip();

                                    value.write(buffer);//写到buffer中,即写给客户端
                                }

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

                selectionKeys.clear();


            }catch (Exception ex){
                ex.printStackTrace();
            }
        }
    }
}

AIO是异步IO的缩写。虽然NIO在网络中提供了非阻塞的方法,但是NIO的IO行为还是替你干部的。对NIO来说,业务操作是在IO操作准备好时,得到通知,接着就由线程自行进行IO操作,IO操作本身还是同步的。对于AIO来说,它不是在IO准备好时再通知线程,而是在IO操作已经完成后,再给线程发出通知。因此AIO是完全不会阻塞的。业务逻辑变为了一个回调函数,等待IO操作完成后,由系统自动触发。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值