本案例来源于《netty权威指南》

一、三大组件

  • Selector:多路复用器。轮询注册在其上的Channel,当发现某个或者多个Channel处于“就绪状态”后(accept接收连接事件、connect连接完成事件、read读事件、write写事件),从阻塞状态返回就绪的Channel的SelectionKey集合,之后进行IO操作。
  • Channel:封装了socket。
    • ServerSocketChannel:封装了ServerSocket,用于accept客户端连接请求;
    • SocketChannel:一对SocketChannel组成一条虚电路,进行读写通信
  • Buffer:用于存取数据,最主要的是ByteBuffer
    • position:下一个将被操作的字节位置
    • limit:在写模式下表示可以进行写的字节数,在读模式下表示可以进行读的字节数
    • capacity:Buffer的大小

二、服务端代码

1、服务端启动类

 public class Server {
public static void main(String[] args) throws IOException {
new Thread(new ServerHandler(), "server-1").start();
}
}

创建一个任务ServerHandler,然后创建一条线程,启动执行该任务。

2、逻辑处理类

 public class ServerHandler implements Runnable {
private Selector selector;
private ServerSocketChannel ssChannel; public ServerHandler(int port) {
try {
//等价于 Selector selector = SelectorProvider.provider().openSelector();
selector = Selector.open();
//等价于 SelectorProvider.provider().openServerSocketChannel()
ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.bind(new InetSocketAddress(port), 1024);
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
} public void run() {
for (; ; ) {
try {
selector.select();
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
handleInput(key);
}
} catch (Throwable t) {
t.printStackTrace();
}
} } private void handleInput(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
sc.register(selector, SelectionKey.OP_READ);
}
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 body = new String(bytes, "UTF-8");
System.out.println("The time server receive order : "
+ body);
String currentTime = "QUERY TIME ORDER"
.equalsIgnoreCase(body) ? new java.util.Date(
System.currentTimeMillis()).toString()
: "BAD ORDER";
doWrite(sc, currentTime);
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
} else
; // 读到0字节,忽略
}
}
} private void doWrite(SocketChannel channel, 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();
channel.write(writeBuffer);
}
}
}

步骤:

1、创建一个Selector和ServerSocketChannel实例

2、配置ServerSocketChannel实例为非阻塞

3、ServerSocketChannel实例bind端口

4、将ServerSocketChannel实例注册到selector上,监听OP_ACCEPT事件

下面的任务在Server创建的新的线程中执行,不影响主线程执行其他逻辑

5、之后进入死循环

5.1、使用select.select()阻塞等待就绪事件(这里是等待OP_ACCEPT事件),一旦有有就绪事件到达,立即向下执行

5.2、使用selector.selectedKeys()获取已经就绪的SelectionKey(即OP_ACCEPT/OP_CONECT/OP_READ/OP_WRITE)集合,之后循环遍历

5.3、从迭代器删除该SelectionKey,防止下一次再被遍历到

5.4、如果SelectionKey==OP_ACCEPT,则通过ServerSocketChannel.accept()创建SocketChannel,该SocketChannel是后续真正的与客户端的SocketChannel进行通信的实体

5.5、配置新创建的SocketChannel实例为非阻塞,然后将该SocketChannel实例注册到selector实例上,监听OP_READ事件

5.6、等客户端发出请求数据时,此处监听到SelectionKey==OP_READ,则创建ByteBuffer实例,将SocketChannel中的数据读取到ByteBuffer中,然后再创建ByteBuffer将信息写回到SocketChannel(也就是说数据的读写一定要通过Buffer)

三、客户端代码

1、客户端启动类

 public class Client {
public static void main(String[] args) {
new Thread(new ClientHandler("127.0.0.1", 8080), "client-1").start();
}
}

创建一个任务ClientHandler,然后创建一条线程,启动执行该任务。

2、逻辑处理类

 public class ClientHandler implements Runnable {
private String host;
private int port; private Selector selector;
private SocketChannel socketChannel; public ClientHandler(String host, int port) {
this.host = host;
this.port = port;
try {
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
} public void run() {
try {
doConnect();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (true) {
try {
selector.select();
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
handleInput(key);
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
} private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
// 判断是否连接成功
SocketChannel sc = (SocketChannel) key.channel();
if (key.isConnectable()) {
if (sc.finishConnect()) {
sc.register(selector, SelectionKey.OP_READ);
doWrite(sc);
} else
System.exit(1);// 连接失败,进程退出
} else if (key.isReadable()) {
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("Now is : " + body);
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
} else
; // 读到0字节,忽略
}
} } private void doConnect() throws IOException {
// 如果直接连接成功,则注册到多路复用器上,发送请求消息,读应答
if (socketChannel.connect(new InetSocketAddress(host, port))) {
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel);
} else
socketChannel.register(selector, SelectionKey.OP_CONNECT);
} private void doWrite(SocketChannel sc) throws IOException {
byte[] req = "QUERY TIME ORDER".getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
writeBuffer.put(req);
writeBuffer.flip();
sc.write(writeBuffer);
if (!writeBuffer.hasRemaining())
System.out.println("Send order 2 server succeed.");
}
}

步骤:

1、创建一个Selector和SocketChannel实例

2、配置SocketChannel实例为非阻塞

下面的任务在Cilent创建的新的线程中执行,不影响主线程执行其他逻辑

3、SocketChannel.connect连接到server端,如果连接没有马上成功,将该SocketChannel实例注册到selector上,监听OP_CONNECT事件;如果连接成功,将该SocketChannel实例注册到selector上,监听OP_READ事件,之后写数据给server端

4、之后进入死循环

4.1、使用select.select()阻塞等待就绪事件,一旦有有就绪事件到达,立即向下执行

4.2、使用selector.selectedKeys()获取已经就绪的SelectionKey(即OP_ACCEPT/OP_CONECT/OP_READ/OP_WRITE)集合,之后循环遍历

4.3、从迭代器删除该SelectionKey,防止下一次再被遍历到

4.4、如果SelectionKey==OP_CONNECT,将该SocketChannel实例注册到selector上,监听OP_READ事件,之后写数据给server端;如果监听到SelectionKey==OP_READ,则创建ByteBuffer实例,将SocketChannel中的数据读取到ByteBuffer中

第一章 java nio三大组件与使用姿势的更多相关文章

  1. Java NIO 三大组件之 Channel

    Java NIO 之 Channel 一.什么是Channel Channel用于源节点(例如磁盘)与目的节点的连接,它可以进行读取,写入,映射和读/写文件等操作. 在Java NIO中负责缓冲区中数 ...

  2. Java NIO 三大组件之 Buffer

    NIO大三组件 之Buffer 一.什么是Buffer Buffer是用于特定原始类型的数据的容器. 它的实质就是一组数组,用于存储不同类型的数据. 二.缓冲区的类型 缓冲区类型除了Boolean值类 ...

  3. java web(五):java web三大组件之另外两个和八大监听器

    java的三大组件指Servlet.Filter.Listener.八大监听器指八个接口.前面介绍了Servlet,现在介绍一下Filter拦截器以及拦截地址的设置, Listener监听那些事件. ...

  4. 第一章 Java的I/O演进之路

    I/O基础入门 Java的I/O演进 第一章 Java的I/O演进之路 1.1 I/O基础入门 1.1.1 Linux网络I/O模型简介 根据UNIX网络编程对I/O模型的分类,UNIX提供了5中I/ ...

  5. Java基础知识二次学习-- 第一章 java基础

    基础知识有时候感觉时间长似乎有点生疏,正好这几天有时间有机会,就决定重新做一轮二次学习,挑重避轻 回过头来重新整理基础知识,能收获到之前不少遗漏的,所以这一次就称作查漏补缺吧!废话不多说,开始! 第一 ...

  6. javaSE习题 第一章 JAVA语言概述

    转眼就开学了,正式在学校学习SE部分,由于暑假放视频过了一遍,略感觉轻松,今天开始,博客将会记录我的课本习题,主要以文字和代码的形式展现,一是把SE基础加强一下,二是课本中有很多知识是视频中没有的,做 ...

  7. 第一章 –– Java基础语法

    第一章 –– Java基础语法 span::selection, .CodeMirror-line > span > span::selection { background: #d7d4 ...

  8. 第一章 java基本多线程技能

    第一章 java多线程技能 1 线程:进程是操作系统结构的基础,是一次程序的执行,是一个程序及其数据在处理顺序时发生的活动:是程序在一个数据集合上运行的过程,他是系统进行资源分配和调度的一个独立单位. ...

  9. java web 三大组件

    JavaWeb三大组件 Servlet,Filter,Listener. Servlet Servlet的作用 在Java web b/s架构中,servlet扮演了重要的角色,作为一个中转处理的容器 ...

随机推荐

  1. PHPcurl的post/get请求

    post/get请求 function getCurl($url,$data=null,$method='post',$https=true){ //1. 初始化 $ch = curl_init(); ...

  2. 042 spring boot在启动之后,自动关闭

    在学校数据源的时候,还没有开始使用数据源,项目就关闭了. 为了学习数据源,就只能使用测试类. 但是,最近需要使用restful进行测试的时候,项目是关闭的,就很糟糕,不好进行测试. 1.日志如下: D ...

  3. 049 DSL语句

    1.说明 2.sql程序 package com.scala.it import org.apache.spark.sql.hive.HiveContext import org.apache.spa ...

  4. Sublime text 3搭建Python开发环境及常用插件安装

    参考  https://www.cnblogs.com/honkly/p/6599642.html 一.环境准备 1.官方网站地址 2.Windows 10 3.Sublime Text 3 + 官网 ...

  5. POJ 3189 Steady Cow Assignment 【二分】+【多重匹配】

    <题目链接> 题目大意: 有n头牛,m个牛棚,每个牛棚都有一定的容量(就是最多能装多少只牛),然后每只牛对每个牛棚的喜好度不同(就是所有牛圈在每个牛心中都有一个排名),然后要求所有的牛都进 ...

  6. Mybatis之占位符与拼接符

    1.占位符 1.1  含义: 在持久化框架中,为了将约束条件中的可变参数从sql中分离出来,在原有的参数位置使用特殊的标记来标记该位置,后期通过代码给sql传递参数(即实现sql与代码分离开).这个特 ...

  7. pygm2安装问题

    pygm2是python的一个库,它提供了大部分数学处理的方式,今天在查看自己环境后,发现这个环境还没有安装上,于是,自己动手丰衣足食吧,我的系统为win10家庭版,首先执行的pip install ...

  8. JAVA中handleEvent和action的区别

    看代码中用到了handleEvent和action,都是对事件进行处理的,觉得这两个方法可以直接合并,于是尝试合并后,发现功能还是有问题,说明两者还是有区别了,查了很久的资料,才基本了解这两者的区别. ...

  9. 说说我安装pyspider遇到的那些坑

    现在python3.7  >>pip install pyspider    配置环境变量 前置的phantomjs 无界面浏览器,设置就不说了 cmd 中运行pyspider all  ...

  10. UVA 1592 DataBase

    思路: 知识补充: ①make_pair和pair: /*pair是将2个数据组合成一个数据,当需要这样的需求时就可以使用pair,如stl中的map就是将key和value放在一起来保存.另一个应用 ...