一,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 随笔的更多相关文章

  1. 源码分析netty服务器创建过程vs java nio服务器创建

    1.Java NIO服务端创建 首先,我们通过一个时序图来看下如何创建一个NIO服务端并启动监听,接收多个客户端的连接,进行消息的异步读写. 示例代码(参考文献[2]): import java.io ...

  2. 支撑Java NIO 与 NodeJS的底层技术

    支撑Java NIO 与 NodeJS的底层技术 众所周知在近几个版本的Java中增加了一些对Java NIO.NIO2的支持,与此同时NodeJS技术栈中最为人称道的优势之一就是其高性能IO,那么我 ...

  3. JAVA NIO学习笔记1 - 架构简介

    最近项目中遇到不少NIO相关知识,之前对这块接触得较少,算是我的一个盲区,打算花点时间学习,简单做一点个人学习总结. 简介 NIO(New IO)是JDK1.4以后推出的全新IO API,相比传统IO ...

  4. Java NIO概述

    Java NIO 由以下几个核心部分组成: Channels Buffers Selectors 虽然 Java NIO 中除此之外还有很多类和组件,但在我看来,Channel,Buffer 和 Se ...

  5. JAVA NIO Socket通道

      DatagramChannel和SocketChannel都实现定义读写功能,ServerSocketChannel不实现,只负责监听传入的连接,并建立新的SocketChannel,本身不传输数 ...

  6. JAVA NIO FileChannel 内存映射文件

      文件通道总是阻塞式的. 文件通道不能创建,只能通过(RandomAccessFile.FileInputStream.FileOutputStream)getChannel()获得,具有与File ...

  7. java nio系列文章

    java nio系列教程 基于NIO的Client/Server程序实践 (推荐) java nio与并发编程相关电子书籍   (访问密码 48dd) 理解NIO nio学习记录 图解ByteBuff ...

  8. Java NIO (转)

    Java NIO提供了与标准IO不同的IO工作方式: Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(B ...

  9. Java NIO使用及原理分析(1-4)(转)

    转载的原文章也找不到!从以下博客中找到http://blog.csdn.net/wuxianglong/article/details/6604817 转载自:李会军•宁静致远 最近由于工作关系要做一 ...

随机推荐

  1. AWS Lambda 借助 Serverless Framework,迅速起飞

    前言 微服务架构有别于传统的单体式应用方案,我们可将单体应用拆分成多个核心功能.每个功能都被称为一项服务,可以单独构建和部署,这意味着各项服务在工作时不会互相影响 这种设计理念被进一步应用,就变成了无 ...

  2. B. Nauuo and Circle 解析(思維、DP)

    Codeforce 1172 B. Nauuo and Circle 解析(思維.DP) 今天我們來看看CF1172B 題目連結 題目 略,請直接看原題 前言 第一個該觀察的事情一直想不到,看了解答也 ...

  3. CodeForces 1426F Number of Subsequences

    题意 给定一个长度为 \(n\) 的串,只包含 abc 和通配符.通配符可以替换 abc 的一个.求所有得到的字符串中子序列 abc 出现的次数,对 \(10^9+7\) 取模. \(\texttt{ ...

  4. Python的Opencv库怎么装

    原文章写于时间2019.4 当时鼓捣Opencv库弄了好长时间,前前后后弄了五天,找了好多帖子不知道删除重装了多少次,现在把我试出来正确的方法给大家分享一下. 1.Pycharm 我用的是win10系 ...

  5. spark求相同key的最大值

    需求: 求相同key的最大值  [("a", 3),  ("a", 2),  ("a", 5),  ("b", 5),  ...

  6. CSP-S 2020模拟训练题1-信友队T1 四平方和

    题意简述 \(n\)是正整数,其四个最小的因子分别为\(d_1,d_2,d_3,d_4\). 求对于所有的\(n \le m\)满足 \[d_1^2+d_2^2+d_3^2+d_4^2=n \] 的\ ...

  7. 【USACO】Strolling Cows

    Strolling Cows 给定有 \(n\) 个点 \(n\) 条边的有向图,每个点的出度都为 \(1\),求图中的最大环. 显然入度为 \(0\) 的点不可能为最大环上的点,所以考虑删点. 然后 ...

  8. 【Luogu】P1306 斐波那契公约数 题解

    原题链接 嗯...很多人应该是冲着这个标题来的 (斐波那契的魅力) 1.分析题面 点开题目,浏览一遍题目,嗯?这么简单?还是蓝题? 再看看数据范围,感受出题人深深的好意... \(n,m \leq 1 ...

  9. 使用sql导出数据_mysql

    在mysql中 使用sql 脚本导出数据的方式之一: select * from table_name where x=y  INFO OUTFILE "/tmp/table_name.tx ...

  10. git引入_版本控制介绍

    八个字形容git技术: 公司必备,一定要会 一.git概念: git是一个免费的,开源的分布式版本控制系统,可以快速高效的处理从小型到大型的项目 二.什么是版本控制: 版本控制是一种一个记录一个或若个 ...