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

2024-03-19 13:36
文章标签 实现 系统 nio 简介 群聊

本文主要是介绍NIO简介以及用NIO实现一个群聊系统,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、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 读取出来 却是 22111byteBuffer.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 {//连接获取SocketChannelSocketChannel 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){//没有事件,returnreturn;}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);}}
}

这篇关于NIO简介以及用NIO实现一个群聊系统的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/826160

相关文章

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

Spring Security简介、使用与最佳实践

《SpringSecurity简介、使用与最佳实践》SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,本文给大家介绍SpringSec... 目录一、如何理解 Spring Security?—— 核心思想二、如何在 Java 项目中使用?——

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Nginx部署HTTP/3的实现步骤

《Nginx部署HTTP/3的实现步骤》本文介绍了在Nginx中部署HTTP/3的详细步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前提条件第一步:安装必要的依赖库第二步:获取并构建 BoringSSL第三步:获取 Nginx

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima