前言
随着互联网的不断发展,用户群体越发庞大。
从互联网初入中国,国内的上网用户不过数万,而上网也只能简单的进行邮件,浏览新闻。到现在几乎每个人都可以通过互联网进行社交,娱乐,学习。中间不但有社会经济的发展,java技术也随之不断发展,究竟是科学技术的发展促使了社会的发展,还是社会经济的发展促使了科学技术?这个问题我想大家应该都知道一句话:科学技术是第一生产力。
随着用户全体的庞大,对于网络通信的要求越发高,BIO越发不适应需求,于是NIO应运而生。
NIO(New IO)又被业内称之为:Non Block IO,即非阻塞IO
一、传统的BIO编程
1、BIO通信模型图
BIO通信的服务器,对于每一个客户端的连接,都会使用一个独立的Acceptor线程,来对客户端进行连接。
即,他收到客户端请求后,会为每一个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,最后线程销毁。这就是典型的一请求一应答的通信模型。
线程是JVM非常宝贵的资源,当线程数过大后,随着并发访问数的继续增大,系统将会产生堆栈移除,创建线程失败等事故。最终导致服务器宕机或僵死,无法对外提供服务。
2、BIO的Server源码分析
package com.xyp.iodemo.nio.server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.logging.Logger;
/**
* @author xuyuanpeng
* @version 1.0
* @date 2019-03-28 18:13
*/
public class TimeServer {
public static Logger XLogUtils = Logger.getLogger("TimeServer");
public static void main(String [] args) throws IOException {
Integer port=20022;
if(args!=null&&args.length>0){
try {
port=Integer.valueOf(args[0]);
}catch (Exception e){
}
}
ServerSocket serverSocket=null;
try {
serverSocket=new ServerSocket(port);
XLogUtils.info("The TimeServer is start in port:"+port);
Socket socket=null;
while (true){
socket=serverSocket.accept();
new Thread(new TimeServerHandler(socket)).start();
}
}finally {
if(serverSocket!=null){
XLogUtils.info("The Time is Close");
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
serverSocket=null;
}
XLogUtils.info("END>>>>");
}
}
}
TimeServer是一个很简单的类,它负责创建ServerSocket,然后对客户端的请求进行接受,这里值得注意的一点就是,这里的
serverSocket.accept()
是阻塞的,即sever等待客户端请求接入,如果接入就创建线程处理请求,创建完毕后,继续等待下一个客户端的接入。
然后我们再来一起看一下具体针对线程的处理。
package com.xyp.iodemo.nio.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.logging.Logger;
/**
* @author xuyuanpeng
* @version 1.0
* @date 2019-03-28 19:13
*/
public class TimeServerHandler implements Runnable {
private Socket socket;
public static Logger logger = Logger.getLogger("TimeServerHandler");
public TimeServerHandler(Socket socket){
this.socket=socket;
logger.info("TimeServerHandler 构造函数");
}
@Override
public void run() {
BufferedReader in =null;
PrintWriter out=null;
try {
in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
out=new PrintWriter(socket.getOutputStream(),true);
String currentTime=null;
String body=null;
logger.info("TimeServerHandler run");
while (true){
logger.info("TimeServerHandler wait input");
body=in.readLine();
logger.info("TimeServerHandler wait finish:"+String.valueOf(body));
if(body== null){
break;
}
logger.info("The time server receive order : "+body);
currentTime="QUERY TIME ORDER".equalsIgnoreCase(body)?new java.util.Date(
System.currentTimeMillis()
).toString():"BAD ORDER";
out.println(currentTime);
}
}catch (Exception e){
if(in!=null){
try {
in.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if(out!=null){
out.close();
out=null;
}
if(socket!=null){
try {
socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}finally {
socket=null;
}
}
}
}
}
3、Client的源码分析
package com.xyp.iodemo.bio.client;
import java.io.*;
import java.net.Socket;
import java.util.logging.Logger;
/**
* @author xuyuanpeng
* @version 1.0
* @date 2019-04-01 18:21
*/
public class TimeClient {
public static Logger logger = Logger.getLogger("TimeClient");
public static void main(String [] args) throws IOException {
Integer port=20022;
if(args!=null&&args.length>0){
try {
port=Integer.valueOf(args[0]);
}catch (Exception e){
}
}
Socket socket=null;
BufferedReader in =null;
PrintWriter out = null;
try {
socket = new Socket("127.0.0.1", port);
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));//用户获取用户输入
OutputStream os = socket.getOutputStream();//用于向服务器输出
System.out.println("请输入要发送的文字:");
String input;
//等待cons
while ((input= reader.readLine()) != null) {
input = input+"\n";//手动加上回车
os.write(input.getBytes("utf-8"));
logger.info("向服务的写入:"+input.toString());
}
}catch (Exception e){
}finally {
if(out==null){
out.close();
out=null;
}
if(in==null){
try {
in.close();
}catch (IOException e){
e.printStackTrace();
}
in=null;
}
if(socket!=null){
try {
socket.close();
}catch (IOException e2){
e2.printStackTrace();
}
socket=null;
}
}
}
}
等待控制台的输入,按下回车键,就会向服务器写入字节。
4、运行结果
(1)启动Server的console
四月 08, 2019 4:25:41 下午 com.xyp.iodemo.nio.server.TimeServer main
信息: The TimeServer is start in port:20022
(2)启动Client的console,在其中输入了 111后,按下回车
请输入要发送的文字:
111
四月 08, 2019 4:27:40 下午 com.xyp.iodemo.bio.client.TimeClient main
信息: 向服务的写入:111
(3)服务器响应输出。输出是分步的
当client启动后,输出
四月 08, 2019 4:27:30 下午 com.xyp.iodemo.nio.server.TimeServerHandler
信息: TimeServerHandler 构造函数
四月 08, 2019 4:27:30 下午 com.xyp.iodemo.nio.server.TimeServerHandler run
信息: TimeServerHandler run
四月 08, 2019 4:27:30 下午 com.xyp.iodemo.nio.server.TimeServerHandler run
信息: TimeServerHandler wait input
当客户端按下回车键后
四月 08, 2019 4:27:40 下午 com.xyp.iodemo.nio.server.TimeServerHandler run
信息: TimeServerHandler wait finish:111
四月 08, 2019 4:27:40 下午 com.xyp.iodemo.nio.server.TimeServerHandler run
信息: The time server receive order : 111
四月 08, 2019 4:27:40 下午 com.xyp.iodemo.nio.server.TimeServerHandler run
信息: TimeServerHandler wait input
二、NIO基础概念
1、缓冲区Buffer
Buffer是一个对象,他包含了一些要写入的或者要读出的数据。
在NIO中加入Buffer对象,体现了新库与原始IO的一个重大区别,即,在面流的IO中,可以直接将数据写入或者直接将数据读到steam对象中。
Java NIO中的Buffer用于和NIO通道进行交互。如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的。
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。
2、通道Channel
Channel是一个通道,可以通过它进行数据的读取和写入。如同自来水管道,网络数据在channel中可以进行双向的流通,这点不同于steam,steam是单向的,即steam要么是InputSteam,要么是OutputSteam。
而通道可以用于读、写、或同时进行读写。
3、多路复用器 Selector
多路复用器,提供平选择已就绪任务的能力。
简单来说,selector会不断轮询注册在其上的Channel,如果某个Channel上有新的Tcp接入,或者有发生读写事件,这个Channel就会处于就绪状态,可以被Selector轮询出来,然后通过selectedKey获取就绪的Channel集合,以便进行后续的IO操作。
selector.selectedKeys()
一个多路复用器可以同时轮询多个Channel。
4、NIO的Server的时序图
5、NIO的Server源码分析
(1)TimeServer
此类的作用主要是声明端口,以及启动线程
package com.xyp.iodemo.nio.server;
/**
* @author xuyuanpeng
* @version 1.0
* @date 2019-04-02 11:05
*/
public class TimeServer {
public static void main(String [] args){
Integer port=8085;
if(args!=null&&args.length>0){
try {
port=Integer.valueOf(args[0]);
}catch (Exception e){
}
}
MultiplexerTimeServer timeServer=new MultiplexerTimeServer(port);
new Thread(timeServer,"NIO-MultiplexerTimeServer-001").start();
}
}
(2)MultiplexerTimeServer
package com.xyp.iodemo.nio.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Logger;
/**
* @author xuyuanpeng
* @version 1.0
* @date 2019-04-02 11:11
*/
public class MultiplexerTimeServer implements Runnable{
public static Logger logger = Logger.getLogger("MultiplexerTimeServer");
private Selector selector;
private ServerSocketChannel serverSocketChannel;
private volatile Boolean stop;
public MultiplexerTimeServer(Integer port){
stop=false;
try {
//创建多路复用器
selector=Selector.open();
//打开ServerSocketChannel,用于监听客户端连接,他是所有的客户端连接的父管道
serverSocketChannel=ServerSocketChannel.open();
//设置为非阻塞
serverSocketChannel.configureBlocking(false);
//绑定监听端口
serverSocketChannel.socket().bind(new InetSocketAddress(port),1024);
//将serverSocketChannel注册到多路复用器,监听ACCEPT事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
logger.info("The Time server is start in port: " +port);
}catch (IOException e){
e.printStackTrace();
System.exit(1);
}
}
public void stop(){
this.stop=true;
}
@Override
public void run() {
while (!stop){
try {
//设置休眠时间为1s,无论是否有读写事件发生,selector每1s都被唤醒一次
selector.select(1000);
// logger.info("selector被唤醒>>>>>>>");
//多路复用器无线循环获取准备就绪的Key
Set<SelectionKey> keys=selector.selectedKeys();
// logger.info("keys len>>>>>>>"+keys.size());
Iterator<SelectionKey> it=keys.iterator();
SelectionKey key=null;
while (it.hasNext()){
key=it.next();
it.remove();
try {
handleInput(key);
}catch (Exception e){
if(key!=null){
key.cancel();
if(key.channel()!=null){
key.channel().close();
}
}
}
}
}catch (Throwable e){
e.printStackTrace();
}
}
if(selector!=null){
try {
selector.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
private void handleInput(SelectionKey key) throws IOException{
if(key.isValid()){
if(key.isAcceptable()){
//多路复用器监听到有新的客户端接入,处理新的接入请求,完成TCP的三次握手
//建立物理连接
ServerSocketChannel ssc= (ServerSocketChannel) key.channel();
logger.info("准备监听客户端连接");
SelectableChannel sc=ssc.accept();
logger.info("设置监听完毕");
//设置客户端链路,为非阻塞模式
sc.configureBlocking(false);
sc.register(selector,SelectionKey.OP_READ);
}
if(key.isReadable()){
logger.info("key.isReadable");
SocketChannel sc= (SocketChannel) key.channel();
ByteBuffer byteBuffe=ByteBuffer.allocate(1024);
//异步读取客户端请求消息到缓存区
logger.info("异步读取客户端请求消息到缓存区>>"+byteBuffe.toString());
int readBytes=sc.read(byteBuffe);
if(readBytes>0){
//将缓冲区的当前limit设置为postion,position设置为0,用于后续对缓冲区的读取操作
byteBuffe.flip();
byte [] bytes=new byte[byteBuffe.remaining()];
byteBuffe.get(bytes);
//对ByteBuffer进行编解码,如果有半包消息指针reset,继续读取后续的报文
//将解码成功的消息封装成Task,投递到业务线程池中,进行业务逻辑编排
String body=new String(bytes,"UTF-8");
logger.info("The Time server receive order : "+body);
String currentTime="QUERY TIME ORDER".equalsIgnoreCase(body)?
new java.util.Date(System.currentTimeMillis()).toString():"BAD ORDER";
//调用异步write接口,将消息异步发送到客户端
doWrite(sc,currentTime);
}else if(readBytes<0){
key.channel();
sc.close();
}else {
}
}
}
}
private void doWrite(SocketChannel channel,String response) throws IOException {
if(response!=null&&response.trim().length()>0){
byte [] bytes=response.getBytes();
ByteBuffer writeBuffer=ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer);
}
}
}
6、NIO的Client
1、NIO的Client的时序图
2、Client源码分析
(1)TimeClient
主要作用是声明端口,启动线程。
/**
* @author xuyuanpeng
* @version 1.0
* @date 2019-04-02 16:59
*/
public class TimeClient {
public static void main(String [] args){
Integer port=8085;
if(args!=null&&args.length>0){
try {
port=Integer.valueOf(args[0]);
}catch (Exception e){
}
}
TimeHandle timeServer=new TimeHandle("127.0.0.1",port);
new Thread(timeServer,"NIO-TimeHandle-001").start();
}
}
(2)TimeHandle
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Logger;
/**
* @author xuyuanpeng
* @version 1.0
* @date 2019-04-02 17:01
*/
public class TimeHandle implements Runnable{
private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private volatile Boolean stop;
public static Logger logger = Logger.getLogger("TimeHandle");
public TimeHandle(String host,int port){
this.host=host==null?"127.0.0.1":host;
this.port=port;
this.stop=false;
try {
selector=Selector.open();
socketChannel=SocketChannel.open();
socketChannel.configureBlocking(false);
}catch (Exception e){
e.printStackTrace();
System.exit(1);
}
logger.info("TimeHandle构造完毕");
}
@Override
public void run() {
try {
logger.info("run >> 开始连接");
doConnect();
logger.info("run >> 连接完毕");
}catch (Exception e){
e.printStackTrace();
System.exit(1);
}
while (!stop){
try {
selector.select(1000);
// logger.info("selector被唤醒>>>>>>>");
Set<SelectionKey> selectionKeys=selector.selectedKeys();
// logger.info("keys len>>>>>>>"+selectionKeys.size());
Iterator<SelectionKey> it=selectionKeys.iterator();
SelectionKey key=null;
while (it.hasNext()){
key=it.next();
it.remove();
try {
handleInput(key);
}catch (Exception e){
if(key!=null){
key.cancel();
if(key.channel()!=null){
key.channel().close();
}
}
}
}
}catch (Exception e){
e.printStackTrace();
System.exit(1);
}
}
if(selector!=null){
try {
selector.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
private void handleInput(SelectionKey key) throws IOException {
// logger.info("handleInput>>");
if(key.isValid()){
// logger.info("key.isValid>>");
SocketChannel sc= (SocketChannel) key.channel();
//测试此键的通道是否已完成其套接字连接操作。
if(key.isConnectable()){
logger.info("sc.isConnected>>");
if(sc.finishConnect()){
logger.info("sc.finishConnect>>");
sc.register(selector,SelectionKey.OP_READ);
doWrite(sc);
}else {
logger.info("Link fail exit...");
System.exit(1);
}
if(key.isReadable()){
ByteBuffer readBuffer=ByteBuffer.allocate(1024);
int readBytes=sc.read(readBuffer);
if(readBytes>0){
readBuffer.flip();
byte [] bytes=new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body=new String(bytes,"UTF-8");
System.out.println("Now is : "+body);
this.stop=true;
}else if(readBytes<0){
key.cancel();
sc.close();
}else {
//ignore
}
}
}
}
}
private void doConnect() throws IOException {
logger.info("连接>>"+host+">>"+port);
if(socketChannel.connect(new InetSocketAddress(host,port))){
socketChannel.register(selector,SelectionKey.OP_READ);
doWrite(socketChannel);
}else {
socketChannel.register(selector,SelectionKey.OP_CONNECT);
}
}
private void doWrite(SocketChannel channel) throws IOException {
logger.info("doWrite>>>>");
byte [] bytes="Hi Service O_O".getBytes();
ByteBuffer writeBuffer=ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer);
if(!writeBuffer.hasRemaining()){
logger.info("Send order 2 server succeed.");
}
}
}
3、运行结果
(1)启动服务器
四月 08, 2019 5:09:25 下午 com.xyp.iodemo.nio.server.MultiplexerTimeServer
信息: The Time server is start in port: 8085
(2)启动客户端
四月 08, 2019 5:10:19 下午 com.xyp.iodemo.nio.client.TimeHandle
信息: TimeHandle构造完毕
四月 08, 2019 5:10:19 下午 com.xyp.iodemo.nio.client.TimeHandle run
信息: run >> 开始连接
四月 08, 2019 5:10:19 下午 com.xyp.iodemo.nio.client.TimeHandle doConnect
信息: 连接>>127.0.0.1>>8085
四月 08, 2019 5:10:19 下午 com.xyp.iodemo.nio.client.TimeHandle run
信息: run >> 连接完毕
四月 08, 2019 5:10:19 下午 com.xyp.iodemo.nio.client.TimeHandle handleInput
信息: sc.isConnected>>
四月 08, 2019 5:10:19 下午 com.xyp.iodemo.nio.client.TimeHandle handleInput
信息: sc.finishConnect>>
四月 08, 2019 5:10:19 下午 com.xyp.iodemo.nio.client.TimeHandle doWrite
信息: doWrite>>>>
四月 08, 2019 5:10:19 下午 com.xyp.iodemo.nio.client.TimeHandle doWrite
信息: Send order 2 server succeed.
(3)服务器响应
四月 08, 2019 5:10:19 下午 com.xyp.iodemo.nio.server.MultiplexerTimeServer handleInput
信息: 准备监听客户端连接
四月 08, 2019 5:10:19 下午 com.xyp.iodemo.nio.server.MultiplexerTimeServer handleInput
信息: 设置监听完毕
四月 08, 2019 5:10:19 下午 com.xyp.iodemo.nio.server.MultiplexerTimeServer handleInput
信息: key.isReadable
四月 08, 2019 5:10:19 下午 com.xyp.iodemo.nio.server.MultiplexerTimeServer handleInput
信息: 异步读取客户端请求消息到缓存区>>java.nio.HeapByteBuffer[pos=0 lim=1024 cap=1024]
四月 08, 2019 5:10:19 下午 com.xyp.iodemo.nio.server.MultiplexerTimeServer handleInput
信息: The Time server receive order : Hi Service O_O
以上就是NIO的运行流程。
参考文档:
《Netty权威指南》