NIO简介以及用NIO实现一个群聊系统

news2024/11/15 11:14:52

一、BIO的工作原理

传统Io(BIO)的本质就是面向字节流来进行数据传输的
在这里插入图片描述
①:当两个进程之间进行相互通信,我们需要建立一个用于传输数据的管道(输入流、输出流),原来我们传输数据面对的直接就是管道里面一个个字节数据的流动(我们弄了一个 byte 数组,来回进行数据传递),所以说原来的 IO 它面对的就是管道里面的一个数据流动,所以我们说原来的 IO 是面向流的。
②:我们说传统的 IO 还有一个特点就是,它是单向的。解释一下就是:如果说我们想把目标地点的数据读取到程序中来,我们需要建立一个管道,这个管道我们称为输入流。相应的,如果如果我们程序中有数据想要写到目标地点去,我们也得再建立一个管道,这个管道我们称为输出流。所以我们说传统的 IO 流是单向的。

二、传统BIO的缺点

BIO属于同步阻塞行IO,在服务器的实现模型为,每一个连接都要对应一个线程。当客户端有连接请求的时候,服务器端需要启动一个新的线程与之对应处理,这个模型有很多缺陷。**当客户端不做出进一步IO请求的时候,服务器端的线程就只能挂着,**不能去处理其他请求。这样会对造成不必要的线程开销。

三、阻塞与同步

同步和异步都是由基于应用程序和操作系统处理IO事件所采用的方式所决定的。
在这里插入图片描述
阻塞和非阻塞式指线程在得到调用结果之前是否被挂起,主要针对线程。
在这里插入图片描述

四、NIO简介(同步非阻塞)

  • Java NIO全称java non-blocking IO, 是指JDK提供的新API。从JDK1.4开始,Java提供了一系列改进的输入/输出的新特性,被统称为NIO(即New IO),是同步非阻塞的。
  • NIO是一种面向缓冲区的、基于通道的IO操作,NIO有三大核心部分: Channel(通道), Buffer(缓冲区),Selector(选择器)
  • java NIO的运行模式是: 客户端发送的链接请求都会被注册到Selector(选择器)上,多路复用器轮询到有I/O请求时才会启动一个线程去服务。

五、NIO三大核心原理

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

缓冲区本质上就是一块内存,数据的读写都是通过Buffer类实现的。缓冲区buffer主要是和通道数据交互,即从通道中读入数据到缓冲区,和从缓冲区中把数据写入到通道中,通过这样完成对数据的传输。

Channel(通道)

java NIO的类似于流,但是又有些不同:既可以从通道中读取数据,又可以写数据到通道。但流的(input和output)读写通常是单向的。通道可以非阻塞读取和写入通道,通道可以支持读取或写入缓冲区,也支持异步读写。

Selector选择器
Selector是一个java NIO组件,可以检测一个或多个NIO通道,并确定已经准备好进行读取或者写入。这样,一个单独的线程就可以管理多个Channel,从而管理多个网络连接,提高效率。
在这里插入图片描述

  • 每个channel都会对应一个Buffer
  • 一个线程对应Selector,一个Selector对应多个Channel
  • 程序切换到那个channel是由事件决定
  • Selector会根据不同的事件,在各个通道上切换
  • Buffer就是一个内存块,底层就是一个数组,数据的读取和写入都是通过Buffer来实现的

六、NIO三板斧

在这里插入图片描述

七、NIO实现一个群聊系统

逻辑简述

服务器:

1)创建服务器NIO通道,绑定端口并启动服务器
2)开启非阻塞模式
3)创建选择器、并把通道注册到选择器上,关心的事件为新连接
4)循环监听选择器的事件,
5)监听到新连接事件:
       5.1) 建立连接、创建客户端通道
       5.2)客户端通道设置非阻塞
       5.3)客户端注册到选择器上,关心的事件为读
6)监听到读 事件
       6.1)获取到发送数据的客户端通道
       6.2)把通道数据写入到一个缓冲区中
       6.3)打印数据
       6.4)发送给其他注册在选择器上的客户端,排除自己

客户器:

1)创建客户端通道,连接服务器 ip和端口
2)创建选择器,注册客户端通道到选择器上,关心的事件为读
3)开启一个线程 循环监听选择器事件
4)监听到读事件后
       4.1)从通道中把数据读到缓冲区中
       4.2)打印数据
5)主线程循环用scanner 来监听控制台输入
       5.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;
 
/**
 
 */
public class GroupChatServer {
 
    private int port = 8888;
 
    private ServerSocketChannel serverSocketChannel;
 
    private Selector selector;
 
 
    public GroupChatServer() throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(port));
 
        //创建选择器
        selector = Selector.open();
        //通道注册到选择器上,关心的事件为 OP_ACCEPT:新连接
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("server is ok");
    }
 
    public void listener() throws IOException {
        for (; ; ) {
            if (selector.select() == 0) {
                continue;
            }
            //监听到时间
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                if (selectionKey.isAcceptable()) {//新连接事件
                    newConnection();
                }
                if (selectionKey.isReadable()) {//客户端消息事件
                    clientMsg(selectionKey);
                }
                iterator.remove();
            }
        }
    }
 
    /**
     * 客户端消息处理
    
     */
    private void clientMsg(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
        try {
            //通道数据读取到 byteBuffer缓冲区
            socketChannel.read(byteBuffer);
            //创建一个数组用于接受 缓冲区的本次写入的数据。
            byte[] bytes = new byte[byteBuffer.limit()];
            //转换模式 写->读
            byteBuffer.flip();
            //获取数据到 bytes 中 从位置0开始到limit结束
            byteBuffer.get(bytes, 0, byteBuffer.limit());
            String msg = socketChannel.getRemoteAddress() + "说:" + new String(bytes, "utf-8");
            //倒带这个缓冲区。位置设置为零,标记为-1.这样下次写入数据会从0开始写。但是如果下次的数据比这次少。那么使用 byteBuffer.array方法返回的byte数组数据会包含上一次的部分数据
            //例如 上次写入了 11111 倒带后 下次写入了 22 读取出来 却是 22111
            byteBuffer.rewind();
            System.out.println(msg);
            //发送给其他客户端
            sendOuterClient(msg, socketChannel);
        } catch (Exception e) {
            System.out.println(socketChannel.getRemoteAddress() + ":下线了");
            socketChannel.close();
        }
    }
 
    /**
     * 发送给其他客户端
     *
     * @param msg           要发送的消息
     * @param socketChannel 要排除的客户端
     * @throws IOException
     */
    private void sendOuterClient(String msg, SocketChannel socketChannel) throws IOException {
        //获取selector上注册的全部通道集合
        Set<SelectionKey> keys = selector.keys();
        for (SelectionKey key : keys) {
            SelectableChannel channel = key.channel();
            //判断通道是客户端通道(因为服务器的通道也注册在该选择器上),并且排除发送人的通道
            if (channel instanceof SocketChannel && !channel.equals(socketChannel)) {
                try {
                    ((SocketChannel) channel).write(ByteBuffer.wrap(msg.getBytes()));
                } catch (Exception e) {
                    channel.close();
                    System.out.println(((SocketChannel) channel).getRemoteAddress() + ":已下线");
                }
            }
        }
    }
 
    /**
     * 新连接处理方法
     * @throws IOException
     */
    private void newConnection() throws IOException {
        //连接获取SocketChannel
        SocketChannel socketChannel = serverSocketChannel.accept();
        //设置非阻塞
        socketChannel.configureBlocking(false);
        //注册到选择器上,关心的事件是读,并附带一个ByteBuffer对象
        socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
        System.out.println(socketChannel.getRemoteAddress() + " 上线了");
    }
 
    public static void main(String[] args) throws IOException {
        GroupChatServer groupChatServer = new GroupChatServer();
        //启动监听
        groupChatServer.listener();
    }
}

客户端:

 
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.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
 
/**    
 */
public class GroupChatClient {
 
    private Selector selector;
 
    private SocketChannel socketChannel;
 
    public GroupChatClient(String host, int port) throws IOException {
        socketChannel = SocketChannel.open(new InetSocketAddress(host, port));
        socketChannel.configureBlocking(false);
        selector = Selector.open();
        //注册事件,关心读事件
        socketChannel.register(selector, SelectionKey.OP_READ);
        System.out.println("我是:" + socketChannel.getLocalAddress());
    }
    /**
     * 读消息
     */
    private void read() {
        try {
            if(selector.select() == 0){
                //没有事件,return
                return;
            }
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();
                if(selectionKey.isReadable()){//判断是 读 事件
                    SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    //读取数据到  byteBuffer 缓冲区
                    socketChannel.read(byteBuffer);
                    //打印数据
                    System.out.println(new String(byteBuffer.array()));
                }
                iterator.remove();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 发送数据
     * @param msg 消息
     * @throws IOException
     */
    private void send(String msg) throws IOException {
        socketChannel.write(ByteBuffer.wrap(new String(msg.getBytes(),"utf-8").getBytes()));
    }
 
 
    public static void main(String[] args) throws IOException {
        //创建客户端 指定 ip端口
        GroupChatClient groupChatClient = new GroupChatClient("127.0.0.1",8888);
        //启动一个线程来读取数据
        new Thread(()->{
            while (true){
                groupChatClient.read();
            }
        }).start();
        //Scanner 发送数据
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()){
            String s = scanner.nextLine();
            //发送数据
            groupChatClient.send(s);
        }
 
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1539349.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

利用Base64加密算法将数据加密解密

1. Base64加密算法 Base64准确来说并不像是一种加密算法&#xff0c;而更像是一种编码标准。 我们知道现在最为流行的编码标准就是ASCLL&#xff0c;它用八个二进制位&#xff08;一个char的大小&#xff09;表示了127个字符&#xff0c;任何二进制序列都可以用这127个字符表…

实用工具推荐:适用于 TypeScript 网络爬取的常用爬虫框架与库

随着互联网的迅猛发展&#xff0c;网络爬虫在信息收集、数据分析等领域扮演着重要角色。而在当前的技术环境下&#xff0c;使用TypeScript编写网络爬虫程序成为越来越流行的选择。TypeScript作为JavaScript的超集&#xff0c;通过类型检查和面向对象的特性&#xff0c;提高了代…

武汉星起航电商:跨境创业领航者,一站式服务助您轻松启航

在当今全球化的浪潮中&#xff0c;跨境电商以其独特的优势&#xff0c;成为越来越多创业者的首选。然而&#xff0c;如何在这个竞争激烈的市场中脱颖而出&#xff0c;实现业务的快速增长&#xff0c;却成为摆在创业者面前的一大难题。武汉星起航电子商务有限公司&#xff0c;正…

【go从入门到精通】if else 条件控制

作者简介&#xff1a; 高科&#xff0c;先后在 IBM PlatformComputing从事网格计算&#xff0c;淘米网&#xff0c;网易从事游戏服务器开发&#xff0c;拥有丰富的C&#xff0c;go等语言开发经验&#xff0c;mysql&#xff0c;mongo&#xff0c;redis等数据库&#xff0c;设计模…

面试算法-82-不同路径

题目 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xff1f; …

OpenHarmony实战开发-手写板应用开发操作流程

分布式手写板&#xff08;ArkTS&#xff09; 介绍 本篇Codelab使用设备管理及分布式键值数据库能力&#xff0c;实现多设备之间手写板应用拉起及同步书写内容的功能。操作流程&#xff1a; 设备连接同一无线网络&#xff0c;安装分布式手写板应用。进入应用&#xff0c;点击…

皓学IT:WEB06_ EL表达式JSTL标签库

一、EL表达式 1.1.特点 是一个由java开发的工具包 用于从特定域对象中读取并写入到响应体开发任务&#xff0c;不能向域对象中写入。 EL工具包自动存在Tomcat的lib中&#xff08;el-api.jar&#xff09;&#xff0c;开发是可以直接使用&#xff0c;无需其他额外的包。 标准…

亚马逊云科技:企业如何开启生成式AI之旅?

如果要评选最近两年全球科技行业最热门的细分领域&#xff0c;那么生成式AI绝对会以遥遥领先的票数成为当仁不让的冠军。 然而眼见生成式AI发展得如火如荼&#xff0c;越来越多的企业却陷入了深深的焦虑&#xff1a;应该如何开启生成式AI之旅&#xff1f;又该怎样搭建大模型&am…

33-Java服务定位器模式 (Service Locator Pattern)

Java服务定位器模式 实现范例 服务定位器模式&#xff08;Service Locator Pattern&#xff09;用于想使用 JNDI 查询定位各种服务的时候考虑到为某个服务查找 JNDI 的代价很高&#xff0c;服务定位器模式充分利用了缓存技术在首次请求某个服务时&#xff0c;服务定位器在 JNDI…

网站引入 Prism,使得代码高亮显示,并一键复制代码块

曾几何时&#xff0c;苦恼如何将本地写好的博文&#xff0c;更好的展示读者屏幕前&#xff1f;若只是简简单单的文章&#xff0c;其实还是很好的解决它的&#xff01;可是&#xff0c;像我们这样写技术文章&#xff08;有点牵强&#xff09;的&#xff0c;在文章内容嵌入部分代…

【C++庖丁解牛】二叉搜索树(Binary Search Tree,BST)

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 1. 二叉搜索树概念2. 二叉…

IDEA 下载依赖包源码报错 Cannot download sources Sources not found for:XXX

最近在做一个功能的时候想看一个库的源码&#xff0c;结果源码下不下来&#xff0c;报Cannot download sources Sources not found for:XXX,网上搜了半天&#xff0c;也找不到靠谱的结论 后来想了下&#xff0c;应该是镜像那边出了问题&#xff0c;把镜像一删&#xff0c;源码…

EPO企业生产运营数智化平台助力制造企业迈向智能制造

随着“中国制造2025”和工业4.0的不断推进&#xff0c;越来越多的制造企业准备迈入智能制造和智慧制造领域&#xff0c;实现数智化管理。企业通过搭建EPO企业生产运营平台&#xff0c;结合自身业务现状和数字化需求&#xff0c;从各个业务场景、部门人员、产品组成等方面进行分…

(一)、Doris安装使用(基于Doris 2.0.6)

第 1 章Doris简介 1.1、 Doris 概述 ​ Apache Doris由百度大数据部研发&#xff08;之前叫百度 Palo&#xff0c;2018年贡献到 Apache 社区后&#xff0c;更名为 Doris&#xff09;&#xff0c;在百度内部&#xff0c;有超过200个产品线在使用&#xff0c;部署机器超过1000台…

国际品牌交期长 雷卯来帮忙

在当今的电子元器件市场中&#xff0c;防静电电子元器件的需求日益增长。无论是通信安防、医疗、消费类电子、照明行业、航空航天还是汽车电子等领域都会使用到防静电产品&#xff0c;使得防静电电子元器件的需求也呈现出爆发式的增长。在这一市场中&#xff0c;雷卯品牌凭借其…

Chain of Note-CoN增强检索增强型语言模型的鲁棒性

Enhancing Robustness in Retrieval-Augmented Language Models 检索增强型语言模型&#xff08;RALMs&#xff09;在大型语言模型的能力上取得了重大进步&#xff0c;特别是在利用外部知识源减少事实性幻觉方面。然而&#xff0c;检索到的信息的可靠性并不总是有保证的。检索…

六.排序nb三人组(快速排序)

目录 17-快速排序原理介绍 思路: 18-快速排序代码实现 19-快速排序代码实现2 缺点: 递归的限度: 17-快速排序原理介绍 思路: --先找一个变量把 5(第一个数) 存起来, (两个箭头分别是left , right) --左边有一个空位, 发现左边的位置是给比5小的值准备的. --找比5小的值…

算法体系-12 第 十二 二叉树的基本算法 下

一 实现二叉树的按层遍历 1.1 描述 1&#xff09;其实就是宽度优先遍历&#xff0c;用队列 2&#xff09;可以通过设置flag变量的方式&#xff0c;来发现某一层的结束&#xff08;看题目&#xff09;看下边的第四题解答 1.2 代码 public class Code01_LevelTraversalBT {publ…

【LabVIEW FPGA入门】FPGA不同传递数据方法比较

数据共享方法的选择应基于应用的需要。根据应用程序的重要特性&#xff0c;所讨论的任何一种方法都可能是合适的。 传输方法FPGA资源损耗&#xff1f;不同时钟源之间传递数据&#xff1f;新数据通知&#xff1f;常见用途变量逻辑片是是否提取最新数据存储器存储器是否否提取最新…

ubuntu20.04_PX4_1.13

说在前面&#xff1a;&#xff08;最好找一个干净的Ubuntu系统&#xff09;如果配置环境的过程中出现很多编译的错误或者依赖冲突&#xff0c;还是建议新建一个虚拟机&#xff0c;或者重装Ubuntu系统&#xff0c;这样会避免很多麻烦&#x1f490; &#xff0c; 安装PX4 1.13.2 …