NIO 的基本使用

yls 2020/5/23

NIO中buffer的使用

/**
 * 测试 nio 中 buffer的使用
 * buffer 内部就是一个数组 : final int[] hb;  、
 *
 * buffer 定义了 position,capacity,limit,mark四个属性来标记buffer中的数据信息
 * 可以通过debug的方式跟踪查看值的变化
 *     private int mark = -1;    标记
 *     private int position = 0;  下一个要被读或写的元素的索引,每次读写都会改变其值,为下次读写做准备
 *     private int limit;   表示缓冲区的当前终点,不能对超过limit限制的缓冲区别进行读写,limit可以修改
 *     private int capacity;   容量,创建buffer时设置,不能被改变
 *
 * buffer  读写切换时需要执行 flip()方法
 *      public final Buffer flip() {
 *         limit = position;
 *         position = 0;
 *         mark = -1;
 *         return this;
 *     }
 * buffer清除数据时调用 clear()方法,只改变标记的指向位置,不真正删除底层数组的值
 *     public final Buffer clear() {
 *         position = 0;
 *         limit = capacity;
 *         mark = -1;
 *         return this;
 *     }
 */
public class BasicBuffer {
    public static void main(String[] args) {
        final IntBuffer intBuffer = IntBuffer.allocate(5);
      for (int i = 0; i <intBuffer.capacity() ; i++) {
            intBuffer.put(i*2);
        }
        intBuffer.flip();
        for (int i = 0; i < intBuffer.capacity(); i++) {
            System.out.println(intBuffer.get());
        }
    }
}

NIO中Socket通信实例

1.服务端
/**
 * 同步 非阻塞
 * FileChannel.transferTo实现了零拷贝,效率高,三十在windows中一次只能传8M,所以大文件需要断点续传
 */
public class NioServer {
    public static void main(String[] args) throws IOException {
        //创建一个ServerSocketChannel
        final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //绑定一个端口,在服务端监听
        serverSocketChannel.socket().bind(new InetSocketAddress(7777));
        //设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        //得到一个selector对象
        Selector selector = Selector.open();
        //serverSocketChannel注册到selector,关心 事件 OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //循环等待客户端连接
        while (true) {
            //等待一秒,若没有事件发生,返回
            if (selector.select(1000) == 0) {
                System.out.println("服务器等待了1秒,没有请求连接。。。。");
                continue;
            }
            //若返回的值>0,说明已经获取到相关的事件,则获取到相关的selectionKeys集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            //使用迭代器遍历
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                //获取到相应的key
                SelectionKey key = iterator.next();
                //根据key对应的通道发生的事件做出处理
                if (key.isAcceptable()) {//如果是 isAcceptable,有新的连接
                    //这里的key对应的channel一定是serverSocketChannel
                    //为该客户端生成一个socketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    //将新生成的socketChannel设置为非阻塞模式,否则会抛出异常
                    socketChannel.configureBlocking(false);
                    //将socketChannel注册到selector,关心事件为OP_READ,并关联一个buffer
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(128));
                }
                if (key.isReadable()) { //发生 isReadable 事件,表示有新数据发送过来
                    //根据key反向获取到相应的channel
                    SocketChannel channel = (SocketChannel) key.channel();
                    //获取到channel关联的buffer
                    ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
                    //先将buffer置于初始状态
                    byteBuffer.clear();
                    //将channel中的数据读到buffer中
                    channel.read(byteBuffer);
                    //buffer读写切换
                    byteBuffer.flip();
                    //从buffer中读有效数据到bytes
                    //byteBuffer.array()直接返回buffer底层数组,如果后面发送的数据比之前发送的少,会将之前获取的值获取出来
                    byte[] bytes = new byte[byteBuffer.limit()];
                    byteBuffer.get(bytes);
                    System.out.println("from 客户端: " + new String(bytes));
                }

                //手动从集合中移除当前的key,防止重复操作
                iterator.remove();
            }
        }
    }
}
2.客户端
public class NioClient {
    public static void main(String[] args) throws IOException {
        //获取一个 socketChannel
        final SocketChannel socketChannel = SocketChannel.open();
        //设置 socketChannel 非阻塞
        socketChannel.configureBlocking(false);
        //提供服务端的ip和端口,连接服务端,不阻塞
        //通过 socketChannel.finishConnect() 判断是否连接成功
        boolean connect = socketChannel.connect(new InetSocketAddress("127.0.0.1", 6999));
        if (!connect) {
            while (!socketChannel.finishConnect()) {
                System.out.println("因为连接需要时间,客户端不会阻塞,可以做其它事情。。。");
            }
        }
        //连接成功后。。。。
        System.out.println("1...");
        String s = "大忽忽";
        // Wraps a byte array into a buffer
        ByteBuffer byteBuffer = ByteBuffer.wrap(s.getBytes());
        //发送数据
        final int write = socketChannel.write(byteBuffer);
        System.out.println("2....");
        System.in.read();
    }
}

NIO中Scattering、Gathering的使用

1.服务端
/**
 * scattering: 分散,把数据写入buffer时,可以采用buffer数组,依次写
 * gathering:  聚合,从buffer中读取数据时,可以采用buffer数组,依次读
 */
public class ScatteringAndGathering {
    public static void main(String[] args) throws IOException {

        //绑定端口到socket,并启动
        final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(7000));
        //创建buffer数组
        final ByteBuffer[] byteBuffers = new ByteBuffer[2];
        byteBuffers[0] = ByteBuffer.allocate(4);
        byteBuffers[1] = ByteBuffer.allocate(4);
        //等待客户端连接,连接成功后生成SocketChannel
        final SocketChannel socketChannel = serverSocketChannel.accept();
        //循环读取
        while (true) {
            //从channel读取数据到buffer数组
            long read = socketChannel.read(byteBuffers);

            System.out.println("read=========" + read);
            if (read == 0 || read == -1) {
                break;
            }
            Arrays.asList(byteBuffers).forEach(buffer -> {
                System.out.println("position=" + buffer.position() + ", limit=" + buffer.limit());
            });

            //读写切换
            Arrays.asList(byteBuffers).forEach(buffer -> {
                buffer.flip();
            });

            //将buffer数组中的数据写入channel,显示到客户端
            final long write = socketChannel.write(byteBuffers);

            //将每个buffer置于初始状态
            Arrays.asList(byteBuffers).forEach(buffer -> {
                buffer.clear();
            });
        }


    }
}
2.客户端使用上边 Socket通信实例 中的就可以(改一下端口号)

NIO 使用 Socket通信实现一个群发系统

1.服务端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * 简单的群聊系统(NIO 实现)
 * 服务端:可以监听客户端的上线和离线,可以接收客户端发送的数据并转发到其它客户端
 * 客户端:可以不阻塞的发送数据和接收其它客户端发送的数据
 */
public class GroupChatServer {
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;
    private static final int port = 7999;

    public GroupChatServer() throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    //监听
    public void listen() {
        try {
            while (true) {
                final int select = selector.select(2000);
                if (select > 0) {//有事件发生
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()) {
                        //获取key
                        SelectionKey key = iterator.next();
                        //根据key对应的通道发生的事件做出处理
                        if (key.isAcceptable()) {//如果是 isAcceptable,有新的连接
                            //这里的key对应的channel一定是serverSocketChannel
                            //为该客户端生成一个socketChannel
//                            SocketChannel socketChannel = serverSocketChannel.accept();
                            SocketChannel socketChannel = ((ServerSocketChannel) key.channel()).accept();
                            //将新生成的socketChannel设置为非阻塞模式,否则会抛出异常
                            socketChannel.configureBlocking(false);
                            //将socketChannel注册到selector,关心事件为OP_READ,并关联一个buffer
                            socketChannel.register(selector, SelectionKey.OP_READ);
                            //提示
                            System.out.println(socketChannel.getRemoteAddress() + ",上线了");
                        }
                        if (key.isReadable()) { //发生 isReadable 事件,表示有新数据发送过来
                            //专门写方法,处理读操作
                            readData(key);
                        }

                        //手动从集合中移除当前的key,防止重复操作
                        iterator.remove();
                    }
                } else {
                    System.out.println("等待。。。");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void readData(SelectionKey key) {

        SocketChannel channel = null;
        try {
            //根据key反向获取到相应的channel
            channel = (SocketChannel) key.channel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(128);
            //将channel中的数据读到buffer中
            final int read = channel.read(byteBuffer);
            String msg = new String(byteBuffer.array(), 0, read);
            System.out.println("from 客户端: " + msg);
            //转发消息到其它客户端(除了自己),专门写一个方法
            sendMsgToOthers(msg, channel);

        } catch (Exception e) {
            e.printStackTrace();
            try {
                System.out.println(channel.getRemoteAddress() + "离线了。。");
                //关闭通道
                channel.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }

        }
    }

    private void sendMsgToOthers(String msg, SocketChannel self) {

        final Set<SelectionKey> keys = selector.keys();
        keys.forEach((key) -> {
             SelectableChannel channel = key.channel();
            if (channel instanceof SocketChannel && channel != self) {
                SocketChannel socketChannel=(SocketChannel)channel;
                final ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                try {
                    socketChannel.write(buffer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public static void main(String[] args) throws IOException {
        final GroupChatServer groupChatServer = new GroupChatServer();
        groupChatServer.listen();
    }
}
2.客户端
public class GroupChatClient {
    private String ip = "127.0.0.1";
    private int port = 7999;
    private Selector selector;
    private SocketChannel socketChannel;
    private String name;

    public GroupChatClient() throws IOException {
        socketChannel = SocketChannel.open(new InetSocketAddress(ip, port));
        socketChannel.configureBlocking(false);
        selector = Selector.open();
        socketChannel.register(selector, SelectionKey.OP_READ);
        name = socketChannel.getLocalAddress().toString().substring(1);
        System.out.println(name + " is start ..");
    }

    //发送消息
    public void sendData(String msg) throws IOException {
        msg = name + " 说:" + msg;
        socketChannel.write(ByteBuffer.wrap(msg.getBytes()));
    }

    //接收消息
    public void rec() {
        try {
            while (true) {
                final int select = selector.select();
                if (select > 0) {
                    final Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()) {
                        final SelectionKey next = iterator.next();
                        if (next.isReadable()) {
                            SocketChannel channel = (SocketChannel) next.channel();
                            final ByteBuffer allocate = ByteBuffer.allocate(128);
                            final int read = channel.read(allocate);
                            System.out.println(new String(allocate.array(), 0, read));
                        }
                        iterator.remove();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        final GroupChatClient groupChatClient = new GroupChatClient();
        //一个线程专门接收数据
        new Thread(() -> {
            groupChatClient.rec();
        }, "接收数据线程").start();
        //主线程用来发送数据
        final Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            final String s = scanner.nextLine();
            groupChatClient.sendData(s);
        }
    }
}
posted @ 2020-05-23 00:11  她的开呀  阅读(226)  评论(0编辑  收藏  举报