java NIO 随笔
一,NIO入门
NIO 是new io的缩写,说实话,nio api比较难用,所用大家需要采用网络通信的时候,普通首先想到的是netty,不直接使用NIO,但是你不了解NIO,说实话,你也理解不了netty
好多人不理解socket 是干啥的,只知道socket是Java 用来通信的。应用层协议(HTTP 协议)如何发送数据,这个协议使我们自己定义的,我们需要和其他机器通信,就必须通过
TCP协议来完成数据传输。你可以理解socket就是应用层和传输层的适配器,负责将应用层的数据转换为TCP协议需要的数据。
我们先来一个demo,通过TCP 协议发送一个文件 我们先学会用,在来考虑细节。
package com.ppdai.user.weixin.controller.test; import com.ppdai.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Constants; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.List;
import java.util.Scanner; /**
* Created by huxuhong on 2020/3/11.
*/
@Slf4j
public class TcpClient {
/**
* @param args
*/
public static void main(String[] args) {
try {
startClient("localhost",8080);
} catch (IOException e) {
e.printStackTrace();
}
} public static void startClient(String serverIp, int serverPort) throws IOException{
log.info("创建一个SocketChannel,指定为非阻塞模式");
/*
* 创建一个SocketChannel,指定为非阻塞模式
* A selectable channel for stream-oriented connecting sockets.
*/
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false); /*
* 创建一个事件选择器Selector
*/
Selector selector = Selector.open(); /*
* 将创建的SocketChannel注册到指定的Selector上,并指定关注的事件类型为OP_CONNECT
*/
SelectionKey selectionKey = socketChannel.register(selector,SelectionKey.OP_CONNECT); /*
* 连接到指定的服务地址,这里有坑socketChannel.connect 异步接口,但是有可能理解返回为true,这里不引申怎么处理啦,等分析kafka-client源码的时候在做解析
* (具体啥时候返回false,true,请看API说明),此时不会触发监控(即selector.select(),此方法可能返回0)
*/
Boolean connection = socketChannel.connect(new InetSocketAddress(serverIp, serverPort));
System.out.println("链接结果:"+connection); /**
* 顾名思义,针对文件的一种channel,类似IO中的FileInputStream,
* 这个类很重要,kafak broker的顺序存储,查找,以及数据推送的客户端都是通过类似的技术mmap(内存映射),sendfile实现的
* java 方式的内存映射(kafka broker 是scala写的)
* fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
* 有兴趣的话,可以去扩展开研究一下
* fileChannel 写入的操作 不一定比FileInputStream 快,它的优势在于如果大数据写入,每次写入4KB和pageCache大小一致,此时速度最快
* 想知道fileChannel mmap 普通IO 读写速度,以及啥时候能够最大利用他们的优势可以看看下面2篇文章
* https://yq.aliyun.com/articles/673567
* https://blog.csdn.net/alex_xfboy/article/details/90174840
*/
FileChannel inputChannel = new FileInputStream(new File("E:\\huhu\\hudad.txt")).getChannel();
int sendCount = 0;
/*
* 发送文件
*/
while(true){
if(socketChannel.isConnected() && sendCount < 1){
++sendCount;
/**
* 这里在linux环境下采用零拷贝技术(send_file)
*/
inputChannel.transferTo(0,inputChannel.size(),socketChannel);
log.info("connect就绪");
} /*
* 设置1sec的超时时间,进行IO事件选择操作
*/
int nSelectedKeys = selector.select();
if(nSelectedKeys > 0){
for(SelectionKey skey: selector.selectedKeys()){ /*
* 判断检测到的channel是不是可连接的,将对应的channel注册到选择器上,指定关心的事件类型为OP_READ
*/
if(skey.isConnectable()){
log.info("key connect就绪");
SocketChannel connChannel = (SocketChannel) skey.channel();
connChannel.configureBlocking(false);
/**
* 这里connection成功,直接监控OP_READ,其实一种偷懒方法,应该是先监控OP_WRITE,然后在OP_WRITE处理方法中监控OP_READ
* OP_WRITE 是指channel对应的ByteBuffer 还有空间可以写入
* OP_READ 是指channel对应的ByteBuffer 还存在数据可以读
*/
connChannel.register(selector, SelectionKey.OP_READ);
/**
* 目的是为了校验是否和服务器连接成功
* SelectionKey.OP_CONNECT触发条件是链接成功或者链接失败抛出异常,所以需要通过finishConnect校验,如果失败,此方法会抛出异常
*/
connChannel.finishConnect();
}
/*
* 若检测到的IO事件是读事件,则处理相关数据的读相关的业务逻辑
*/
else if(skey.isReadable()){
log.info("key read就绪");
SocketChannel readChannel = (SocketChannel) skey.channel();
StringBuilder sb = new StringBuilder();
/*
* 定义一个ByteBuffer的容器,容量为1k
*/
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); int readBytes = 0;
int ret = 0;
/*
* 注意,对ByteBuffer的操作,需要关心的是flip,clear等。
*/
while ((ret = readChannel.read(byteBuffer)) > 0) {
readBytes += ret;
byteBuffer.flip();
sb.append(Charset.forName("UTF-8").decode(byteBuffer).toString());
byteBuffer.clear();
} if (readBytes == 0) {
System.err.println("handle opposite close Exception");
readChannel.close();
} System.out.println("服务器返回信息"+sb.toString());
}
}
/*
* 一次监听的事件处理完毕后,需要将已经记录的事件清除掉,准备下一轮的事件标记
*/
selector.selectedKeys().clear();
}else{
System.err.println("handle select timeout Exception");
}
}
}
}
package com.ppdai.user.weixin.controller.test; import lombok.extern.slf4j.Slf4j; 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.nio.charset.Charset;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set; /**
* Created by hxh on 2020/3/11.
*/
@Slf4j
public class TcpServer {
/**
* @param args
*/
public static void main(String[] args) {
try {
startServer(8080);
} catch (IOException e) {
e.printStackTrace();
}
/* Set<String> key = new HashSet<String>();
key.add("11");
key.add("22");
Set<String> publicKey = new HashSet<String>();
publicKey = key;
key.add("33"); publicKey.stream().forEach(keyName->{
System.out.println(keyName);
}); //key.clear();
publicKey.stream().forEach(keyName->{
System.out.println("+"+keyName);
});*/ } public static void startServer(int port) throws IOException {
log.info("开启一个服务channel");
/*
*开启一个服务channel,
*A selectable channel for stream-oriented listening sockets.
*/
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress(port)); /*
* 创建一个selector
*/
Selector selector = Selector.open();
/*
* 将创建的serverChannel注册到selector选择器上,指定这个channel只关心OP_ACCEPT事件
*/
serverChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) {
/*
* select()操作,默认是阻塞模式的,即,当没有accept或者read时间到来时,将一直阻塞不往下面继续执行。
*/
int readyChannels = selector.select();
if (readyChannels <= 0) {
continue;
} /*
* 从selector上获取到了IO事件,可能是accept,也有可能是read
*/
Set<SelectionKey> SelectonKeySet = selector.selectedKeys();
Iterator<SelectionKey> iterator = SelectonKeySet.iterator(); /*
* 循环遍历SelectionKeySet中的所有的SelectionKey
*/
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) { //处理OP_ACCEPT事件
log.info("OP_ACCEPT事件就绪");
SocketChannel socketChannel = serverChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) { //处理OP_READ事件
log.info("OP_READ事件就绪");
SocketChannel socketChannel = (SocketChannel) key.channel();
StringBuilder sb = new StringBuilder();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); int readBytes = 0;
int ret = 0;
/*
* 注意读数据的时候,ByteBuffer的操作,需要flip,clear进行指针位置的调整
*/
while ((ret = socketChannel.read(byteBuffer)) > 0) {
readBytes += ret;
byteBuffer.flip();
sb.append(Charset.forName("UTF-8").decode(byteBuffer).toString());
byteBuffer.clear();
} if (readBytes == 0) {
System.err.println("handle opposite close Exception");
socketChannel.close();
} String message = sb.toString();
System.out.println("Message from client: " + message);
if ("client_close".equalsIgnoreCase(message.toString().trim())) {
System.out.println("Client is going to shutdown!");
socketChannel.close();
} else if ("server_close".equalsIgnoreCase(message.trim())) {
System.out.println("Server is going to shutdown!");
socketChannel.close();
serverChannel.close();
selector.close();
System.exit(0);
} else {
String outMessage = "Server response:我已收到";
socketChannel.write(Charset.forName("UTF-8").encode(outMessage));
}
}
/*
* 将selector上当前已经监听到的且已经处理了的事件标记清除掉。
*/
iterator.remove();
}
}
}
}
上面是一个简单的利用TCP协议,发送一个文件的demo,代码基本上都有注释,就不过多解释
二,理论知识
NIO必然存在2个元素,channel,byteBuffer。他们和stream 有啥区别,或者说NIO和IO的区别。channel有很多种
IO和NIO区别
https://www.cnblogs.com/aspirant/p/8630283.html(理论参考这篇文章)
文件NIO(FileChannle 标准文件channel 不可以设置为非阻塞)
stream,底层是一个一个从磁盘读取字节,然后上层封装起来进行业务处理,而且分为inputstream和outputStream,也就说是单向的。
文件NIO channel 和ByteBuffer 的效果,就像,在目的地和磁盘直接开了一个通道(channel)然后利用卡车(ByteBuffer)一次性传送大量数据,双向,可读可写。
网络NIO
SocketChannel 是一个连接到TCP网络套接字的通道
ServerSocketChannel 可以监听新进来的TCP连接, 就像标准IO中的ServerSocket一样。
DatagramChannel 是一个能收发UDP包的通道
下面以SocketChannel为例,给大家列出来SocketChannel 的上下文,然后熟悉一下每个interface的作用,对后面的kafka-client 发送报文很有帮助
GatheringByteChannel,将多个ByteBuffer 聚合到一个channel中
ScatteringByteChannel 将从channel读初数据,按此存储到多个ByteBuffer中
AbstractSelectableChannel 这个类是用来管理channel的注册,注销和close,主要是配合selector才有效果
ByteChannel 用来实现channel读写ByteBuffer的interface
ByteBuffer 是NIO另外一个最重要的接口
https://blog.csdn.net/mrliuzhao/article/details/89453082(ByteBuffer用法小结,很详细,看完写个demo,你就入门啦)
PS:NIO没用过没关系,现在学习起来也不迟,熟悉一下主要的类,写个demo就入门啦。学无之境,一天不进步就是退步。
PS:NIO使用TCP发送报文会出现沾包和黏包具体原因以及解决方法(https://blog.csdn.net/fgx_123456/article/details/80031821)
java NIO 随笔的更多相关文章
- 源码分析netty服务器创建过程vs java nio服务器创建
1.Java NIO服务端创建 首先,我们通过一个时序图来看下如何创建一个NIO服务端并启动监听,接收多个客户端的连接,进行消息的异步读写. 示例代码(参考文献[2]): import java.io ...
- 支撑Java NIO 与 NodeJS的底层技术
支撑Java NIO 与 NodeJS的底层技术 众所周知在近几个版本的Java中增加了一些对Java NIO.NIO2的支持,与此同时NodeJS技术栈中最为人称道的优势之一就是其高性能IO,那么我 ...
- JAVA NIO学习笔记1 - 架构简介
最近项目中遇到不少NIO相关知识,之前对这块接触得较少,算是我的一个盲区,打算花点时间学习,简单做一点个人学习总结. 简介 NIO(New IO)是JDK1.4以后推出的全新IO API,相比传统IO ...
- Java NIO概述
Java NIO 由以下几个核心部分组成: Channels Buffers Selectors 虽然 Java NIO 中除此之外还有很多类和组件,但在我看来,Channel,Buffer 和 Se ...
- JAVA NIO Socket通道
DatagramChannel和SocketChannel都实现定义读写功能,ServerSocketChannel不实现,只负责监听传入的连接,并建立新的SocketChannel,本身不传输数 ...
- JAVA NIO FileChannel 内存映射文件
文件通道总是阻塞式的. 文件通道不能创建,只能通过(RandomAccessFile.FileInputStream.FileOutputStream)getChannel()获得,具有与File ...
- java nio系列文章
java nio系列教程 基于NIO的Client/Server程序实践 (推荐) java nio与并发编程相关电子书籍 (访问密码 48dd) 理解NIO nio学习记录 图解ByteBuff ...
- Java NIO (转)
Java NIO提供了与标准IO不同的IO工作方式: Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(B ...
- Java NIO使用及原理分析(1-4)(转)
转载的原文章也找不到!从以下博客中找到http://blog.csdn.net/wuxianglong/article/details/6604817 转载自:李会军•宁静致远 最近由于工作关系要做一 ...
随机推荐
- Java 8新特性解读
(四)Java 8 相关知识 关于 Java 8 中新知识点,面试官会让你说说 Java 8 你了解多少,下面分享一我收集的 Java 8 新增的知识点的内容,前排申明引用自:Java8新特性及使用 ...
- java前后端开发需掌握的框架及技术
一.Java开发 1.J2EE架构及主流框架,spring4.spring boot.spring MVC.spring Security.spring cloud.struct2.hibernate ...
- 跟我一起学Redis之Redis配置文件啃了一遍之后,从尴尬变得有底气了
前言 秋高气爽的一天,那时候年轻帅气的我正在参照着搜索引擎写代码,迷之微笑般的敲着键盘(又从搜索引擎上找到代码案例啦),突然领导在小隔间里传来了呼唤声,然后有了以下场景: 领导:小Z,你过来一下: 当 ...
- Python3网络学习案例二:traceroute详解
1. 写在前面 本文是基于上一篇"ping详解"写的: 不同操作系统下的命令也不同,本文仅针对windows系统,命令为"tracert xxx",效果如下 2 ...
- CopyOnWriteArrayList线程安全分析
CopyOnWriteArrayList是开发过程中常用的一种并发容器,多用于读多写少的并发场景.但是CopyOnWriteArrayList真的能做到完全的线程安全吗? 答案是并不能. 一.Copy ...
- 你说一下对Java中的volatile的理解吧
前言 volatile相关的知识其实自己一直都是有掌握的,能大概讲出一些知识,例如:它可以保证可见性:禁止指令重排.这两个特性张口就来,但要再往深了问,具体是如何实现这两个特性的,以及在什么场景下使用 ...
- 2018-12-5 及 codeforces round 525v2
突然发现五天没记录了,这五天学习完全没有按着正常规划进行,先罗列一下吧. 机器学习视频第一周的全部看完了. 算法导论看了几页. 参加了一次CF.rating只加了20,看来提高实力才是最关键的. C+ ...
- 转载-Eclipse无法打开Eclipse MarketPlace的解决办法
问题描述: Eclipse点击 help-->Eclipse MarketPlace 后无任何反应,无报错,打不开 解决方法: 重新安装一下 epp MarketPlace help--> ...
- CSS ------ 样式学习 (一)
CSS 指层叠样式表 (Cascading Style Sheets) :定义如何显示 HTML 元素(一套自定义的衣服) 语法: 由选择器和声明(可以是一条或多条)构成, 声明以大括号({})括起来 ...
- Spring Security 实战干货:客户端OAuth2授权请求的入口
1. 前言 在Spring Security 实战干货:OAuth2第三方授权初体验一文中我先对OAuth2.0涉及的一些常用概念进行介绍,然后直接通过一个DEMO来让大家切身感受了OAuth2.0第 ...