Netty的介绍与使用--第二天(NIO中Selector的介绍与多人聊天室实现)

1.Selector(选择器)

1.1 Selector基本介绍

(1)java的NIO,用非阻塞的IO方式。可以使用一个线程,处理多个的客户端连接,就会使用到Selector(选择器);
(2)Selector能够检测多个注册的通道上是否有事件发生(注意:多个Channel以事件的方式可以注册同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求;
(3)只有在连接(通道)真正有读写事件发生时,才会进行读写,就大大减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程;
(4)避免了多线程之间的上下文切换导致的开销。
Selector与Channel的关系图Selector与Channel的关系图

1.2 Selector类相关方法

1.2.1 Selector类是一个抽象类,常用方法和说明如下:

Public abstract class Selector implement Closeable{
Public static selector open();//得到一个选择器对象
Public int select(long timeout);//监控所注册的通道,当其中有IO操作可以进行时,将对应的SelectionKey加入到内部集合中并返回,参数来设置超时时间
Public Set selecteKeys();//从内部集合中得到所有的SelectionkKey
}

1.2.2 Selector相关方法说明

  1. Selector.select()//阻塞
  2. Selector.select(1000)//阻塞1000毫秒,在1000毫秒后返回
  3. Selector.wakeup()//唤醒selector
  4. Selector.selectNow;//不阻塞

1.2.3 SelectionKey说明

SelectionKey表示Selector和网络通道的注册关系,共有四种情况:

  • int OP_ACCPET:有新的网络可以连接accept,值为16
  • Int OP_CONNECT:代表连接已经建立,值为8
  • Int OP_WRITE:代表写操作,值为4
  • Int OP_READ:代表读操作 值为1

SelectionKey相关的方法

 /**
     * Returns the selector for which this key was created.  This method will
     * continue to return the selector even after the key is cancelled.
     *
     * @return  This key's selector
     * 
     */
     //得到关联的selector
    public abstract Selector selector();


/**
     * Returns the channel for which this key was created.  This method will
     * continue to return the channel even after the key is cancelled.
     *
     * @return  This key's channel
     */
     //得到与之相关的channel
    public abstract SelectableChannel channel();

//得到与之关联的共享数据
private volatile Object attachment = null;

    private static final AtomicReferenceFieldUpdater<SelectionKey,Object>
        attachmentUpdater = AtomicReferenceFieldUpdater.newUpdater(
            SelectionKey.class, Object.class, "attachment"
        );



/**
     * Sets this key's interest set to the given value.
     *
     * <p> This method may be invoked at any time.  Whether or not it blocks,
     * and for how long, is implementation-dependent.  </p>
     *
     * @param  ops  The new interest set
     *
     * @return  This selection key
     *
     * @throws  IllegalArgumentException
     *          If a bit in the set does not correspond to an operation that
     *          is supported by this key's channel, that is, if
     *          {@code (ops & ~channel().validOps()) != 0}
     *
     * @throws  CancelledKeyException
     *          If this key has been cancelled
     */
     //设置或改变监听事件
    public abstract SelectionKey interestOps(int ops);

1.3 Selector的使用

1,但客户端连接时,会通过ServerSocketChannel得到SocketChannel
2,selector进行监听 select方法,返回有事件发生的通道的个数)
3,将SocketChannel注册到selectot上,register(Selector sel,int ops)
4,注册后返回一个selectionKey,会和该selector关联(集合)
5,进一步得到各个selectionKey(有事件发生)
6,再通过selectionKey反向获取SocketChannel,方法 channel()
可以通过得到的channel完成业务处理
在这里插入图片描述

2.1 Selector代码示例

2.1.1 实现服务器与客户端连接

服务端代码:

package com.Channel;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * @author linjiazeng
 * @version 1.0
 * @date 2020/10/27 14:07
 **/
public class NIOServer {

    public static void main(String[] args)throws Exception {

        //创建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){
            //这里我们等待一秒,如果没有事件发生,则返回
            if (selector.select(1000)==0){//没有事件发生
                System.out.println("服务器等待了1秒,无连接。。。");
                continue;
            }
            //如果返回大于0,就获取相关的selectionKey集合
            //1.如果返回的大于0,表示已经获取到关注的事件
            //2.selector.selectedkeys()返回关注事件的集合
            //通过selectionKeys反向获取通道

            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            //遍历 Set<SelectionKey>,使用迭代器遍历
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();

            while (keyIterator.hasNext()){
                //获取selectionKey
                SelectionKey key = keyIterator.next();
                //根据key对应的通道发生的事件进行相应的处理
                if (key.isAcceptable()){
                    //该客户端生成一个 SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();

                    socketChannel.configureBlocking(false);//设置为非阻塞状态

                    //将socketChannel注册到selector,关注事件为OP_READ,同时给socketChannel
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if (key.isReadable()){//发生OP_READ
                    //通过key 反向获取到对应channel
                    SelectableChannel channel =(SocketChannel)key.channel();
                    //获取到该channel关联的buffer
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    ((SocketChannel) channel).read(buffer);
                    System.out.println("from 客户端"+new String(buffer.array()));
                }
                //手动从集合中移动当前的selectionKey,防止重复操作
                keyIterator.remove();
            }
        }
    }
}

客户端代码:

package com.Channel;

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

/**
 * @author linjiazeng
 * @version 1.0
 * @date 2020/10/27 14:34
 **/
public class NIOClient {
    public static void main(String[] args) throws Exception{
        //得到一个网络通道
        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("因为连接需要时间,客户端不会阻塞,可以做其他的工作。。。");
            }
        }
        //如果连接成功,就发送数据
        String str = "hello,林老三";
        ByteBuffer wrap = ByteBuffer.wrap(str.getBytes());
        socketChannel.write(wrap);
        System.in.read();
    }
}

运行后效果:
在这里插入图片描述

2.1.2 多人聊天室实现

服务端代码:

package com.groupchat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

/**
 * @author linjiazeng
 * @version 1.0
 * @date 2020/10/27 14:37
 **/
public class GroupChatServer {

    //定于属性
    private Selector selector;
    private ServerSocketChannel listenChannel;
    private static final int PORT = 6667;

    //构造器
    //初始化工作
    public GroupChatServer(){
        try {
            //得到选择器
            selector = Selector.open();
            //ServerSocketChannel
            listenChannel =  ServerSocketChannel.open();
            //绑定端口
            listenChannel =  listenChannel.bind(new InetSocketAddress(PORT));
            listenChannel.configureBlocking(false);

            listenChannel.register(selector,SelectionKey.OP_ACCEPT);
            //TODO 进行监听的行为

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


    }
    //监听
    public void listen(){
        try {
            while (true){

                int count = selector.select();
                if (count>0){//有事件处理
                    //遍历得到的SelecttionKey集合
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()){
                        //取出selecttionKey
                        SelectionKey key = iterator.next();
                        //监听到accept
                        if (key.isAcceptable()){
                            SocketChannel sc = listenChannel.accept();
                            sc.configureBlocking(false);
                            //将该sc注册到selector
                            sc.register(selector,SelectionKey.OP_READ);
                            //提示
                            System.out.println(sc.getRemoteAddress()+"   上线");
                        }
                        if (key.isReadable()){//通道发送read事件,即通道是可读的状态
                            //TODO 处理写方法
                            readData(key);
                        }
                        //当前的key删除,防止重复出现
                        iterator.remove();
                    }
                }else{
                    System.out.println("等待---");
                }
            }
        }catch (IOException e){
            e.printStackTrace();
        }

    }
    private void readData(SelectionKey key){
        //取到关联的channel
        SocketChannel channel = null;
        try{
            //得到channel
            channel = (SocketChannel)key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int count = channel.read(buffer);
            if (count>0){
                //把缓冲区的数据转成字符串
                String msg = new String(buffer.array());
                //输出该信息
                System.out.println("from 客户端"+msg);

                //TODO 向其他的客户端发送信息
                sendInfoToOtherClients(msg,channel);

            }

        }catch (IOException e){
            try {
                System.out.println(channel.getRemoteAddress()+"离线了。。。");
                //取消注册
                key.cancel();
                //关闭通道
                channel.close();
            }catch (IOException e1){

            }
            e.printStackTrace();
        }
    }


    private void sendInfoToOtherClients(String msg,SocketChannel self)throws IOException{
        System.out.println("服务器转发信息中。。。。");

        //遍历所有注册selector上的Socketchannel ,并排除self
        for (SelectionKey key:selector.keys()){
            //通过key取出对应的SocketChannel
            SelectableChannel targetChannel = key.channel();

            if (targetChannel instanceof SocketChannel &&targetChannel !=self){
                //转型
                SocketChannel dest = (SocketChannel) targetChannel;
                //将mg存储到buffer中
                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                dest.write(buffer);
            }

        }
    }

    public static void main(String[] args) {
        //创建服务器对象
        GroupChatServer groupChatServer = new GroupChatServer();
        groupChatServer.listen();
    }


}


客户端代码:

package com.groupchat;

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

/**
 * @author linjiazeng
 * @version 1.0
 * @date 2020/10/15 23:11
 **/
public class GroupChatClient {
    //定义相关的属性
    private final String HOST = "127.0.0.1";//服务器的ip
    private final int PORT = 6667;
    private Selector selector;
    private SocketChannel socketChannel;
    private String username;

    public GroupChatClient()throws IOException {
        selector = Selector.open();
        //连接服务器
         socketChannel = SocketChannel.open(new InetSocketAddress(HOST,PORT));
         //设置为非阻塞
        socketChannel.configureBlocking(false);
        //将channel注册到selector
        socketChannel.register(selector, SelectionKey.OP_READ);

        //得到username
        username = socketChannel.getLocalAddress().toString().substring(1);
        System.out.println(username+"is ok...");

    }

    //先服务器发送消息
    public void sendInfo(String info){
        info = username+"说:"+info;
        try{
            socketChannel.write(ByteBuffer.wrap(info.getBytes()));

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

        }
    }

    //读取从服务器端回复的信息
    public void readInfo(){
        try{
            int readChannel = selector.select();
            if (readChannel>0){//有可以用的通道
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    if (key.isReadable()){
                        //得到相关的通道
                        SocketChannel sc =(SocketChannel) key.channel();
                        //得到一个buffer
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        //读取
                        sc.read(buffer);
                        //把读到的缓冲区的数据转成字符串
                        String msg = new String(buffer.array());
                        System.out.println(msg.trim());
                    }
                }

            }else {

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

    public static void main(String[] args) throws IOException{
        //启动客户端
        GroupChatClient groupChatClient = new GroupChatClient();

        //启动一个线程
        new Thread(){
            public void run(){
                while (true){
                    groupChatClient.readInfo();
                        try {
                            Thread.currentThread().sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                }
            }
        }.start();


        //发送数据给服务端
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()){
            String s = scanner.nextLine();
            groupChatClient.sendInfo(s);
        }
    }
}

客户端1:
在这里插入图片描述
客户端2:
在这里插入图片描述
服务端:
在这里插入图片描述

好了,就这样!!!明天开始正式进入Netty主题,因为Netty是对NIO进行进一步包装的,我们在学习Netty前需要先学习了解一些NIO和IO的一些知识,这样能够在以后的学习或工作中更好的理解Netty的工作原理。

打工人,打工神,打工都是人上人!!!!
哎,果然资本家都是吃人血馒头的!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值