网络I/o编程模型6 Nio之Selector以及NIO客户服务通讯

Posted 健康平安的活着

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络I/o编程模型6 Nio之Selector以及NIO客户服务通讯相关的知识,希望对你有一定的参考价值。

一 selector

1.1 介绍

  1. java的NIO,用非阻塞的IO方式,可以用一个线程,处理多个的客户端连接,就会使用到selector(选择器)。

  2. netty的IO线程NIOEventLoop聚合了selector(选择器,也叫多路复用器),可以同时并发处理成百上千各个客户端连接。

  3. 当线程从某客户端socket通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。

  4. 只有在连接/通道真正有读写事件发生时,才会进行读写,就大大地减少了系统的开销,并且不必为每个连接都创建一个线程,不用去维护多个线程。避免了多线程之间的上下文切换导致系统的开销。

  5. 一个I/O线程可以并发处理N个哭护短连接和读写操作,这从根本上解决了传统同步阻塞I/O一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。

1.2 常用方法

selector.select();//阻塞

selector.select(1000);//阻塞1000毫秒,在1000毫秒后返回

selector.wakeup();//唤醒 selector

selector.selectNow();//不阻塞,立即返回

1.3 selectionKey

selectionKey:表示selector和网络通道的注册关系。
int  OP_ACCEPT 有新的网络连接可以accept ,值为16
int  OP_CONNECT 代表连接已经建立,值为8
int  OP_READ   代表读操作,值为1
int  OP_WRITE  代表写操作,值为4

1.4 ServerSocketChannel

serversocketchannel:作用是在服务器端监听新的客户端socket连接。

1.5 socketchannel

网络IO通道,具体负责进行读写操作。NIO把缓冲区的数据写入通道,或者把通道里的数据读到缓存区。

 

1.6 selector和socketchannel和selectorkey的关系

1.当客户端连接时,会通过ServerSocketChannel得到socketchannel
2.selector进行监听select方法,返回有事件发生的通道的个数;
3.将socketchannel注册到selector上,一个selector上可以注册多个socketchannel
4.注册后返回一个selectorkey,会和该selector关联。

二 代码实操

2.1 使用Nio的非网络阻塞机制,实现客户端和网络端进行通讯。

1 代码

package com.ljf.netty.nio.cs;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @ClassName: Nioserver
 * @Description: TODO
 * @Author: liujianfu
 * @Date: 2022/05/15 11:40:54
 * @Version: V1.0
 **/
public class NioServer 
    public static void main(String[] args) throws IOException 
        //创建ServerSocketCHannel->  ServerSocket
        ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
        //得到一个selector对象
        Selector selector=Selector.open();
        //绑定一个端口6666,在服务器端监听
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        //设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        //把serverSocketChannel注册到selector关心的事件为OP_Accept
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //循环等待客户端的连接
        while(true)
            //这里我们等待设置1秒,如果没有事件发生,则返回
            //selector.select()>0.则就获取到相关的selectionKey集合,也表示已经获取到关注的事件集合
            //System.out.println("selector.select():"+selector.select());
            if(selector.select(1000)==0)
                System.out.println("服务器等待1秒,无连接");
                continue;
            
            //2.selector.selectedKeys() 返回关注事件的集合
            Set<SelectionKey> selectionKeys=selector.selectedKeys();
            //3.进行遍历,获取可用
            Iterator<SelectionKey> keyIterator=selectionKeys.iterator();
            while(keyIterator.hasNext())
                //获取到selectionKey
                SelectionKey key=keyIterator.next();
                //根据key对应的通道发生相应的事件,做出相应事件处理
                if (key.isAcceptable())//如果是OP_ACCEPT,则又新客户端连接
                    //该客户端生成一个socketchannel
                    SocketChannel socketChannel=serverSocketChannel.accept();
                    System.out.println("客户端连接成功,生成了一个 socketChannel"+socketChannel.hashCode());
                    //将 socketChannel设置为非阻塞
                    socketChannel.configureBlocking(false);
                    //将socketchannel注册到selector,关于事件为OP_READ,同时给socketchannel
                    //关联一个buffer
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                
                if(key.isReadable())//开始op_read
                    //通过key,反向获取到对应的channel
                    SocketChannel channel=(SocketChannel) key.channel();
                    //获取到该channel关联的buffer
                    ByteBuffer buffer=(ByteBuffer) key.attachment();
                    channel.read(buffer);
                    System.out.println("from 客户端:"+new String(buffer.array()));
                
                //手动从集合移动当前selectionKey,防止重复操作
                keyIterator.remove();
                System.out.println("========================");
            

        

    


客户端:

package com.ljf.netty.nio.cs;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * @ClassName: NioClient
 * @Description: TODO
 * @Author: liujianfu
 * @Date: 2022/05/15 11:40:45
 * @Version: V1.0
 **/
public class NioClient 
    public static void main(String[] args) throws IOException 
        //得到一个网络通道
        SocketChannel socketChannel=SocketChannel.open();
        //设置非阻塞
        socketChannel.configureBlocking(false);
        //提供服务器端的ip和端口
        InetSocketAddress inetSocketAddress=new InetSocketAddress("127.0.0.1",6666);
        //连接服务器
        if(!socketChannel.connect(inetSocketAddress))
            while(!socketChannel.finishConnect())
                System.out.println("连接客户端需要一些时间,因为是非阻塞io,暂时可以先做其他工作!");
            
        
        //连接成功
        String string="hello,北京";
        ByteBuffer buffer=ByteBuffer.wrap(string.getBytes());
        //返送数据,将buffer中的数据写入到channel
        socketChannel.write(buffer);
        System.in.read();
    

3.结果

启动一个服务端,两个客户端

以上是关于网络I/o编程模型6 Nio之Selector以及NIO客户服务通讯的主要内容,如果未能解决你的问题,请参考以下文章

网络I/o编程模型5 Nio之buffer的操作和常用方法

网络通信之 AIO 和 BIO和 NIO

网络i/o编程3 NIO

网络编程NIO-异步

04-AIO通讯模型

网络I/o编程模型7 Nio实现聊天室