相关系列文章
1. 高并发编程系列之IO模型(一)-------- BIO教程
2. 高并发编程系列之IO模型(二)-------- NIO教程
NIO和NIO的区别
高并发编程系列之IO模型(一)--------BIO教程
BIO | NIO |
---|---|
面向的流的操作 | 面向缓冲区 |
阻塞IO | 非阻塞IO(能立即返回) |
无 | 选择器 |
NIO的三个重要核心
1、Channel(通道)
Channel通道,可以使用它实现数据的读取和写入,通道是双向的,可以双向同时读写(流是单项的,BIO中要输入流、输出流),这充分的利用了操作系统的全双工的支持,从而实现更高效率的传输数据。
Channel的实现
类名 | 作用 |
---|---|
FileChannel | 文件读取通道(阻塞模式的通道) |
DatagramChannel | UDP网络通道 |
SocketChannel | TCP网络通道 |
ServerSocketChannel | 监听TCP连接(服务端) |
2、Buffer (缓冲区)
缓冲区实际上是一个数组,数据的读取和写入都要经过它,它的内存读写是一块一块的采用映射的方式,速度非常快
CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer等实现
3、Selector(选择器)
一个Selector可以实现监听上千个Channel,Selector只能管理非阻塞的通道,通过轮训监听的关键字(SelectorKey)实现对通道的状态的监听
NIO的实现原理
服务端只有开启一个专门的IO线程,负责事件的分发
NIO中通过套接字通道SocketChannel和ServerSocketChannel,完成同步阻塞、同步非阻塞的连接
Selector监听的事件
事件名 | 事件值 |
---|---|
服务端接受客户端事件 | SelectionKey.OP_ACCEPT |
客户端的连接事件 | SelectionKey.OP_CONNECT |
读事件 | SelectionKey.OP_READ |
写事件 | SelectionKey.OP_WRITE |
项目代码
服务端代码:
客户端端代码:
public class NIOClient {
public static void main(String[] args) {
new Thread(new NIOClientHandler(null , 8080)).start();
}
}
客户端处理器
/**
* @author : GONG ZHI QIANG
* @data : 2019-09-06 , 15:07
* @user : SnaChat
* @project: Netty
* @description :
*/
public class NIOClientHandler implements Runnable {
private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private volatile boolean stop;
public NIOClientHandler(String host, int port) {
this.host = host == null ? "127.0.0.1" : host;
this.port = port;
try {
// 创建选择器
selector = Selector.open();
// SocketChannel 绑定客户端的本地地址
socketChannel = SocketChannel.open();
// 客户端设置为非阻塞模式
socketChannel.configureBlocking(false);
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
try {
doConnect();
} catch (IOException e) {
e.printStackTrace();
}
while (!stop) {
try {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
SelectionKey key = null;
while (iterator.hasNext()) {
key = iterator.next();
iterator.remove();
try {
// 将将要通信的key
handleInput(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 真正处理网络请求
* @param key
* @throws IOException
*/
private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
System.out.println("客户端请求建立的连接成功了吗? " + socketChannel.finishConnect());
//判断是否连接成功
if (key.isConnectable()) {
if (socketChannel.finishConnect()) {
System.out.println("客户端连接成功");
//连接成功,将key注册到 Selector上 ,SelectionKey.OP_READ:监听网络中的读操作
socketChannel.register(selector, SelectionKey.OP_READ);
// 将信息发送给 服务端
doWrite(socketChannel);
}
}
//当期的key 是可读的 ,那么读取服务端的消息(异步的读取)
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 readResult = new String(bytes, "UTF-8");
System.out.println("服务端返回的信息:" + readResult);
this.stop = true;
} else { //读取不到数据
key.cancel();
socketChannel.close();
}
}
}
}
/**
* 向服务端 发送消息
* @param socketChannel
* @throws IOException
*/
private void doWrite(SocketChannel socketChannel) throws IOException {
/*** 将String类型的字符串 转化 成 byte数组 */
byte[] clientMessage = ("你好!我是客户端 , Time = " + new Date(System.currentTimeMillis()).toString()).getBytes() ;
//申请缓冲区
ByteBuffer buffer = ByteBuffer.allocate(clientMessage.length);
//将数组的数据写入缓冲区中
buffer.put(clientMessage);
//调整指针的位置
buffer.flip();
//将数据映射到 Channel中
socketChannel.write(buffer);
if (!buffer.hasRemaining()) {
System.out.println("全部信息发送成功");
}
}
private void doConnect() throws IOException {
//根据IP和端口号做连接,并将socketChannel绑定到 selector上
if (socketChannel.connect(new InetSocketAddress(host, port))) {
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel);
} else {
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
}
}
服务端代码:
/**
* @author : GONG ZHI QIANG
* @data : 2019-09-06 , 14:31
* @user : SnaChat
* @project: Netty
* @description :
*/
public class NIOServer {
public static void main(String[] args) throws IOException {
final int PORT = 8080;
NIOServerHandler server = new NIOServerHandler(PORT);
new Thread(server , "NIO-Server--0001").start();
}
}
服务端处理器
public class NIOServerHandler implements Runnable {
/**
* 选择器,
*/
private Selector selector;
/**
* 通道对象ServerSocketChannel
*/
private ServerSocketChannel serverSocketChannel;
/**
* volatile: 这里使用 volatile,保证多线程中的线程的可见性,用于后面线程的停止
*/
private volatile boolean stopFlag;
public NIOServerHandler(int port) throws IOException {
// 创建选择器
selector = Selector.open();
// 监听客户端的连接
serverSocketChannel = ServerSocketChannel.open();
// 设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 绑定监听端口 , 并且将backlog的值设置为1024
serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);
// 将Channel注册到 Selectord多路复用器上 , 并且监听SelectionKey.OP_ACCEPT操作
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务端已经启动:" + port);
}
public void stop() {
this.stopFlag = true;
}
public void run() {
/**
* 多线程中 实现轮训 找到准备就绪的 Key
*/
System.out.println("服务端监测客户端的连接");
while (!stopFlag) {
try {
//设定休眠的时间
selector.select(1000);
// 筛选出准备就绪的 Channel的Key,然后可以进行一步的读写操作
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
SelectionKey key = null;
while (iterator.hasNext()) {
key = iterator.next();
iterator.remove();
try {
handlerInput(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 这个方法中处理客户端的具体的请求
* @param key
* @throws IOException
*/
private void handlerInput(SelectionKey key) throws IOException {
if (key.isValid()) {
// 根据key的类型判断网络处理事件的类型
if (key.isAcceptable()) {
//处理新接入的请求消息
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
// 此处处理新的连接(来自客户端)
SocketChannel socketChannel = serverSocketChannel.accept();
//设置是阻塞模式还是非阻塞模式
socketChannel.configureBlocking(false);
//将新的连接 添加到 selector
socketChannel.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
//读取数据
int redBytes = socketChannel.read(readBuffer);
if (redBytes > 0) {
//调整缓冲区的指针
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
//将读取的字符数组 封装成字符串
String message = new String(bytes, "UTF-8");
System.out.println("服务端接受到的请求信息 : " + message);
String returnMessage = "NIO--服务端已经收到你的请求 , Time = " + new Date(System.currentTimeMillis()).toString();
/*** 这里再向客户端返回一条数据*/
doReturn(socketChannel, returnMessage + " , 这是服务端返回数据");
}
}
}
}
/**
* 返回给客户端的信息
*
* @param socketChannel
* @param responseMessage
* @throws IOException
*/
private void doReturn(SocketChannel socketChannel, String responseMessage) throws IOException {
if (responseMessage != null && responseMessage.trim().length() > 0) {
//将String转换成为 byte[]
byte[] bytes = responseMessage.getBytes();
ByteBuffer writerBuffer = ByteBuffer.allocate(bytes.length);
writerBuffer.put(bytes);
writerBuffer.flip();
//将数据写入 Channel中
socketChannel.write(writerBuffer);
}
}
}