1 Netty简介
Netty是由JBOSS提供的一个java开源框架,Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序;也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用;Netty简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发;
选择Netty,原因无他,简单!编程模型固定化,再也不必去编写复杂的代码逻辑去实现通信,也不需要去考虑性能问题,不需要考虑编解码问题,半包读写问题等,这些强大的Netty已经帮我们实现好了,只需要使用即可。
Netty目前是最流行的NIO框架,其健壮性、功能、性能、可定制性和可扩展性在同类框架都是首屈一指的。它已经得到成百上千的商业/商用项目验证,如Hadoop的RPC框架Avro、以及JMS框架,强大的RocketMQ、还有主流的分布式通信框架Dubbox等等。
Netty是一个NIO client-server(客户端服务器)框架,使用Netty可以快速开发网络应用,例如服务器和客户端协议。Netty提供了一种新的方式来使开发网络应用程序,这种新的方式使得它很容易使用和有很强的扩展性。Netty的内部实现是很复杂的,但是Netty提供了简单易用的api从网络处理代码中解耦业务逻辑。Netty是完全基于NIO实现的,所以整个Netty都是异步的。
2. Netty架构图
3. Netty特性
4. hello netty
下载地址:http://netty.io/
Netty实现通信的步骤:
-1 创建两个的NIO线程组,一个专门用于网络事件处理(接受客户端的连接),另一个则进行网络通信读写
-2 创建一个ServerBootstrap对象,配置Netty的一系列参数,例如接受传出数据的缓存大小等等。
-3 创建一个实际处理数据的类ChannelInitializer,进行初始化的准备工作,比如设置接受传出数据的字符集、格式、已经实际处理数据的接口。
-4 绑定端口,执行同步阻塞方法等待服务器端启动即可。
step1:新建springboot项目、引入依赖包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cc</groupId>
<artifactId>netty-001</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>netty-001</name>
<description>netty-001</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.12.Final</version>
</dependency>
<!-- netty jboss序列化框架marshalling-->
<dependency>
<groupId>org.jboss.marshalling</groupId>
<artifactId>jboss-marshalling</artifactId>
<version>1.3.0.CR9</version>
</dependency>
<dependency>
<groupId>org.jboss.marshalling</groupId>
<artifactId>jboss-marshalling-serial</artifactId>
<version>1.3.0.CR9</version>
</dependency>
<!-- netty google序列化框架protobuf(推荐)-->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.5.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
step2:创建Server及ServerHandler
package com.cc.netty.quickstart;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class Server {
public static void main(String[] args) throws InterruptedException {
/* 1、创建两个线程组:一个用于进行网络连接的接收另一个用于数据处理(网络通信的读写)*/
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
/* 2、通过辅助类去构造server/client*/
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup) //传入两个线程组
/*option设置server参数,childOption设置socket(套接字)对象参数*/
.channel(NioServerSocketChannel.class) //创建通道(传入类底层通过clazz字节码new传入类对象【反射】)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS , 3000) //3秒超时
.option(ChannelOption.SO_BACKLOG, 1024) //sync+acept=backlog 指定队列缓存为1024
//.option(ChannelOption.SO_KEEPALIVE, false) //(是否为长连接)如果在两个小时内没有数据通信,TCP自动发送一个活动探测数据报文
//.option(ChannelOption.SO_REUSEADDR, true) //允许重复使用本地地址和端口
.childOption(ChannelOption.SO_RCVBUF, 1024 * 32) //接收区缓存为3K
.childOption(ChannelOption.SO_SNDBUF, 1024 * 32) //发送区缓存为3K
.childOption(ChannelOption.TCP_NODELAY, true) //禁用了Nagle算法,允许小数据即时传输(默认为false)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ServerHandler());
/*采用责任链模式,添加多个Handler,对数据进行层层过滤
ch.pipeline().addLast(new ServerHandler()); //1 解码数据(二进制转换为java对象)
ch.pipeline().addLast(new ServerHandler()); //2校验(检查参数是否正确,正确向下执行,错误直接返回)
ch.pipeline().addLast(new ServerHandler()); //3处理业务逻辑(处理实际业务逻辑)
*/
}
});
/*服务器端绑定端口启动服务,启动同步阻塞(启动Server,并进入同步等待,直到有链接请求,数据最终进入ServerHandler处理)
* 注意:此处阻塞为应用程序阻塞,实际工作中此方式不会同步阻塞,而且有概率应用服务会停止
*/
ChannelFuture channelFuture = serverBootstrap.bind(8765).sync();
//使用channel级别的监听close端口(调用channel().closeFuture().sync()实现连接通道阻塞)
channelFuture.channel().closeFuture().sync();
//关闭资源
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
package com.cc.netty.quickstart;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/*ServerHandler默认继承ChannelHandler,推荐实现ChannelInboundHandlerAdapter
public class ServerHandler implements ChannelHandler {
*/
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("server channel active...");
}
/**
* 真正的数据最终会走到这个方法进行处理
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//读取客户端的数据
ByteBuf buf = (ByteBuf) msg;
//使用一个byte[]数组存储客户端数据,数组大小设置为buf可读大小
byte[] request = new byte[buf.readableBytes()];
//从buf中将数据读取到request数组中
buf.readBytes(request);
String requestBody = new String(request, "utf-8");
System.err.println("Server: " + requestBody);
//返回响应数据
String responseBody = "返回响应数据" + requestBody;
//writeAndFlush内通过递归的方式不断从通道读数据,读完后关闭通道自动释放msg资源
ctx.writeAndFlush(Unpooled.copiedBuffer(responseBody.getBytes()));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.fireExceptionCaught(cause);
}
}
step3:创建Client及ClientHandler
package com.cc.netty.quickstart;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class Client {
public static void main(String[] args) throws InterruptedException {
//1. 只需要一个线程组用于我们的实际处理(网络通信的读写)
EventLoopGroup workGroup = new NioEventLoopGroup();
//2 通过辅助类去构造client
Bootstrap b = new Bootstrap();
b.group(workGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
.option(ChannelOption.SO_RCVBUF, 1024 * 32)
.option(ChannelOption.SO_SNDBUF, 1024 * 32)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ClientHandler()); //1
}
});
//客户端连接服务器端地址
ChannelFuture cf = b.connect("127.0.0.1", 8765).syncUninterruptibly();
//使用writeAndFlush将数据写入缓冲并冲刷入通道进行数据传输,应用Unpooled.copiedBuffer将字节数组转为buffer
cf.channel().writeAndFlush(Unpooled.copiedBuffer("hello netty!".getBytes()));
Thread.sleep(1000);
cf.channel().writeAndFlush(Unpooled.copiedBuffer("hello netty!".getBytes()));
//使用channel级别的监听close端口 阻塞的方式
cf.channel().closeFuture().sync();
workGroup.shutdownGracefully();
}
}
package com.cc.netty.quickstart;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
public class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("client channel active...");
}
/**
* client处理响应数据
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
//读取服务器端返回数据
ByteBuf buf = (ByteBuf) msg;
byte[] request = new byte[buf.readableBytes()];
buf.readBytes(request);
String requestBody = new String(request, "utf-8");
System.err.println("Clent: " + requestBody);
//读取返回数据后可以继续回写,此处做只读模拟
} finally {
//只读必须关闭msg
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.fireExceptionCaught(cause);
}
}
step4:启动Server和Client