前面在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之间的主要区别, ...
随机推荐
- sdk 更新的时连接不上dl-ssl.google.com解决办法
今天有朋友说sdk的更新不了,借了个VPN给他也没解决问题,后来还是他自己解决了,下面分享下经验 这里介绍一种不需要FQ的解决办法,修改C:\Windows\System32\drivers\etc下 ...
- Maven-007-Nexus 用户添加,用户角色分配,用户修改密码,管理员重置用户密码
配置好 maven nexus 私服后,默认的用户可通过查看[Users]查看当前私服中所存在的用户,如下图所示:
- request.getHeader所想到的
request.getHeader(""),简单的说就是获取请求的头部信息,根据http协议,它能获取到用户访问链接的信息. /** * Returns the value of ...
- iOS开发小技巧
1. 解析详情页(是webView)遇到的3个问题: 1.图片太大,超出屏幕范围 2.怎么在webView上面添加一行文字 3.文字太小 1.解决方法 webView.scalesPageToFit ...
- 淘宝开放平台Session Key有效期
各标签session时长及RefreshToken失效时长 *Refresh失效时长为0,即该sessionkey不可刷新. 标签 授权时长 Refresh失效时长 商家后台应用 固定时长1年 0 ...
- tomcat access log 参数
%a - 客户端IP地址 %A - 本机IP地址 %b - 发送字节数,不含HTTP头 如果为空是 '-' %B - 同上 %h - 客户端机器名 (如果connector的enableLookup ...
- RAC One Node转换为RAC
1.查看状态 [oracle@rone1 ~]$ srvctl config database -d rone Database unique name: rone Database name: ro ...
- 湖大OJ-实验C----NFA转换为DFA
实验C----NFA转换为DFA Time Limit: 1000ms, Special Time Limit:2500ms, Memory Limit:65536KB Total submit us ...
- jQuery选择器和事件
选择器 常用事件 绑定与解除绑定 事件目标与冒泡 自定义事件
- win2003超过最大连接数
远程连接强制登陆: 命令:mstsc /v:IP /admin|console 例如:mstsc /v:123.456.789.0 /console 或mstsc /v:123.456.789.0 / ...