前面在Tomcat中讲解了两个通道,BIO和NIO,我们这里来通过两端程序,简单模拟两个通道,找找异同点:
BIO:
1.
public class SocketServer {
public SocketServer() {
try {
int clientcount = 0; // 统计客户端总数
boolean listening = true; // 是否对客户端进行监听
ServerSocket server = null; // 服务器端Socket对象
try {
// 创建一个ServerSocket在端口2121监听客户请求
server = new ServerSocket(2121);
System.out.println("Server starts...");
} catch (Exception e) {
System.out.println("Can not listen to. " + e);
}
while (listening) {
// 客户端计数
clientcount++;
// 监听到客户请求,根据得到的Socket对象和客户计数创建服务线程,并启动之
new ServerThread(server.accept(), clientcount).start(); ====》一请求,一线程,这个就是模拟Tomcat的前端的
}
} catch (Exception e) {
System.out.println("Error. " + e);
}
}
public static void main(String[] args) {
new SocketServer();
}
}
上面的这一段代码,在Tomcat中实质就是Acceptor线程。
2.
对应Tomcat的工作线程实际上就是ServerThread这个类:
public class ServerThread extends Thread {
private static int number = 0; // 保存本进程的客户计数
Socket socket = null; // 保存与本线程相关的Socket对象
public ServerThread(Socket socket, int clientnum) {
this.socket = socket;
}
public void run() {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(socket
.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream()); //从socket中获取流
BufferedReader sysin = new BufferedReader(new InputStreamReader(
System.in));
System.out.println("[Client " + number + "]: " + in.readLine());
String line;
line = sysin.readLine(); //流式读取
while (!line.equals("bye")) { // 如果该字符串为 "bye",则停止循环
out.println(line);
out.flush(); 流式写入与刷新
System.out.println("[Server]: " + line);
System.out.println("[Client " + number + "]: " + in.readLine());
line = sysin.readLine();
}
out.close(); // 关闭Socket输出流
in.close(); // 关闭Socket输入流
socket.close(); // 关闭Socket
} catch (Exception e) {
System.out.println("Error. " + e);
}
}
}
上述的工作线程,直接在这里进行了处理,在Tomcat中,这个流程非常复杂,后续的代码一直延伸到Tomcat的后端容器当中。
总结一下,其实BIO的模式很明白,一线程一个请求,从上面的例子中已经看得很清晰,
而socket的读取是流式的读取,也就是从socket中获得流,直接通过java,io进行流的读取。
NIO:
public class NIOServer {
/*标识数字*/
private int flag = 0;
/*缓冲区大小*/
private int BLOCK = 4096;
/*接受数据缓冲区*/
private ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
/*发送数据缓冲区*/
private ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
private Selector selector;
public NIOServer(int port) throws IOException {
//======>这个相当于Tomcat中Acceptor线程,只不过这里对通道进行了再次包装,对SelectionKey进行传入,
// 打开服务器套接字通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 服务器配置为非阻塞
serverSocketChannel.configureBlocking(false);
// 检索与此通道关联的服务器套接字
ServerSocket serverSocket = serverSocketChannel.socket();
// 进行服务的绑定
serverSocket.bind(new InetSocketAddress(port));
// 通过open()方法找到Selector
selector = Selector.open();
// 注册到selector,等待连接
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server Start----8888:");
}
private void listen() throws IOException {
//======> 这个相当于Tomcat中Poller线程,对前面包装的SelectionKey进行轮询,并通过handleKey传递到工作线程中去
while (true) {
// 选择一组键,并且相应的通道已经打开
selector.select();
// 返回此选择器的已选择键集。
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
handleKey(selectionKey);
}
}
}
// 处理请求
//======>这下面直接就是工作线程,进入Tomcat容器的环节中,并最终返回调用的结果
private void handleKey(SelectionKey selectionKey) throws IOException {
// 接受请求
ServerSocketChannel server = null;
SocketChannel client = null;
String receiveText;
String sendText;
int count=0;
// 测试此键的通道是否已准备好接受新的套接字连接。
if (selectionKey.isAcceptable()) {
// 返回为之创建此键的通道。
server = (ServerSocketChannel) selectionKey.channel();
// 接受到此通道套接字的连接。
// 此方法返回的套接字通道(如果有)将处于阻塞模式。
client = server.accept();
// 配置为非阻塞
client.configureBlocking(false);
// 注册到selector,等待连接
client.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 返回为之创建此键的通道。
client = (SocketChannel) selectionKey.channel();
//将缓冲区清空以备下次读取
receivebuffer.clear();
//读取服务器发送来的数据到缓冲区中
count = client.read(receivebuffer);
if (count > 0) {
receiveText = new String( receivebuffer.array(),0,count);
System.out.println("服务器端接受客户端数据--:"+receiveText);
client.register(selector, SelectionKey.OP_WRITE);
}
} else if (selectionKey.isWritable()) {
//将缓冲区清空以备下次写入
sendbuffer.clear();
// 返回为之创建此键的通道。
client = (SocketChannel) selectionKey.channel();
sendText="message from server--" + flag++;
//向缓冲区中输入数据
sendbuffer.put(sendText.getBytes());
//将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
sendbuffer.flip();
//输出到通道
client.write(sendbuffer);
System.out.println("服务器端向客户端发送数据--:"+sendText);
client.register(selector, SelectionKey.OP_READ);
}
}
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
int port = 8888;
NIOServer server = new NIOServer(port);
server.listen();
}
}
总结一下,上述的NIO程序中的几个阶段,可以反映出Tomcat的NIO的前端线程池的工作模式,只不过上面的代码是串行的,而Tomcat中是通过几个线程池进行交接的,每个线程池之间需要共享一些数据用于传递。
最后,在笔者编写NIO的程序出了几个几个错误,提醒一下各位,我们需要注意一下:
1.上述在每一次selectkey轮询出来的时候,需要注意最后一句话,例如当Accept事件发生后,SocketChannel需要改变自身关注的事件,因为这个时候如果还关注Accept事件,那相当于什么都发生不了,因为这个时候SocketChannel已经再发送数据了,相当于只有监测Read和Write事件才能拿到Socket的输入和输出。因此,可以看到当Accept事件发生后:
client.register(selector, SelectionKey.OP_READ);
重新注册Read事件。
再来看看Read事件的最后一句话,注册的是Write事件
client.register(selector, SelectionKey.OP_WRITE);
这个是因为读取完成后,立刻就要发送一些内容,所以需要改变SocketChannel的工作模式,将其置为WRITE模式。
同理,对于READ事件之后,同样立刻要读取客户端的数据,这个还得重新注册Read事件;
对于上述的通道模式的改变,Tomcat是将这些封装到NioChannel中,不断基于socket的工作模式进行切换。
2.还有一个值得注意的事情,就是buffer缓冲区的flip操作,首先需要注意的是三个buffer的属性:
这三个属性,根据读模式和写模式的不同而不同
capacity:
- 容量,无论是写模式,还是读模式,容量都是固定的
- position:指当前的位置
- 写模式时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1.
读模式时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。
limit:最大能读/写的限制
在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。
当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)
我们明白上述的原理后,就不难理解,为啥上述的buffer经常要flip一下,flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值,这样就相当于做好了读的准备,而上述代码中的sendbuffer立马要进行:
/输出到通道
client.write(sendbuffer);
这个client.write方法中,实际是先从buffer中进行读取字节,然后再发送到socket缓冲区中,因此虽然这个表面上看来没有读的意思,但实际上隐藏着读,因此flip操作也是必不可少的。
- c.BIO连接器与NIO连接器的对比
前面两节,我们分别看了BIO和NIO的两种模式Tomcat的实现方式. BIO的方式,就是传统的一线程,一请求的模式,也就是说,当同时又1000个请求过来,如果Tomcat设置了最大Accept线程数 ...
- Java--Stream,NIO ByteBuffer,NIO MappedByteBuffer性能对比
目前Java中最IO有多种文件读取的方法,本文章对比Stream,NIO ByteBuffer,NIO MappedByteBuffer的性能,让我们知道到底怎么能写出性能高的文件读取代码. pack ...
- 连接器|网络滤波连接器|电脑连接器|RJ45变压器-华联威电子有限公司
连接器|网络滤波连接器|电脑连接器|RJ45变压器-华联威电子有限公司
- Revit MEP API找到连接器连接的连接器
通过conn.AllRefs;可以找到与之连接的连接器. //连接器连接的连接器 [TransactionAttribute(Autodesk.Revit.Attributes.Transaction ...
- Java NIO 学习笔记(七)----NIO/IO 的对比和总结
目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...
- EMS设置发送连接器和接收连接器邮件大小
任务:通过EMS命令设置发送接收连接器和接收连接器的邮件大小限制值为50MB. 以Exchange管理员身份打开EMS控制台.在PowerShell命令提示符下. 键入以下命令设置接收-连接器的最大邮 ...
- 1、nio说明 和 对比bio
nio和bio的区别 bio: 面向流的. 单向的. 阻塞的,这也是b这个的由来. nio: 面向块的.(buffer) 双向的. 非阻塞的.同步的编程方式.是一种select模型 nio编程的常规步 ...
- Java NIO学习系列四:NIO和IO对比
前面的一些文章中我总结了一些Java IO和NIO相关的主要知识点,也是管中窥豹,IO类库已经功能很强大了,但是Java 为什么又要引入NIO,这是我一直不是很清楚的?前面也只是简单提及了一下:因为性 ...
- Java NIO学习笔记九 NIO与IO对比
Java NIO与IO Java nio 和io 到底有什么区别,以及什么时候使用nio和io,本文做一个比较. Java NIO和IO之间的主要区别 下表总结了Java NIO和IO之间的主要区别, ...
随机推荐
- Redis的Python客户端redis-py
1. 安装 1. redis-py a. 使用easy_install 1 sudo easy_install redis b. 源码安装 1 2 3 git clone https://githu ...
- English随笔1
英语中的基本五大句型 1.Subject (主语) + Verb (谓语) Li Ming works The accident happened 2.Subject (主语) + Link. V( ...
- 真正的让iframe自适应高度 兼容多种浏览器随着窗口大小改变
今天有朋友问到我关于"iframe自适应高度"的问题,原本以为是很简单的问题,没想到折腾了20分钟才搞定.期间遇到几个问题,要么是高度自适应了,但是当窗口改变时会出现滚动条.也就是 ...
- javascript源代码学习之五——jQuery.deferred
jQuery.Defered——异步队列用于管理一组回调函数(成功resolve,失败reject,消息progress),基于上一节实现的jQuery.callbacks完成. done,fail, ...
- react 年-月-日 时:分:秒
// 时间let date = new Date();debugger;let seperator1 = "-";let seperator2 = ":";le ...
- sql 中实现往表中插入一条记录并返回当前记录的ID
写一条存储过程,实现往User中插入一条记录并返回当前UserId(自增长id) --推荐写法 if(Exists(select * from sys.objects where name=N'Usp ...
- Web Components是不是Web的未来
今天 ,Web 组件已经从本质上改变了HTML.初次接触时,它看起来像一个全新的技术.Web组件最初的目的是使开发人员拥有扩展浏览器标签的能力,可以自由的进行定制组件.面对新的技术,你可能会觉得无从下 ...
- Dynamics AX 2012 R2 配置E-Mail模板
在AX中使用邮件模板可以,可以让邮件的内容更专业化.下面,跟随Reinhard一起,配置E-Mail模板吧. 进入Organization Administration>Setup>E-m ...
- Python之反射
一.引言 有时候我们会碰到类似这样的需求,就是想要执行类的某个方法,或者需要对对象的某个参数赋值,而方法名或参数名已经包装在类中并不能去顶,需要通过参数传递字符串的形式输入.在这样的情况你会选择什么样 ...
- eclipse开启断言
断言在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制.一般来说,断言用于保证程序最基本.关键的正确性.断言检查通常在开发和测试时开启. 为了保证程序的执行效率,在软件发布后断言检查通常是 ...