d.BIO连接器与NIO连接器的对比之二

前面在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操作也是必不可少的。

来自为知笔记(Wiz)

时间: 2024-07-30 23:54:26

d.BIO连接器与NIO连接器的对比之二的相关文章

c.BIO连接器与NIO连接器的对比

前面两节,我们分别看了BIO和NIO的两种模式Tomcat的实现方式. BIO的方式,就是传统的一线程,一请求的模式,也就是说,当同时又1000个请求过来,如果Tomcat设置了最大Accept线程数为500,那么第一批的500个线程直接进入线程池中进行执行,而其余500个根据Accept的限制的数量在服务器端的操作系统的内核位置的socket缓冲区进行阻塞,一直到前面500个线程处理完了之后,Acceptor组件再逐步的放进来. 分析一下,这种模式的BIO的好处,可以让一个请求在cpu轮转时间

Tomcat 8(十)HTTP/AJP Connector、Bio/Nio/Apr性能对比

Tomcat 8(七)解读Bootstrap介绍过,Connector初始化/启动的时候,将初始化/启动内部的ProtocolHandler.其实ProtocolHandler只是个接口 ProtocolHandler的UML图(以下这些类在org.apache.coyote包下) 创建Connector对象时,Connector的构造函数内会根据server.xml的Connector标签的配置创建ProtocolHandler(默为Http11NioProtocol) public Conn

Java--Stream,NIO ByteBuffer,NIO MappedByteBuffer性能对比

目前Java中最IO有多种文件读取的方法,本文章对比Stream,NIO ByteBuffer,NIO MappedByteBuffer的性能,让我们知道到底怎么能写出性能高的文件读取代码. package com.seeyon.nio; import org.junit.Test; import java.io.*; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.Fi

MTP连接器和MPO连接器简介

随着人们对更快的数据传输速度的要求越来越高,尽可能高效地传输数据的能力仍然是当今通信时代的主要力量.由于大多数现代数字信息系统的骨干网都是由光纤网络支撑的,因此人们越来越关注MPO光纤连接器和MTP光纤连接器. 什么是MPO和MTP连接器? MPO是"多光纤推进"的行业缩写.MPO连接器最初创建于20世纪末,是日本NTT公司开发的第一代夹持多芯光纤连接器,它可以方便地互连多股光纤并传输不断增加的数据量. 虽然MTP是美国康耐克公司的注册商标,作为"与普通MPO连接器相比,具有

tomcat 用nio连接器,建ssl安全协议配置

第一步:用连接器 <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="150" minSpareThreads="4"/> 启用 <Connector executor="tomcatThreadPool" port="80" protocol="org.apach

java 的nio与io对比

转:本文并非Java.io或Java.nio的使用手册,也不是如何使用Java.io与Java.nio的技术文档.这里只是尝试比较这两个包,用最简单的方式突出它们的区别和各自的特性.Java.nio提出了新的流(stream)通讯概念并且加入了新的缓冲.文件流以及socket(套接字)特性. java.io 概览 这个包通过数据流和序列化机制来实现系统输入和输出.并且支持多种类型的数据流,包括简单的字节.原生数据类型.地区字符以及对象.流是一个数据的序列:一个程序使用输入流从一个源头读取数据:

BIO,NIO,AIO总结(二)

这里重点介绍NIO 待定 http://www.apigo.cn/2018/11/09/javacore5/ https://juejin.im/entry/598da7d16fb9a03c42431ed3 https://mp.weixin.qq.com/s/c9tkrokcDQR375kiwCeV9w? 原文地址:https://www.cnblogs.com/smallJunJun/p/10607078.html

Java NIO系列教程(十二) Java NIO与IO

当学习了Java NIO和IO的API后,一个问题马上涌入脑海: 我应该何时使用IO,何时使用NIO呢?在本文中,我会尽量清晰地解析Java NIO和IO的差异.它们的使用场景,以及它们如何影响您的代码设计. Java NIO和IO的主要区别 下表总结了Java NIO和IO之间的主要差别,我会更详细地描述表中每部分的差异. IO                NIO 面向流            面向缓冲 阻塞IO           非阻塞IO 无 选择器 面向流与面向缓冲 Java NIO

通过读取excel数据和mysql数据库数据做对比(二)-代码编写测试

通过上一步,环境已搭建好了. 下面开始实战, 首先,编写链接mysql的函数conn_sql.py import pymysql def sql_conn(u,pwd,h,db): conn=pymysql.connect(user=u,passwd=pwd,host=h,db=db) #print("连接数据库"+db+"成功了!!") return conn 在编写,查询数据库的语句:sql.py import pymysql import conn_sql d