• NIO通信实现


    目录

    NIO编程

    BIO与NIO区别

    基于channel通道实现通信

    服务端代码

    客户端代码

    通信结果

    Channel通道与流的区别

    基于Selector选择器服务端实现通信

    服务端代码

    通信结果


    之前手写了同步阻塞的socket通信聊天室:Java实现ChatRoom_zhangm2020的博客-CSDN博客

    NIO是怎么实现的?那跟同步阻塞有什么区别呢?

    NIO编程

    三大核心部分:Channel(通道)、Buffer(缓冲区)、Selector(选择器)

    NIO是面向缓冲区编程的。数据读取到一个缓冲区中,需要时可在缓冲区中前后移动。Selector选择器用于监听多个通道的事件(连接请求,数据到达等)因此使用单个线程就可以监听多个客户端通道

    BIO与NIO区别

    • BIO以流的方式处理数据;NIO以缓冲区的方式处理数据
    • BIO是阻塞的;NIO是非阻塞的
    • BIO基于字节流和字符流进行操作;NIO基于Channel通道和Buffer缓冲区进行操作

    基于channel通道实现通信

    场景:一个服务端一个客户端(不涉及Selector)

    服务端代码

    1. public class NIOServer {
    2. public static void main(String[] args) throws IOException, InterruptedException {
    3. //1. 打开一个服务端通道
    4. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    5. //2. 绑定对应的端口号
    6. serverSocketChannel.bind(new InetSocketAddress(9999));
    7. //3. 通道默认是阻塞的,需要设置为非阻塞
    8. serverSocketChannel.configureBlocking(false);
    9. System.out.println("服务端启动成功");
    10. while (true) {
    11. //4. 检查是否有客户端连接 有客户端连接会返回对应的通道
    12. SocketChannel socketChannel = serverSocketChannel.accept();
    13. if (null == socketChannel) {
    14. System.out.println("没有客户连接,非阻塞我去干其他事情");
    15. Thread.sleep(2000);
    16. continue;
    17. }
    18. //5. 获取客户端传递过来的数据,并把数据放在byteBuffer这个缓冲区中
    19. ByteBuffer allocate = ByteBuffer.allocate(1024);
    20. int read = socketChannel.read(allocate);
    21. String message = new String(allocate.array(), 0, read);
    22. System.out.println("读到客户端消息:" + message);
    23. //6. 给客户端回写数据
    24. socketChannel.write(ByteBuffer.wrap("我是服务端,我收到了你的消息".getBytes()));
    25. //7. 释放资源
    26. socketChannel.close();
    27. }
    28. }
    29. }

    注:java.nio.channels.SocketChannel#read(java.nio.ByteBuffer)返回值

    //正数: 表示本次读到的有效字节个数. //0 : 表示本次没有读到有效字节. //-1 : 表示读到了末尾

    该方法是阻塞的,如果客户端连接后一直没有发数据,那么这个方法会阻塞等待客户端信息,所以会造成服务端资源的浪费,场景2会涉及到Selector选择器,可以避免这个问题

    客户端代码

    1. public class NIOClient {
    2. public static void main(String[] args) throws IOException {
    3. //1. 打开通道
    4. SocketChannel socketChannel = SocketChannel.open();
    5. //2. 设置连接IP和端口号
    6. socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
    7. //3. 写出数据
    8. socketChannel.write(ByteBuffer.wrap("你好,我是客户端".getBytes()));
    9. //4. 读取服务器写回的数据
    10. ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    11. int read = socketChannel.read(byteBuffer);
    12. if (read > 0) {
    13. System.out.println("获取到服务端消息:" + new String(byteBuffer.array(), 0, read));
    14. //5. 释放资源
    15. socketChannel.close();
    16. }
    17. }
    18. }

    通信结果

    Channel通道与流的区别

    • 通道可读可写;流是单向的(只能读或者写,输入流输出流)
    • 通道可以异步读写(非阻塞)
    • 通道是基于缓冲区buffer来读写的

    基于Selector选择器服务端实现通信

    场景:一个服务端多个客户端

    服务端代码

    1. public class NIOSelectorServer {
    2. public static void main(String[] args) throws IOException {
    3. //1. 打开一个服务端通道
    4. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    5. //2. 绑定对应的端口号
    6. serverSocketChannel.bind(new InetSocketAddress(9999));
    7. //3. 通道默认是阻塞的,需要设置为非阻塞
    8. serverSocketChannel.configureBlocking(false);
    9. //4. 创建选择器
    10. Selector selector = Selector.open();
    11. //5. 将服务端通道注册到选择器上,并指定注册监听的事件为OP_ACCEPT
    12. serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    13. System.out.println("服务端启动成功");
    14. while (true) {
    15. //6. 检查选择器是否有事件
    16. //返回值:事件个数
    17. int select = selector.select(2000);
    18. if (0 == select) {
    19. System.out.println("没有事件");
    20. continue;
    21. }
    22. //7. 获取事件集合
    23. Set selectionKeys = selector.selectedKeys();
    24. Iterator iterator = selectionKeys.iterator();
    25. while (iterator.hasNext()) {
    26. SelectionKey selectionKey = iterator.next();
    27. //8. 判断事件是否是客户端连接事件SelectionKey.isAcceptable()
    28. if (selectionKey.isAcceptable()) {
    29. System.out.println("获取到连接继续事件");
    30. //9. 得到客户端通道,并将通道注册到选择器上, 并指定监听事件为OP_READ
    31. SocketChannel socketChannel = serverSocketChannel.accept();
    32. System.out.println("客户端已连接" + socketChannel);
    33. //通道必须设置为非阻塞,selector是轮询监听,所以不能被阻塞到某个通道上
    34. socketChannel.configureBlocking(false);
    35. socketChannel.register(selector, SelectionKey.OP_READ);
    36. }
    37. //10. 判断是否是客户端读就绪事件SelectionKey.isReadable()
    38. if (selectionKey.isReadable()) {
    39. System.out.println("获取到读就绪事件");
    40. //11. 得到客户端通道,读取数据到缓冲区
    41. //获取selector监听到的通道事件
    42. SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
    43. ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    44. int read = socketChannel.read(byteBuffer);
    45. if (read > 0) {
    46. System.out.println("读到客户端消息:" + new String(byteBuffer.array(), 0, read));
    47. //12. 给客户端回写数据
    48. socketChannel.write(ByteBuffer.wrap("我是服务端,我收到了你的消息".getBytes()));
    49. socketChannel.close();
    50. }
    51. }
    52. //13. 从集合中删除对应的事件, 因为防止二次处理
    53. iterator.remove();
    54. }
    55. }
    56. }
    57. }

    java.nio.channels.Selector#select(long)返回值:事件个数 

    客户端代码不变

    通信结果

    开启两个客户端:客户端0,客户端1

  • 相关阅读:
    RS-485 通讯协议简介
    c++11产生指定范围内均匀分布随机数、产生大量不重复随机数
    【关系抽取】基于Bert的信息抽取模型CasRel
    Termux安装node
    Linux项目自动化构建工具-make/Makefile的打字练习
    JVM类加载机制、双亲委派和SPI机制
    小程序封装组件简单案例,所有小程序适用(传入参数、外抛事件、传入样式)
    ADS 使用调整器动态调参教程(Tuning)
    mapbox使用marker创建html点位信息
    网课查题公众号如何搭建查题系统
  • 原文地址:https://blog.csdn.net/tec_1535/article/details/126406690