传统同步阻塞I/O(BIO)

在NIO之前编写服务器使用的是同步阻塞I/O(Blocking I/O)。下面是一个典型的线程池客服端服务器示例代码,这段代码在连接数急剧上升的情况下,这个服务器代码就会不好使了,因为serverSocket.accept(),以及IO的read(),write()方法都是同步阻塞的,虽然通过线程池,避免频繁创建线程开销,但是该系统过于依赖线程,一个是线程的创建和销毁很耗时,再者线程的切换开销很大,尤其是在高并发的情况下系统压力不堪设想。

BIO线程池客服端服务器示例代码

/**
* BIO服务器
* @author monkjavaer
* @date 2019/7/17 13:55
*/
public class BioServer {
public static final int PORT = 8888;
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(PORT);
Socket socket = null;
ThreadFactory namedThreadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("demo-pool-%d");
return t;
}
};
//通过线程池,避免频繁创建线程开销
ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); //主线程死循环等待新连接到来
while(!Thread.currentThread().isInterrupted()){
socket = serverSocket.accept();
singleThreadPool.execute(new BioServerHandler(socket));
}
} catch (IOException e) {
//TODO异常处理
} finally {
//TODO关闭资源
}
}
}
/**
* BIO服务器事件处理方法
* @author monkjavaer
* @date 2019/7/17 14:00
*/
public class BioServerHandler implements Runnable {
private Socket socket;
public BioServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
byte[] input = new byte[1024];
//服务器接收的数据
socket.getInputStream().read(input);
byte[] output = "服务器返回数据".getBytes();
socket.getOutputStream().write(output);
} catch (IOException e) {
//TODO异常处理
} finally {
//TODO关闭资源
}
}
}
/**
* 服务端
* @author monkjavaer
* @date 2019/7/17 15:06
*/
public class BioClient {
public static final int PORT = 8888;
public static final String IP = "127.0.0.1";
public static void main(String[] args) {
Socket socket = null;
PrintWriter printWriter = null;
try {
socket = new Socket(IP,PORT);
socket.setSoTimeout(5000);
printWriter = new PrintWriter(socket.getOutputStream());
printWriter.println("客户端发送数据");
printWriter.flush();
} catch (IOException e) {
//TODO异常处理
} finally {
//TODO关闭资源
}
}
}

NIO(非阻塞I/O)

NIO就是非阻塞I/O(Non-blocking I/O)

NIO重要组件回顾

  • 缓冲区(Buffer):一个Buffer对象是固定数量的数据的容器。其作用是一个存储器,或者分段运输区,在这里数据可被存储并在之后用于检索。ByteBuffer、IntBuffer、CharBuffer、LongBuffer、DoubleBuffer、FloatBuffer、ShortBuffer都是其实现类。
  • 通道(Channel):Channel 用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据。 Channel是全双工的。
  • 选择器(Selector):Selector是NIO的多路复用器。Selector会不断轮询注册在它上面的通道Channel,找出就绪状态的Channel(Channel通道发生读、写事件)。Selector是基于底层操作系统机制,不同模式、不同版本都存在区别。Linux 上依赖于epoll;所以没有最大句柄的限制,因此一个线程做Selector轮询就能接入大量的客户端连接。

NIO服务器示例代码

NIO实现服务器代码步骤非常多,比较繁杂,所以推荐使用成熟的NIO框架Netty等。

public class NioServer implements Runnable {
private static Logger LOGGER = LoggerFactory.getLogger(NioServer.class);
@Override
public void run() {
try {
//1、打开ServerSocketChannel,监听客户端的链接
ServerSocketChannel serverSocket = ServerSocketChannel.open();
//2、绑定监听端口,设置backlog(默认50):请求传入连接队列的最大长度
serverSocket.socket().bind(new InetSocketAddress(9011), 1024);
//3、false,设置为非阻塞模式
serverSocket.configureBlocking(false);
//4、创建Selector,Selector是NIO的多路复用器,Selector会不断轮询注册在它上面的通道Channel,
//找出就绪状态的Channel(Channel通道发生读、写事件)。
Selector selector = Selector.open();
//5、注册通道Channel到多路复用器Selector,并说明关注点SelectionKey.OP_ACCEPT,监听ACCEPT事件
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
LOGGER.info("Listening on port {}" , 9011); //6、Selector轮询就绪的Channel
while (true) {
// 阻塞等待就绪的 Channel,这是关键点之一
//selector1秒被唤醒
int n = selector.select(1000);
if (n == 0) {
continue;
}
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectedKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
if (key.isValid()) { if (key.isAcceptable()) {
//SelectionKey可以获取就绪状态的Channel
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
//7、多路复用器Selector监听到有新的客户端连接,完成TCP三次握手建立连接。
SocketChannel clientSocketChannel = serverSocketChannel.accept();
//8、设置客户端SocketChannel为非阻塞模式
clientSocketChannel.configureBlocking(false);
//9、注册加入新的通道OP_READ
clientSocketChannel.register(selector, SelectionKey.OP_READ);
} //读取客户端数据
//if(key.isReadable())等价于if((key.readyOps( ) & SelectionKey.OP_READ) != 0)
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
//创建buffer
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readPosition = socketChannel.read(readBuffer);
if (readPosition > 0) {
//flip()方法,Buffer从写模式切换到读模式,将limit设置为position,position设为0。
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
//从可读buffer中读取数据
readBuffer.get(bytes);
LOGGER.info("接收客户端发送消息:{}" , new String(bytes, StandardCharsets.UTF_8)); byte[] sendBytes = "server 收到".getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
writeBuffer.flip();
//put 向buffer添加元素
writeBuffer.put(sendBytes);
socketChannel.write(writeBuffer);
} if (readPosition < 0) {
// Close channel on EOF, invalidates the key
key.cancel();
socketChannel.close();
}
}
}
iter.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
new Thread(new NioServer()).start();
}
}
public class NioClient {
private static Logger LOGGER = LoggerFactory.getLogger(NioClient.class);
private static int PORT = 9011;
private static String[] messages = {"这是服务器"}; public static void main(String[] args) {
try {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(InetAddress.getLocalHost(), PORT));
for (String msg : messages) {
ByteBuffer myBuffer = ByteBuffer.allocate(1024);
myBuffer.put(msg.getBytes());
myBuffer.flip();
socketChannel.write(myBuffer);
}
LOGGER.info("Closing Client connection...");
socketChannel.close();
} catch (IOException e) {
LOGGER.error(e.getMessage());
}
} }

Reactor模式

Reactor模式首先是事件驱动的,有一个或多个并发输入源,有一个Service Handler,有多个Request Handlers;这个Service Handler会同步的将输入的请求(Event)多路复用的分发给相应的Request Handler。是一种为处理并发服务请求,并将请求提交到一个或者多个服务处理程序的事件设计模式。

Reactor模式模块组成

http://www.blogjava.net/DLevin/archive/2015/09/02/427045.html

Scalable IO in Java原文和翻译

http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf

https://www.cnblogs.com/luxiaoxun/archive/2015/03/11/4331110.html

Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events

http://www.dre.vanderbilt.edu/~schmidt/PDF/reactor-siemens.pdf

JAVA BIO,NIO,Reactor模式总结的更多相关文章

  1. tomcat bio nio apr 模式性能测试

    转自:tomcat bio nio apr 模式性能测试与个人看法 11.11活动当天,服务器负载过大,导致部分页面出现了不可访问的状态.那后来主管就要求调优了,下面是tomcat bio.nio.a ...

  2. Java BIO NIO 与 AIO

    回顾 上一章我们介绍了操作系统层面的 IO 模型. 阻塞 IO 模型. 非阻塞 IO 模型. IO 复用模型. 信号驱动 IO 模型(用的不多,知道个概念就行). 异步 IO 模型. 并且介绍了 IO ...

  3. Java NIO Reactor模式

    一.NIO介绍: NIO模型: 1.Channel为连接通道,相当于一个客户端与服务器的一个连接,Selector为通道管理器,将Channel注册到Selector上,Selector管理着这些Ch ...

  4. JAVA bio nio aio

    [转自]http://qindongliang.iteye.com/blog/2018539 在高性能的IO体系设计中,有几个名词概念常常会使我们感到迷惑不解.具体如下: 序号 问题 1 什么是同步? ...

  5. java BIO/NIO/AIO 学习

    一.了解Unix网络编程5种I/O模型 1.1.阻塞式I/O模型 阻塞I/O(blocking I/O)模型,进程调用recvfrom,其系统调用直到数据报到达且被拷贝到应用进程的缓冲区中或者发生错误 ...

  6. 3. 彤哥说netty系列之Java BIO NIO AIO进化史

    你好,我是彤哥,本篇是netty系列的第三篇. 欢迎来我的公从号彤哥读源码系统地学习源码&架构的知识. 简介 上一章我们介绍了IO的五种模型,实际上Java只支持其中的三种,即BIO/NIO/ ...

  7. Java IO的Reactor模式

    1.    Reactor出现的原因 Reator模式是大多数IO相关组件如Netty.Redis在使用时的IO模式,为什么需要这种模式,如何设计来解决高性能并发的呢? 最最原始的网络编程思路就是服务 ...

  8. java BIO/NIO

    一.BIO Blocking IO(即阻塞IO); 1.      特点: a)   Socket服务端在监听过程中每次accept到一个客户端的Socket连接,就要处理这个请求,而此时其他连接过来 ...

  9. java BIO NIO IO

    参考 https://www.cnblogs.com/zedosu/p/6666984.html 摘要: 关于BIO和NIO的理解 最近大概看了ZooKeeper和Mina的源码发现都是用Java N ...

随机推荐

  1. Win8 Metro(C#)数字图像处理--2.61哈哈镜效果

    原文:Win8 Metro(C#)数字图像处理--2.61哈哈镜效果  [函数名称] 哈哈镜效果函数  WriteableBitmap DistortingMirrorProcess(Writea ...

  2. Android零基础入门第57节:日期选择器DatePicker和时间选择器TimePicker

    原文:Android零基础入门第57节:日期选择器DatePicker和时间选择器TimePicker 在实际开发中,经常会遇见一些时间选择器.日期选择器.数字选择器等需求,那么从本期开始来学习And ...

  3. Database Comparer VCL 6.4.908.0 D5-XE10.1

    Database Comparer VCL compares and synchronizes databases structure (metadata) and table data for ma ...

  4. Android零基础入门第86节:探究Fragment生命周期

    一个Activity可以同时组合多个Fragment,一个Fragment也可被多个Activity 复用.Fragment可以响应自己的输入事件,并拥有自己的生命周期,但它们的生命周期直接被其所属的 ...

  5. LINQ学习笔记(三)

    下面对各子句解释 from子句:查询表达式的开始子句,查询表达式必须以from子句开头. 格式:from u in source 其中u表示范围变量,它表示源序列中的每个后续元素,source为数据源 ...

  6. UBUNTU 16.04 + CUDA8.0 + CUDNN6.0 + OPENCV3.2 + MKL +CAFFE + tensorflow

    首先说一下自己机子的配置 CPU:Intel(R) Core(TM) i5-5600 CUP @3.20GHz *4 GPU : GTX 1060 OS : 64bit Ubuntu16.04LTS ...

  7. 转载 《我用 TypeScript 语言的七个月》

    快速使用Romanysoft LAB的技术实现 HTML 开发Mac OS App,并销售到苹果应用商店中.   <HTML开发Mac OS App 视频教程> 土豆网同步更新:http: ...

  8. xe5 firemonkey关闭应用程序

    在FMX中,由Activity替代了Form的概念,虽然TForm类仍然存在,但MainForm通过关闭函数无法结束程序,使用Application.Terminate均无效,调整为: uses   ...

  9. Java 函数传入参数后,究竟发生了什么?java函数传参数原理解析

    JAVA函数在传入参数A时,会在函数作用周期内生成一个与参数相同类型的局部变量B. B与A指向同一块内存区域,并且具有相同的名字如param. 在函数内所有对param的操作都是对B的操作.对B进行赋 ...

  10. Kafka笔记7

    Kafka提供了一些命令行工具,用于管理集群变更.这些工具使用Java实现,Kafka提供了一些脚本调用这些Java类. 9.1主题操作 使用Kafka-topics.sh工具可以执行主题大部分工作, ...