相关博文:
https://www.cnblogs.com/YjfDIY/p/9835022.html
Linux 5种IO:
https://mp.weixin.qq.com/s?__biz=Mzg3MjA4MTExMw==&mid=2247484746&idx=1&sn=c0a7f9129d780786cabfcac0a8aa6bb7&source=41&scene=21#wechat_redirect
NIO
NIO原理对比 Socket
NIO过程总结
- 创建选择器 Selector, 创建通道 Channel
- 通道开启,并设置为非阻塞
- Server(通道绑定 Port );Client(通道绑定 IP + Port )
- 选择器和多路通道绑定
- *多路选择器 轮询监听(接收数据)
- *向通道(写数据)
(每个通道 对应一个key)==(selectionKey.channel()----SelectionKey)
NIO中的一些标志位表示
Buffer在与Channel交互时,需要一些标志:
- buffer的大小/容量 - Capacity
作为一个内存块,Buffer有一个固定的大小值,用参数capacity表示。
- 当前读/写的位置 - Position
当写数据到缓冲时,position表示当前待写入的位置,position最大可为capacity – 1;当从缓冲读取数据时,position表示从当前位置读取。
- 信息末尾的位置 - limit
在写模式下,缓冲区的limit表示你最多能往Buffer里写多少数据; 写模式下,limit等于Buffer的capacity,意味着你还能从缓冲区获取多少数据。
- 从写到读的标志位变化 : limit --> positon, position–> 0(起点位置)
级 从 写–> 读 需要 调用 Buffer.flip(); 方法
向缓冲区写数据:
-
从Channel写到Buffer;
-
通过Buffer的put方法写到Buffer中;
从缓冲区读取数据:
-
从Buffer中读取数据到Channel;
-
通过Buffer的get方法从Buffer中读取数据;
flip方法:
将Buffer从写模式切换到读模式,将position值重置为0,limit的值设置为之前position的值;
clear方法 vs compact方法:
clear方法清空缓冲区;compact方法只会清空已读取的数据,而还未读取的数据继续保存在Buffer中;
写数据
String message = "data";
byte[] str = message.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(str.length);
writeBuffer.put(str);
writeBuffer.flip();
socketChannel.write(writeBuffer); // 向通道发送数据
读数据
if (key.isReadable()) {
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = socketChannel.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String result= new String(bytes, "UTF-8");
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
socketChannel.close();
} else
; // 读到0字节,忽略
}
参考https://www.jianshu.com/p/362b365e1bcc
问题
- NIO 和 BIO 通信时发现, NIO 不能一次性读取 BIO发送的消息
服务端和客户端 通信的代码
- 可以实现 服务端NIO 和 客户端NIO 的双向通信
实现 基于 FileChannel 的 文件传输
服务端(发送)文件
if(key.isWritable()){
SocketChannel socketChannel = (SocketChannel)key.channel();
FileInputStream file = new FileInputStream("D:\\test.txt");
FileChannel fileChannel = file.getChannel();
//500M 堆外内存
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
while(fileChannel.position() < fileChannel.size()){
System.out.println("position:"+ fileChannel.position() +" size:"+ fileChannel.size());
fileChannel.read(byteBuffer);//从文件通道读取到byteBuffer
byteBuffer.flip();
while(byteBuffer.hasRemaining()){ // 读到数据了吗
socketChannel.write(byteBuffer);//写入通道
}
byteBuffer.clear();//清理byteBuffer
}
System.out.println("结束写操作");
socketChannel.close();
}
客户端(接收)文件
if(key.isReadable()){ //有可读数据事件。
SocketChannel channel = (SocketChannel)key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
File file = new File("D:\\test3.txt");
if(!file.exists()) file.createNewFile();
FileOutputStream fe =new FileOutputStream(file,true);//可追加写
FileChannel outFileChannel = fe.getChannel();
while(channel.read(byteBuffer) > 0){ //分多次读取
System.out.println(new String(byteBuffer.array(), "UTF-8"));
byteBuffer.flip(); // 读 模式 --转换为-- 写模式
outFileChannel.write(byteBuffer);//byteBuffer转换为数据写到FileChannel
fe.flush(); // 写入文件
byteBuffer.clear();
}
outFileChannel.close();
fe.close();
System.out.println("读取结束");
channel.close();
}
实现 基于 SocketChannel 的 字符串传输
服务端 NIO
package com.net.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.*;
public class NioServer implements Runnable{
public static Scanner cin = new Scanner(System.in);
public static Map<Integer, SelectionKey> ClientMap = new HashMap<>();
public static void main(String[] args) throws IOException {
int port = 8001;
Selector selector = null;
ServerSocketChannel serverSocketChannel = null;
new Thread(new NioServer()).start();
/** 通道初始化 */
try {
selector = Selector.open(); // 产生多路选择器
serverSocketChannel = ServerSocketChannel.open(); // 开启 socket通道(Channel)
serverSocketChannel.configureBlocking(false); // 开启非阻塞模式
serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024); // 驻守在 port端口
System.out.println("accept 接受");
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 绑定多路选择器+通道
System.out.printf("服务器在%d端口守候\n", port);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while(true) {
try {
selector.select(2000); // 开始轮询 通道,每隔2000ms轮询一次
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
/** 开始处理通道 */
handleInput(selector, key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null)
key.channel().close();
}
}
}
} catch(Exception ex) {
ex.printStackTrace();
}
try {
Thread.sleep(500);
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
public static void handleInput(Selector selector, SelectionKey key) throws IOException {
if (key.isValid()) {
// 处理新接入的请求消息
if (key.isAcceptable()) { // 连接刚刚建立
// Accept the new connection
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
// Add the new connection to the selector
System.out.println("reda 读取");
sc.register(selector, SelectionKey.OP_READ);
System.out.println("有一个新的连接");
}
if (key.isReadable()) { // 可以读数据
// Read the data
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String request = new String(bytes, "UTF-8"); //接收到的输入
String[] requests = request.split(":");
int id = Integer.parseInt(requests[0]);
System.out.printf("client (id = %d)said: %s\n" , id, requests[1]);
/** 记录客户 */
if(ClientMap.containsValue(key) == false) {
ClientMap.put(id, key);
System.out.printf("客户 %d 初次注册", id);
}else {
System.out.printf("客户 %d 已经注册", id);
}
String response = requests[0] + ":I know";
doWrite(sc, response);
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
} else
; // 读到0字节,忽略
}
}
}
public static void doWrite(SocketChannel socketChannel, String response) throws IOException {
if (response != null && response.trim().length() > 0) {
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
socketChannel.write(writeBuffer);
}
}
@Override
public void run() {
while (true) {
System.out.print("输入客户端id:");
int a = Integer.parseInt(cin.nextLine());
if (ClientMap.containsKey(a) == true) {
System.out.println("发送消息给:" + a);
try {
String temp = cin.nextLine();
SocketChannel sc = (SocketChannel) ClientMap.get(a).channel();
doWrite(sc, temp);
} catch (IOException e) {
e.printStackTrace();
}
}else{
System.out.println("没有此用户");
}
}
}
}
客户端 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.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
import java.util.UUID;
public class NioClient implements Runnable{
public static Scanner cin = new Scanner(System.in);
public static int ID = 1;
public static SelectionKey key = null;
public static Selector selector = null;
public static void main(String[] args) {
//String host = "192.168.179.182";
String host = "127.0.0.1";
int port = 8001;
System.out.print("输入客户端编号:" );
ID = Integer.parseInt(cin.nextLine());
new Thread(new NioClient()).start();
System.out.println(ID);
selector = null;
SocketChannel socketChannel = null;
try {
selector = Selector.open(); // 开选择器
socketChannel = SocketChannel.open(); // 开通道
socketChannel.configureBlocking(false); // 设置非阻塞
// 如果直接连接成功,则注册到多路复用器上,发送请求消息,读应答
if (socketChannel.connect(new InetSocketAddress(host, port))) {
System.out.println("服务器连接成功");
System.out.println("reda 读取");
System.out.println(socketChannel.register(selector, SelectionKey.OP_READ));
/** 开始写数据 */
doWrite(socketChannel);
} else {
socketChannel.register(selector, SelectionKey.OP_CONNECT); // 选择器和通道 注册绑定
System.out.println("选择器注册成功, connect 连接");
}
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (true) {
try {
selector.select(1000); // 遍历选择器(中的通道)
Set<SelectionKey> selectedKeys = selector.selectedKeys(); //所有连接上的选择器 key
Iterator<SelectionKey> it = selectedKeys.iterator();
key = null;
while (it.hasNext()) { // 顺序处理 选择器中 有反应的通道
key = it.next();
it.remove();
try {
// 处理每一个channel
handleInput(selector, key);
}
catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null)
key.channel().close();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 多路复用器关闭后,所有注册在上面的Channel资源都会被自动去注册并关闭
// if (selector != null)
// try {
// selector.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
//
// }
}
/**
* 写数据
* @param sc
* @throws IOException
*/
public static void doWrite(SocketChannel sc) throws IOException {
String temp = cin.nextLine();
temp = Integer.toString(ID) + ":" + temp;
byte[] str = temp.getBytes();
// byte[] str = UUID.randomUUID().toString().getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(str.length);
writeBuffer.put(str);
writeBuffer.flip();
sc.write(writeBuffer); // 向通道发送数据
}
/**
* 读数据:处理通道
* @param selector
* @param key
* @throws Exception
*/
public static void handleInput(Selector selector, SelectionKey key) throws Exception {
/** 判断是否连接成功*/
if (key.isValid()) {
// 获取key 相对应 的 通道Channel
SocketChannel sc = (SocketChannel) key.channel();
if (key.isConnectable()) { // 判断 key 和 selector 是否已经注册
if (sc.finishConnect()) {
sc.register(selector, SelectionKey.OP_READ);
}
}
// 可以读数据
if (key.isReadable()) {
System.out.println("可以读数据");
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("Server said : " + body);
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
} else
; // 读到0字节,忽略
}
//Thread.sleep(3000);
/** 发送数据 */
doWrite(sc);
}
}
@Override
public void run() {
while (true) {
try {
SocketChannel sc = (SocketChannel) key.channel();
if (key.isConnectable()) { // 判断 key 和 selector 是否已经注册
if (sc.finishConnect()) {
sc.register(selector, SelectionKey.OP_READ);
}
// System.out.printf("已经注册");
}
if (key.isReadable()) {
System.out.print("准备接收消息");
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("Server said : " + body);
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
} else
; // 读到0字节,忽略
}
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO: handle exception
}
}
}
}