java NIO 学习笔记
2023-09-27 14:27:01 时间
项目组是做IM产品的,服务端当然用的是NIO技术做通信底层。可是一直都是对NIO有些理论的了解,没有实践。近期有空了。就实践了下NIO。
NIO,新IO,也称之为非堵塞IO。
非堵塞是它跟传统IO的最重要的差别之中的一个。传统IO用Socket进行通信,NIO则用channel进行消息交互。channel必须注冊到selector上。把它感兴趣的事件告诉selector。这是个观察者模式的实现。能够这样描写叙述channel和selector的关系,channel是火车轨道,selector是火车调度室。
多个channel能够注冊到同一个selector上。在一个线程了对这些channel进行调度。然而传统IO须要为每个socket创建一个线程。
这也是NIO的优势之中的一个,不须要太多的线程。
NIO版本号的服务端代码:
package cn.nio; 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.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; public class NioServer { private Selector serverSelector; public NioServer initServer(int port) throws IOException { ServerSocketChannel serverChannel = ServerSocketChannel.open(); // 非堵塞模式 serverChannel.configureBlocking(false); // 绑定端口号 serverChannel.socket().bind(new InetSocketAddress(port)); this.serverSelector = Selector.open(); serverChannel.register(serverSelector, SelectionKey.OP_ACCEPT); return this; } public void startListening() throws IOException { System.out.println("服务端启动成功!"); while (true) { // 当注冊的事件到达时。方法返回。否则,该方法会一直堵塞 serverSelector.select(); Iterator<SelectionKey> iterator = this.serverSelector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey key = (SelectionKey) iterator.next(); // 必须删除。否则下次遍历时还会遍历旧的key iterator.remove(); if (key.isAcceptable()) { accept(key); } else if (key.isReadable()) { read(key); } } } } // 接受client的连接请求 private void accept(SelectionKey key) throws IOException { ServerSocketChannel server = (ServerSocketChannel) key.channel(); // 获得和client连接的通道 SocketChannel channel = server.accept(); // 设置成非堵塞 channel.configureBlocking(false); channel.write(ByteBuffer.wrap(new String("client连接成功\n").getBytes())); // 注冊 channel.register(this.serverSelector, SelectionKey.OP_READ); } private void read(SelectionKey key) { try { SocketChannel channel = (SocketChannel) key.channel(); // 创建读取的缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); channel.read(buffer); String msg = new String(buffer.array()).trim(); System.out.println("服务端:" + msg); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException { new NioServer().initServer(8080).startListening(); } }NIO版本号的client代码:
package cn.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; public class NioClient { private Selector selector; public NioClient initClient(String ip, int port) throws IOException { SocketChannel channel = SocketChannel.open(); // 非堵塞 channel.configureBlocking(false); this.selector = Selector.open(); // connect方法的凝视:此方法返回 false。而且必须在以后通过调用 finishConnect 方法来完毕该连接操作。 boolean result = channel.connect(new InetSocketAddress(ip, port)); System.out.println(result); // 返回false // 注冊 channel.register(selector, SelectionKey.OP_CONNECT); return this; } public void startListening() throws IOException { while (true) { selector.select(); Iterator<SelectionKey> iterator = this.selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey key = (SelectionKey) iterator.next(); // 必须删除,否则下次遍历时还会遍历旧的key iterator.remove(); if (key.isConnectable()) { connect(key); } else if (key.isReadable()) { read(key); } } } } private void connect(SelectionKey key) throws IOException, ClosedChannelException { SocketChannel channel = (SocketChannel) key.channel(); // 假设正在连接,则完毕连接 if (channel.isConnectionPending()) { channel.finishConnect(); } channel.configureBlocking(false); channel.write(ByteBuffer.wrap(new String("client连接成功").getBytes())); // 注冊 channel.register(this.selector, SelectionKey.OP_READ); } private void read(SelectionKey key) throws IOException { // server可读取消息:得到事件发生的Socket通道 SocketChannel channel = (SocketChannel) key.channel(); // 创建读取的缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); channel.read(buffer); String msg = new String(buffer.array()).trim(); System.out.println("client收到信息:" + msg); } /** * 启动client測试 * * @throws IOException */ public static void main(String[] args) throws IOException { new NioClient().initClient("localhost", 8080).startListening(); } }
考虑到其它语言不一定有NIO技术。我们的client都是C++的。
那么一个NIO服务端能否够和一个非NIO的client进行通信呢?做了一个传统IO的client。做測试。可行!
package cn.nio; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; public class NormalClient { Socket clientSocket = null; public NormalClient initClient(String ip, int port) { try { clientSocket = new Socket(ip, port); } catch (IOException e) { e.printStackTrace(); } return this; } public void read() throws IOException { PrintWriter pw = new PrintWriter((clientSocket.getOutputStream())); BufferedReader br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); String line = null; while (true) { pw.println("客户端发来的消息"); pw.flush(); line = br.readLine(); System.out.println(line); } } public static void main(String[] args) throws IOException { new NormalClient().initClient("127.0.0.1", 8000).read(); } }
写这个client的时候。client一直无法收到服务端的消息。请教资深同事,问题在readline()方法上。假设调用的是readline(),那么服务端给client发消息时,须要传换行符。
相关文章
- Java NIO Path 接口
- JAVA nio 2 定义 Path 类
- Java面试题相关内容
- 为什么不建议使用 Java 原生 NIO?
- 由浅入深!java性能调优实战网盘
- 安装java为什么要配置环境变量
- Java NIO:Buffer 与 Channel
- Java基础——NIO(一)通道与缓冲区
- java使用javamail读取邮箱(收件箱为例)
- java.nio.Buffer flip()方法的用法详解
- [nacos]JAR启动并加载/解析Nacos yml格式的配置文件时,报“java.nio.charset.MalformedInputException: Input length = 1 ”
- Java NIO-3
- JAVA语言学校的危险性
- java多线程-CyclicBarrier
- Java BIO、NIO、AIO 学习
- Java中调用SQL Server存储过程示例
- 【netty in action】学习笔记-第一章 了解java NIO(1)
- Java字节流基础详解(InputStream/OutputStream)
- 18.JAVA-pull解析XML
- 5种调优Java NIO和NIO.2的方式
- SpringBoot2.x系列教程(五十五)Mybatis反向生成Java代码