Tomcat架构解析(六)-----BIO、NIO、NIO2、APR
对于应用服务器来说,性能是非常重要的,基本可以说决定着这款应用服务器的未来。通常从软件角度来说,应用服务器性能包括如下几个方面:
1、请求处理的并发程度,当前主流服务器均采用异步的方式处理客户端的请求;
2、减少网络传输的数据量,提高网络利用率;
3、降低新建网络链接的开销,以实现链接在多个请求之间的复用;
4、选择合适的I/O方式,例如NIO等。
一、阻塞与非阻塞、同步与异步
------同步:发出一个调用时,没有得到结果之前,该调用不返回,由调用者主动等待调用结果。
|
关注的是消息通信机制---------------------|
|
------异步:调用发出之后,调用直接返回,此时不会拿到返回结果。被调用者通过状态通知调用者或回调函数处理这个调用。 ------阻塞:调用结果返回之前,当前线程会被挂起。
|
关注的是程序在等待调用结果时的状态---------|
|
------非阻塞:调用返回结果之前,当前线程不会被挂起。
二、BIO
概念:bio基于流,是同步阻塞IO模式,数据的读取写入必须阻塞在一个线程内等待其完成。这里使用那个经典的烧开水例子,这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一 个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。不知道io操作中什么时候有数据可读,所以一直是阻塞的模式。
缺点:当并发数达到一定量时,并且服务端需要一定的时间去处理请求时,例如1-2s,这时需要开启非常多的线程数,并且这些线程啥事不干,都是等着请求返回,大大浪费了系统资源,而且在线程切换上下文的过程中,也会浪费很多的资源
BIO是阻塞式I/O,通过socket在客户端与服务端建立双向链接以实现通信,主要步骤如下:
- a、服务端监听某个端口是否有链接请求;
- b、客户端向服务端发出链接请求;
- c、服务端向客户端返回accept()消息,此时链接成功;
- d、客户端和服务端通过send()、write()等方法与对方通信;
- e、关闭链接
eg:简单的网络通信如下:
服务端:


客户端:


这种简单的示例只支持一个客户端链接到一个服务端,现实情况是N个客户端链接到服务端。Tomcat是这么实现的:

三、NIO
概念:bio的性能是相对较差的,在NIO中,基于块的概念,可以在不编写本地代码的情况下利用底层优化。
NIO结构图:

来个复杂点的:

selectionKey则是用来描述相关事件。
1、通道(channel)

2、缓冲区(buffer)

3、选择器(selector)

简单的NIO示例:
服务端:NIOServer
package com.ty.server; import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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.Iterator;
import java.util.Set; /**
* 服务端
*/
public class NIOServer { /**
* 定义服务端的selector,
* 主要作用:
* 1、将各种事件注册到selector中,selector监控各种事件的发生,例如accept、read等
* 2、将不同事件分配到不同的channel
*/
private Selector selector; //服务端初始化
public void init() throws IOException {
//创建一个selector对象
this.selector = Selector.open();
//创建serverSocketChanel对象
ServerSocketChannel serverSocketChanel = ServerSocketChannel.open();
//设置为非阻塞
serverSocketChanel.configureBlocking(false);
//通过serverSocketChannel对象获取serverSocket
ServerSocket serverSocket = serverSocketChanel.socket();
//绑定端口
InetSocketAddress address = new InetSocketAddress(8080);
serverSocket.bind(address);
//注册accept事件到selector中,accept用于获取客户端请求
serverSocketChanel.register(selector, SelectionKey.OP_ACCEPT);
} //服务端启动服务
public void start() throws IOException {
//这地方只做一个最简单的示例,不考虑服务端stop
while(true) {
/**
* selector监控客户端是否有对应事件发生,例如accept、read等等。
* 注:此方法是阻塞的,当客户端一直没有事件触发,线程一直挂起,直到至少有一事件触发,走后续流程
*/
selector.select(); //获取该selector监控到的所有触发的事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//拿到迭代器,循环所有监控到的事件
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()) {
//事件用SelectionKey描述,主要包括connect、accept、read、write事件
SelectionKey selectionKey = iterator.next();
//每种事件只处理一次,避免重复处理
iterator.remove();
if(selectionKey.isAcceptable()) {
accept(selectionKey);
}
if(selectionKey.isReadable()) {
read(selectionKey);
}
}
}
} private void accept(SelectionKey selectionKey) throws IOException {
/**
* 从selectionkey中获取serverSocketChannel。
* ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样
*/
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
//serverSocketChannel监听到新连接后,会创建socketChannel。获取socketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
//设置为非阻塞
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} private void read(SelectionKey selectionKey) throws IOException {
/**
* SocketChannel是一个连接到TCP网络套接字的通道,就像标准IO中的socket
* 创建方式:可以通过以下2种方式创建SocketChannel
* 1、打开一个SocketChannel并连接到互联网上的某台服务器。
* 2、一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。
*/
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
//创建读取缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//通过socketChannel.read()获取客户端的请求数据
socketChannel.read(byteBuffer);
String request = new String(byteBuffer.array()).trim();
System.out.println("客户端发送的请求为:" + request);
//将一个数组包装成ByteBuffer
ByteBuffer outBuffer = ByteBuffer.wrap("请求收到啦!".getBytes());
//数据发送到客户端
socketChannel.write(outBuffer);
} public static void main(String[] args) throws IOException {
NIOServer server = new NIOServer();
server.init();
server.start();
}
}
客户端:NIOClient
package com.ty.client; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
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; public class NIOClient { private Selector selector; private BufferedReader clientInput = new BufferedReader(new InputStreamReader(System.in)); public void init() throws IOException {
//创建selector
this.selector = Selector.open();
//创建SocketChannel
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
//注册connect事件
socketChannel.register(selector, SelectionKey.OP_CONNECT);
} public void start() throws IOException {
while(true) {
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
if(selectionKey.isConnectable()) {
connect(selectionKey);
}
if(selectionKey.isReadable()) {
read(selectionKey);
}
}
}
} public void connect(SelectionKey selectionKey) throws IOException {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
//如果客户端正在链接
if(socketChannel.isConnectionPending()) {
//如果客户端已经链接成功
if(socketChannel.finishConnect()) {
socketChannel.configureBlocking(false);
//链接成功后自然要获取服务端的返回,因此注册read事件
socketChannel.register(selector, SelectionKey.OP_READ);
String request = clientInput.readLine();
//数据发送到服务端
socketChannel.write(ByteBuffer.wrap(request.getBytes()));
}else {
//事件未注册成功,取消掉
selectionKey.cancel();
}
}
} public void read(SelectionKey selectionKey) throws IOException {
//socketChannel与服务端的对应,双方友好建立一个通道
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
socketChannel.read(byteBuffer);
System.out.println("服务端响应为:" + new String(byteBuffer.array()).trim());
String request = clientInput.readLine();
socketChannel.write(ByteBuffer.wrap(request.getBytes()));
} public static void main(String[] args) throws IOException {
NIOClient client = new NIOClient();
client.init();
client.start();
}
}
测试结果:
服务端:

客户端:

这样通过NIO,客户端与服务端即可正常通信。
Tomcat中的NIO实现:



Tomcat架构解析(六)-----BIO、NIO、NIO2、APR的更多相关文章
- Apache Tomcat 7 Configuration BIO NIO AIO APR ThreadPool
Apache Tomcat 7 Configuration Reference (7.0.93) - The Executor (thread pool)https://tomcat.apache.o ...
- Tomcat架构解析(二)-----Connector、Tomcat启动过程以及Server的创建过程
Connector用于跟客户端建立连接,获取客户端的Socket,交由Container处理.需要解决的问题有监听.协议以及处理器映射等等. 一.Connector设计 Connector要实现的 ...
- tomcat架构分析(connector BIO 实现)
出处:http://gearever.iteye.com 在tomcat架构分析(概览)中已经介绍过,connector组件是service容器中的一部分.它主要是接收,解析http请求,然后调用本s ...
- Tomcat架构解析(一)-----Tomcat总体架构
Tomcat是非常常用的应用服务器,了解Tomcat的总体架构以及实现细节,对于理解整个java web也是有非常大的帮助. 一.Server 1.最简单的服务器结构 最简单的服务器结构如图所示: ...
- 【转】Tomcat 的三种(bio,nio.apr) 高级 Connector 运行模式
转载地址:http://www.oschina.net/question/54100_16195 tomcat的运行模式有3种.修改他们的运行模式.3种模式的运行是否成功,可以看他的启动控制台,或 ...
- Tomcat 的三种(bio,nio.apr) 高级 Connector 运行模式及apr配置
转: http://www.oschina.net/question/54100_16195omcat的运行模式有3种.修改他们的运行模式.3种模式的运行是否成功,可以看他的启动控制台,或者启动日志. ...
- Tomcat 的三种(bio,nio.apr) 高级 Connector 运行模式
tomcat的运行模式有3种.修改他们的运行模式.3种模式的运行是否成功,可以看他的启动控制台,或者启动日志.或者登录他们的默认页面http://localhost:8080/查看其中的服务器状态. ...
- Tomcat架构解析(四)-----Coyote、HTTP、AJP、HTTP2等协议
Connector是Tomcat中非常重要的一个组成部分,说白了,就是如何从客户端获取到相应的请求信息.这部分主要包括的难点有这样几个部分: 1.客户端与服务端的协议 客户端与服务端的协议是多种多样的 ...
- Tomcat架构解析(三)-----Engine、host、context解析以及web应用加载
上一篇博文介绍了Server的创建,在Server创建完之后,就进入到Engine的创建过程,如下: 一.Engine的创建 1.创建Engine实例 当前次栈顶元素为Service对象,通过Se ...
随机推荐
- python3与python2的区别(目前遇到的)
1.进击的print,变成一个函数,print() 2.urllib大一统,呵呵 3.python3默认绝对路径导入
- Wannafly挑战赛14 C.可达性(tarjan缩点)
题目描述 给出一个 0 ≤ N ≤ 105 点数.0 ≤ M ≤ 105 边数的有向图, 输出一个尽可能小的点集,使得从这些点出发能够到达任意一点,如果有多个这样的集合,输出这些集合升序排序后字典序最 ...
- 动态规划:压缩编码;WirelessRouters;
转载请注明~ 如果有理解不到位或错误的情况,劳烦大神指正,一定感激不尽! 题目来源:CCF201612-4 压缩编码 题目的意思是: 1. 顺序给定文字中n个单词出现的频率(次数): 2. 对这n个单 ...
- Linux 编译时内存不足
1.编译内核出现问题:No space left on device AS .tmp_kallsyms1.o .tmp_kallsyms1.S:2: fatal error: when wr ...
- vue 自定义组件directives
自定义指令:以v开头,如:v-mybind. 代码示例: <input v-mybind /> directives:{ mybind:{ bind:function (el) { el. ...
- 包含了重复的“Content”项。.NET SDK 默认包含你项目目录中的“Content”项。可从项目文件中删除这些项;如果希望将其显式包含在项目文件中,可将“EnableDefaultContentItems”属性设置为“false”
从.netcore 1.1 升级到2.0时遇到该问题. 参考http://www.cnblogs.com/xishuai/p/visual-studio-for-mac.html 根据提示可知(我是看 ...
- c#一个统计运行时间方法
public string STD(int HowManySecond) { ) { "; } string ShowStr = ""; * )) { ShowStr + ...
- 2.Mysql SQL基础
2.Mysql SQL基础2.1 SQL简介 SQL(Structure Query Language)是结构化查询语言.2.2 SQL使用入门 2.2.1 SQL分类 SQL分为DDL.DML(DQ ...
- virtaulbox docker虚拟机使用主机代理shandowsocks
1.virtaulbox 配置NatNetwork File->Preference->network->add new nat network 2.virtaulbox 虚拟机配置 ...
- Python 字符串(count)
字符串 count:(python中的count()函数,从字面上可以知道,他具有统计功能) Python count() 方法用于统计字符串里某个字符出现的次数.可选参数为在字符串搜索的开始与结束位 ...