Netty基本介绍及使用
中国加油,武汉加油!
篇幅较长,请配合目录观看
项目准备
- 本案例基于 扩展篇】二. NIO实现通讯
1. Netty是什么
- JBoss提供的一个java开源框架。
- 不需要运行在Tomcat之类的服务器,是单独构建的服务器。
- 可以构建HTTP服务器,socket服务器,websocket服务器。
- 对JDK1.4以后提供的NIO的封装。
- 是一款异步的事件驱动的网络应用程序框架,支持快速开发可维护的高性能面向协议服务器和客户端,底层利用了NIO的机制
- 解决了io操作的繁琐,性能不保证的问题,便于开发者将精力集中在业务上,对开发者透明化,大大降低了开发门槛。
2. Netty中的重要组件
组件 | 含义 | 详情 |
---|---|---|
Bootstrap/ServerBootstrap | 建立连接 | Netty引导组件,简化了NIO的开发步骤,是一个Netty的程序的开始,作用是配置和串联各个组件 |
EventLoopGroup | 事件循环组 | 是EvenLoop组合,可以包含多个EventLoop。创建EventLoopGroup的时候,内部包含的方法就会创建一个子对象EventLoop |
EventLoop | 事件循环 | 循环服务Channel,可以包含多个Channel |
Channel | 通道 | 代表一个Scoket连接,或者其他IO操作组件。是通讯的载体 |
ChannelInitalizer | 初始化连接 | 主要提供了一个传输通道Piepeline |
ChannelPiepeline | 传输通道 | 主要是管理各种ChannelHandler业务控制器,提供了一个链式管理模式 |
ChannelHandler | 业务控制器 | 主要业务写入的地方,用开发人员写入,Netty也提供了很多写好的控制器和适配器,可以直接引用 |
ChannelInboundHandler | 通道传入控制器 | 继承至ChannelHandler,在传输通道中对传入事间进行控制 |
ChannelOutboundHandler | 通道传出控制器 | 继承至ChannelHandler,在传输通道中对传出事件进行控制 |
Decoder | 解码 | 网络传输都是byte传输,所以Netty首先接收到的是byte,需要进行解码,编成java对象,Netty提供了很多解码器 |
Encoder | 编码 | 和解码类似,在传出服务器的时候,需要编码成byte传输给客户端 |
Future/ChannelFuture | 消息返回 | Netty提供的返回结果,类似回调函数,告知你执行的结果是什么 |
3. Netty线程模型*
模型 | 含义 |
---|---|
单线程模型 | 所有的IO操作都在同一个NIO线程上完成,一个线程纪要处理客户端连接还有处理客户端读写操作。 |
多线程模型 | 与单线程模型对比,最大的区别就是一组NIO线程(线程池)处理IO操作。 |
主从线程模型 | 服务端有2个线程池,主线程池主要处理客户端的连接,从线程池处理客户端的读写。 |
4. Netty实现通讯
4.1 新建netty-demo(maven)
4.2 导包
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.48.Final</version>
</dependency>
4.3 编写ServerSocketHandler
package com.wpj.hello;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.nio.charset.Charset;
public class ServerSocketHandler extends SimpleChannelInboundHandler<ByteBuf> {
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
System.out.println("服务端读取的内容: " + byteBuf.toString(Charset.defaultCharset()));
}
}
4.4 编写ServerDemo
package com.wpj.hello;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* Netty实现服务端
*/
public class ServerDemo {
public static void main(String[] args) {
try {
// 创建2个线程池(主,从)
EventLoopGroup master = new NioEventLoopGroup(); // 主要负责连接
EventLoopGroup slave = new NioEventLoopGroup(); // 主要负责读写
// 创建引导类
ServerBootstrap serverBootstrap = new ServerBootstrap();
// serverBootstrap.group(); // 单线程模型
// serverBootstrap.group(master); // 多线程模型
serverBootstrap.group(master, slave);
// 设置通道的类型
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childHandler(new ServerSocketHandler());
// 绑定端口号是用异步来完成的
ChannelFuture channelFuture = serverBootstrap.bind(8080);// 设置服务端端口号
channelFuture.sync(); // 阻塞住
System.out.println("服务端启动成功。。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.5 编写ClientDemo
package com.wpj.hello;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;
public class ClientDemo {
public static void main(String[] args) {
try {
// 创建一个线程池
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
// 创建一个引导类
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup); // 客户端这边用多线程模型处理
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new SimpleChannelInboundHandler<ByteBuf>() {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
System.out.println("读取服务端响应的内容:"+byteBuf.toString(Charset.defaultCharset()));
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 8080);
channelFuture.sync();
System.out.println("客户端连接成功。。。");
// 创建一个ByteBuf对象
ByteBuf byteBuf = Unpooled.buffer(1024 * 10);
byteBuf.writeBytes("hello".getBytes("utf-8"));
channelFuture.channel().writeAndFlush(byteBuf);
System.out.println("客户端写入完成。。。。");
// 给服务端写入数据
} catch (Exception e) {
e.printStackTrace();
}
}
}
5. 解编码器
5.1 编写SocketChannelHandlerString
package com.wpj.channelpipeline;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class SocketChannelHandlerString extends SimpleChannelInboundHandler<String>{
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String str) throws Exception {
System.out.println("服务端读取的内容:"+str);
}
}
5.2 定义服务端
package com.wpj.channelpipeline;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class ServerDemo {
public static void main(String[] args) {
EventLoopGroup master = new NioEventLoopGroup();
EventLoopGroup salve = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(master,salve);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
// 添加字符串类型的解编码器
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
// 添加自己的处理器
pipeline.addLast(new SocketChannelHandlerString());
}
});
ChannelFuture channelFuture = bootstrap.bind(8080);
channelFuture.sync();
System.out.println("服务端启动成功");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
5.3 定义客户端
package com.wpj.channelpipeline;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;
public class ClientDemo {
public static void main(String[] args) {
try {
// 创建一个线程池
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
// 创建一个引导类
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup); // 客户端这边用多线程模型处理
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new SimpleChannelInboundHandler<String>(){
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
System.out.println("ClientDemo.channelRead0");
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 8080);
channelFuture.sync();
System.out.println("客户端连接成功。。。");
Scanner scanner = new Scanner(System.in);
while(true){
ByteBuf byteBuf = Unpooled.buffer(1024 * 10);
String next = scanner.next();
byteBuf.writeBytes(next.getBytes("utf-8"));
channelFuture.channel().writeAndFlush(byteBuf); // 异步的操作
System.out.println("客户端写入完成。。。。");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
6. Http解编码器
6.1 定义SocketChannelHandlerHttpRequest
package com.wpj.http;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
public class SocketChannelHandlerHttpRequest extends SimpleChannelInboundHandler<FullHttpRequest>{
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest req) throws Exception {
System.out.println("uri:"+req.uri());
System.out.println("method:"+req.method().name());
HttpHeaders headers = req.headers();
List<Map.Entry<String, String>> entries = headers.entries();
for(Map.Entry<String, String> ent:entries){
String key = ent.getKey();
String value = ent.getValue();
System.out.println("--"+key+":"+value);
}
ByteBuf content = req.content();
System.out.println(content.toString(Charset.defaultCharset()));
// 响应给浏览器
Channel channel = channelHandlerContext.channel();
FullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK);
// 响应浏览器的内容
String html ="<html><body><h1>Hello World</h1></body></html>";
// 把html内容写入到resp对象里面
resp.content().writeBytes(html.getBytes("utf-8"));
// 输出resp对象
channel.writeAndFlush(resp);
}
}
6.2 定义服务端
package com.wpj.http;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
public class ServerDemo {
public static void main(String[] args) {
EventLoopGroup master = new NioEventLoopGroup();
EventLoopGroup salve = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(master,salve);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
// 添加Http解编码器
pipeline.addLast(new HttpServerCodec()); // -- HttpRqeuest
pipeline.addLast(new HttpObjectAggregator(1024*10)); // -->FullHttpRequest
pipeline.addLast(new SocketChannelHandlerHttpRequest());
}
});
ChannelFuture channelFuture = bootstrap.bind(8080);
channelFuture.sync();
System.out.println("服务端启动成功");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
6.3 Test
通过浏览器或者PostMan去测试
7. Nttey实现Ftp服务器
7.1 服务端
package com.wpj.ftp;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
public class FtpServer {
public static void main(String[] args) {
try {
EventLoopGroup master = new NioEventLoopGroup();
EventLoopGroup slave= new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(master,slave);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new HttpServerCodec()); // 解码HttpRequest
pipeline.addLast(new HttpObjectAggregator(1024*10)); // 加密FullHttpRequset
pipeline.addLast(new SocketChannelHandler());
}
});
ChannelFuture channelFuture= bootstrap.bind(8080);
channelFuture.sync();
System.out.println("服务端启动成功。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
7.2 编写SocketChannelHandler
package com.wpj.ftp;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class SocketChannelHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
String path = "E:\\codeDevelop\\ideaDevelop\\springboot\\nz1904-shop";
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest request) throws Exception {
String name = request.method().name();
System.out.println(name);
// /abc
String uri = request.uri();
if(!"GET".equals(name)){
respError(channelHandlerContext,"只支持GET类型");
return ;
}
File file = new File(path+uri);
if(!file.exists()){
respError(channelHandlerContext,"文件不存在");
return ;
}
// 判断file是文件还是目录
if(file.isDirectory()){
showDirectory(channelHandlerContext,file,uri);
}else{
System.out.println("文件:"+file);
showDirectoryFile(channelHandlerContext,file);
}
}
private void showDirectoryFile(ChannelHandlerContext channelHandlerContext, File file) {
try {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 10);
FileChannel fileChannel = new FileInputStream(file.getAbsolutePath()).getChannel();
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("<html>");
while(fileChannel.read(byteBuffer) != -1){
byteBuffer.flip();
stringBuffer.append(new String(byteBuffer.array()));
byteBuffer.clear();
}
stringBuffer.append("</html>");
respContent(channelHandlerContext,stringBuffer.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void showDirectory(ChannelHandlerContext channelHandlerContext, File file,String uri) {
File[] files = file.listFiles(); // 获取目录下面子文件
String html ="<html>";
for(int i =0;i<files.length;i++){
if("/".equals(uri)){
html+="<li><a href ='"+uri+files[i].getName()+"'>"+files[i].getName()+"</a></li>";
}else{
html+="<li><a href ='"+uri+File.separator+files[i].getName()+"'>"+files[i].getName()+"</a></li>";
}
}
html+="</html>";
respContent(channelHandlerContext,html);
}
/**
* 下载文件
* 1.设置响应头(stream)
* a)流类型
* b)文件件的名称
* 2。把文件写到resp中
* @param channelHandlerContext
* @param content
*/
public void respError(ChannelHandlerContext channelHandlerContext,String content){
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
response.headers().add("ContextType","text/html;charset=utf8");
String html = "<html><head><meta charset=\"utf-8\"></head> <h1>"+content+"</h1></html>";
try {
response.content().writeBytes(html.getBytes("utf-8"));
channelHandlerContext.channel().writeAndFlush(response);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
public void respContent(ChannelHandlerContext channelHandlerContext,String conent){
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
response.headers().add("ContextType","text/html;charset=utf8");
try {
response.content().writeBytes(conent.getBytes("utf-8"));
channelHandlerContext.channel().writeAndFlush(response);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}