nio原理之netty常用API详解

一、nio简介

         nio 是New IO 的简称,在jdk1.4 里提供的新api 。Sun 官方标榜的特性如下:

1、为所有的原始类型提供(Buffer)缓存支持。

2、字符集编码解码解决方案。 Channel :一个新的原始I/O 抽象。

3、支持锁和内存映射文件的文件访问接口。

4、提供多路(non-bloking) 非阻塞式的高伸缩性网络I/O 。

 

1.1.传统的I/O

使用传统的I/O程序读取文件内容, 并写入到另一个文件(或Socket), 如下程序:

File.read(fileDesc, buf, len);

Socket.send(socket, buf, len);

会有较大的性能开销, 主要表现在一下两方面:

1. 上下文切换(context switch), 此处有4次用户态和内核态的切换

2. Buffer内存开销, 一个是应用程序buffer, 另一个是系统读取buffer以及socket buffer

其运行示意图如下

nio原理之netty常用API详解_第1张图片

DMA(Direct Memory Access,直接内存存取) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。

  • 1) 先将文件内容从磁盘中拷贝到操作系统buffer
  • 2) 再从操作系统buffer拷贝到程序应用buffer
  • 3) 从程序buffer拷贝到socket buffer
  • 4) 从socket buffer拷贝到协议引擎.

代码示例:

1.1.1服务端代码

package cn.itcast_02_nio;

import java.io.DataInputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TraditionalServer {

	@SuppressWarnings("resource")
	public static void main(String args[]) throws Exception {
		// 监听端口
		ServerSocket server_socket = new ServerSocket(2000);
		System.out.println("等待,端口为:" + server_socket.getLocalPort());

		while (true) {
			// 阻塞接受消息
			Socket socket = server_socket.accept();
			// 打印链接信息
			System.out.println("新连接: " + socket.getInetAddress() + ":"
					+ socket.getPort());
			// 从socket中获取流
			DataInputStream input = new DataInputStream(socket.getInputStream());
			// 接收数据
			byte[] byteArray = new byte[4096];
			while (true) {
				int nread = input.read(byteArray, 0, 4096);
				System.out.println(new String(byteArray, "UTF-8"));
				if (-1 == nread) {
					break;
				}

			}
			socket.close();
			System.out.println("Connection closed by client");

		}
	}
}

1.1.2客户端端代码

package cn.itcast_02_nio;

import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.net.Socket;

public class TraditionalClient {

	public static void main(String[] args) throws Exception {
		long start = System.currentTimeMillis();
		// 创建socket链接
		Socket socket = new Socket("localhost", 2000);
		System.out.println("Connected with server " + socket.getInetAddress()
				+ ":" + socket.getPort());
		// 读取文件
		FileInputStream inputStream = new FileInputStream("C:/sss.txt");
		// 输出文件
		DataOutputStream output = new DataOutputStream(socket.getOutputStream());
		// 缓冲区4096K
		byte[] b = new byte[4096];
		// 传输长度
		long read = 0, total = 0;
		// 读取文件,写到socketio中
		while ((read = inputStream.read(b)) >= 0) {
			total = total + read;
			output.write(b);
		}
		// 关闭
		output.close();
		socket.close();
		inputStream.close();
		// 打印时间
		System.out.println("bytes send--" + total + " and totaltime--"
				+ (System.currentTimeMillis() - start));
	}
}

1.2.NIO原理

NIO技术省去了将操作系统的read buffer拷贝到程序的buffer, 以及从程序buffer拷贝到socket buffer的步骤, 直接将 read buffer 拷贝到 socket buffer. java 的 FileChannel.transferTo() 方法就是这样的实现, 这个实现是依赖于操作系统底层的sendFile()实现的.

Public void transferTo(long position, long count, WritableByteChannel target);

他的底层调用的是系统调用sendFile()方法

sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

如下图

 nio原理之netty常用API详解_第2张图片   

理解零拷贝: 从WIKI的定义中,我们看到“零拷贝”是指计算机操作的过程中,CPU不需要为数据在内存之间的拷贝消耗资源。而它通常是指计算机在网络上发送文件时,不需要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space
)中传输到网络的方式。 

nio原理之netty常用API详解_第3张图片

1.2.1.服务端代码

package cn.itcast_02_nio;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;

public class TransferToClient {

	@SuppressWarnings("resource")
	public static void main(String[] args) throws IOException {
		long start = System.currentTimeMillis();
		// 打开socket的nio管道
		SocketChannel sc = SocketChannel.open();
		sc.connect(new InetSocketAddress("localhost", 9026));// 绑定相应的ip和端口
		sc.configureBlocking(true);// 设置阻塞
		// 将文件放到channel中
		FileChannel fc = new FileInputStream("C:/sss.txt").getChannel();// 打开文件管道
		//做好标记量
		long size = fc.size();
		int pos = 0;
		int offset = 4096;
		long curnset = 0;
		long counts = 0;
		//循环写
		while (pos

1.2.2.客户端代码

package cn.itcast_02_nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class TransferToServer {

	public static void main(String[] args) throws IOException {
		// 创建socket channel
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		ServerSocket ss = serverSocketChannel.socket();
		ss.setReuseAddress(true);// 地址重用
		ss.bind(new InetSocketAddress("localhost", 9026));// 绑定地址
		System.out.println("监听端口 : "
				+ new InetSocketAddress("localhost", 9026).toString());

		// 分配一个新的字节缓冲区
		ByteBuffer dst = ByteBuffer.allocate(4096);
		// 读取数据
		while (true) {
			SocketChannel channle = serverSocketChannel.accept();// 接收数据
			System.out.println("Accepted : " + channle);
			channle.configureBlocking(true);// 设置阻塞,接不到就停
			int nread = 0;
			while (nread != -1) {
				try {
					nread = channle.read(dst);// 往缓冲区里读
					byte[] array = dst.array();//将数据转换为array
					//打印
					String string = new String(array, 0, dst.position());
					System.out.print(string);
					dst.clear();
				} catch (IOException e) {
					e.printStackTrace();
					nread = -1;
				}
			}
		}
	}
}

​​​​​​

二、netty常用API学习

2.1.netty简介

         Netty是基于Java NIO的网络应用框架.

    Netty是一个NIO client-server(客户端服务器)框架,使用Netty可以快速开发网络应用,例如服务器和客户端协议。Netty提供了一种新的方式来使开发网络应用程序,这种新的方式使得它很容易使用和有很强的扩展性。Netty的内部实现时很复杂的,但是Netty提供了简单易用的api从网络处理代码中解耦业务逻辑。Netty是完全基于NIO实现的,所以整个Netty都是异步的。

        网络应用程序通常需要有较高的可扩展性,无论是Netty还是其他的基于Java NIO的框架,都会提供可扩展性的解决方案。Netty中一个关键组成部分是它的异步特性.

•       下载netty包,下载地址http://netty.io/

2.2.代码示例

2.2.1.服务端启动类:

package cn.itcast_03_netty.sendstring.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import cn.itcast_03_netty.sendobject.coder.PersonDecoder;

/**
 * • 配置服务器功能,如线程、端口 • 实现服务器处理程序,它包含业务逻辑,决定当有一个请求连接或接收数据时该做什么
 * 
 * @author wilson
 *
 */
public class EchoServer {

	private final int port;

	public EchoServer(int port) {
		this.port = port;
	}

	public void start() throws Exception {
		EventLoopGroup eventLoopGroup = null;
		try {
			//server端引导类
			ServerBootstrap serverBootstrap = new ServerBootstrap();
			//连接池处理数据
			eventLoopGroup = new NioEventLoopGroup();
			//装配bootstrap
			serverBootstrap.group(eventLoopGroup)
			.channel(NioServerSocketChannel.class)//指定通道类型为NioServerSocketChannel,一种异步模式,OIO阻塞模式为OioServerSocketChannel
			.localAddress("localhost",port)//设置InetSocketAddress让服务器监听某个端口已等待客户端连接。
			.childHandler(new ChannelInitializer() {//设置childHandler执行所有的连接请求
				@Override
				protected void initChannel(Channel ch) throws Exception {
					ch.pipeline().addLast(new EchoServerHandler());//注册handler
				}
					});
			// 最后绑定服务器等待直到绑定完成,调用sync()方法会阻塞直到服务器完成绑定,然后服务器等待通道关闭,因为使用sync(),所以关闭操作也会被阻塞。
			ChannelFuture channelFuture = serverBootstrap.bind().sync();
			System.out.println("开始监听,端口为:" + channelFuture.channel().localAddress());
			channelFuture.channel().closeFuture().sync();
		} finally {
			eventLoopGroup.shutdownGracefully().sync();
		}
	}

	public static void main(String[] args) throws Exception {
		new EchoServer(20000).start();
	}
}

​​​​​​​2.2.2.服务端回调方法:

package cn.itcast_03_netty.sendstring.server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.Date;

import cn.itcast_03_netty.sendobject.bean.Person;

public class EchoServerHandler extends ChannelInboundHandlerAdapter {

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg)
			throws Exception {
		System.out.println("server 读取数据……");
		//读取数据
        ByteBuf buf = (ByteBuf) msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req, "UTF-8");
        System.out.println("接收客户端数据:" + body);
        //向客户端写数据
        System.out.println("server向client发送数据");
        String currentTime = new Date(System.currentTimeMillis()).toString();
        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
        ctx.write(resp);
        
	}

	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		System.out.println("server 读取数据完毕..");
        ctx.flush();//刷新后才将数据发出到SocketChannel
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
			throws Exception {
		cause.printStackTrace();
		ctx.close();
	}

}

​​​​​​​​​​​​​​2.2.3.客户端启动类:

package cn.itcast_03_netty.sendstring.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.net.InetSocketAddress;

import cn.itcast_03_netty.sendobject.coder.PersonEncoder;

/**
 * • 连接服务器 • 写数据到服务器 • 等待接受服务器返回相同的数据 • 关闭连接
 * 
 * @author wilson
 *
 */
public class EchoClient {

	private final String host;
	private final int port;

	public EchoClient(String host, int port) {
		this.host = host;
		this.port = port;
	}

	public void start() throws Exception {
		EventLoopGroup nioEventLoopGroup = null;
		try {
			// 客户端引导类
			Bootstrap bootstrap = new Bootstrap();
			// EventLoopGroup可以理解为是一个线程池,这个线程池用来处理连接、接受数据、发送数据
			nioEventLoopGroup = new NioEventLoopGroup();
			bootstrap.group(nioEventLoopGroup)//多线程处理
					.channel(NioSocketChannel.class)//指定通道类型为NioServerSocketChannel,一种异步模式,OIO阻塞模式为OioServerSocketChannel
					.remoteAddress(new InetSocketAddress(host, port))//地址
					.handler(new ChannelInitializer() {//业务处理类
								@Override
								protected void initChannel(SocketChannel ch)
										throws Exception {
									ch.pipeline().addLast(new EchoClientHandler());//注册handler
								}
							});
			// 链接服务器
			ChannelFuture channelFuture = bootstrap.connect().sync();
			channelFuture.channel().closeFuture().sync();
		} finally {
			nioEventLoopGroup.shutdownGracefully().sync();
		}
	}

	public static void main(String[] args) throws Exception {
		new EchoClient("localhost", 20000).start();
	}
}

​​​​​​​​​​​​​​2.2.4.客户端回调方法:

package cn.itcast_03_netty.sendstring.client;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import cn.itcast_03_netty.sendobject.bean.Person;

public class EchoClientHandler extends SimpleChannelInboundHandler {
	// 客户端连接服务器后被调用
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		System.out.println("客户端连接服务器,开始发送数据……");
		byte[] req = "QUERY TIME ORDER".getBytes();//消息
		ByteBuf firstMessage = Unpooled.buffer(req.length);//发送类
		firstMessage.writeBytes(req);//发送
		ctx.writeAndFlush(firstMessage);//flush
	}

	// • 从服务器接收到数据后调用
	@Override
	protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg)
			throws Exception {
		System.out.println("client 读取server数据..");
		// 服务端返回消息后
		ByteBuf buf = (ByteBuf) msg;
		byte[] req = new byte[buf.readableBytes()];
		buf.readBytes(req);
		String body = new String(req, "UTF-8");
		System.out.println("服务端数据为 :" + body);
	}

	// • 发生异常时被调用
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
			throws Exception {
		System.out.println("client exceptionCaught..");
		// 释放资源
		ctx.close();
	}
}

2.2.3.nettyhandler的执行顺序

  1. 简介

Handler在netty中,无疑占据着非常重要的地位。Handler与Servlet中的filter很像,通过Handler可以完成通讯报文的解码编码、拦截指定的报文、统一对日志错误进行处理、统一对请求进行计数、控制Handler执行与否。一句话,没有它做不到的只有你想不到的。

Netty中的所有handler都实现自ChannelHandler接口。按照输出输出来分,分为ChannelInboundHandler、ChannelOutboundHandler两大类。ChannelInboundHandler对从客户端发往服务器的报文进行处理,一般用来执行解码、读取客户端数据、进行业务处理等;ChannelOutboundHandler对从服务器发往客户端的报文进行处理,一般用来进行编码、发送报文到客户端。

Netty中,可以注册多个handler。ChannelInboundHandler按照注册的先后顺序执行;ChannelOutboundHandler按照注册的先后顺序逆序执行,如下图所示,按照注册的先后顺序对Handler进行排序,request进入Netty后的执行顺序为:

nio原理之netty常用API详解_第4张图片

总结

 

在使用Handler的过程中,需要注意:

1、ChannelInboundHandler之间的传递,通过调用 ctx.fireChannelRead(msg) 实现;调用ctx.write(msg) 将传递到ChannelOutboundHandler。

2、ctx.write()方法执行后,需要调用flush()方法才能令它立即执行。

3、流水线pipeline中outhandler不能放在最后,否则不生效

4、Handler的消费处理放在最后一个处理。

2.2.4.netty发送对象

简介

Netty中,通讯的双方建立连接后,会把数据按照ByteBuf的方式进行传输,例如http协议中,就是通过HttpRequestDecoder对ByteBuf数据流进行处理,转换成http的对象。基于这个思路,我自定义一种通讯协议:Server和客户端直接传输java对象。

实现的原理是通过Encoder把java对象转换成ByteBuf流进行传输,通过Decoder把ByteBuf转换成java对象进行处理,处理逻辑如下图所示:

.nio原理之netty常用API详解_第5张图片

代码示例:https://download.csdn.net/download/lixinkuan328/10940886​​nio原理之netty常用API详解_第6张图片

你可能感兴趣的:(Java,JUC多线程)