Netty使用
Netty使用
- Netty编写HTTP
- Netty编写HTTPS
- Netty编写WebSocket
- Netty编写序列化数据
代码: gitee仓库
1、编码器/解码器
1.1、HTTP编码器/解码器
1.2、WebSocket编码器/解码器
1.3、序列化数据编码器/解码器
-
JDK 序列化
-
JBoss Marshalling 序列化
- Protocol Buffers 序列化
2、编写例子
构建Netty例子的包
这里使用gradle
implementation 'io.netty:netty-all:4.1.107.Final'
2.1、Netty编写HTTP服务
创建一个HTTP服务,接收JSON请求,并返回JSON数据
java代码
例子1、
public class NettyHttpServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup eventGroup = new NioEventLoopGroup();
new ServerBootstrap()
.group(bossGroup,eventGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer(){
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new HttpServerCodec());
// ch.pipeline().addLast(new HttpClientCodec());
ch.pipeline().addLast(new HttpObjectAggregator(65535));
ch.pipeline().addLast(new SimpleChannelInboundHandler<FullHttpRequest>(){
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception {
// 处理请求信息,并返回json数据
String result = requestMappingHandle(fullHttpRequest);
ByteBuf byteBuf = Unpooled.wrappedBuffer(result.getBytes(StandardCharsets.UTF_8));
FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,byteBuf);
fullHttpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json;charset=UTF-8");
fullHttpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
ctx.writeAndFlush(fullHttpResponse).addListener(ChannelFutureListener.CLOSE);
}
});
}
})
.bind(8080)
;
}
private static String requestMappingHandle(FullHttpRequest fullHttpRequest) {
ByteBuf byteBuf = fullHttpRequest.content();
System.out.println("HTTP METHOD:"+fullHttpRequest.method().name());
System.out.println("HTTP URI:"+fullHttpRequest.uri());
System.out.println(HttpHeaderNames.CONTENT_TYPE+":"+fullHttpRequest.headers().get(HttpHeaderNames.CONTENT_TYPE));
System.out.println("接收JSON:\n"+byteBuf.toString(CharsetUtil.UTF_8));
return "{\"content\": \"测试 ,返回请求body\"}";
}
}
2.2、Netty编写HTTPS(SSL)服务
编写ssl服务,只需要在 channelPipline 上添加ch.pipeline().addLast(“ssl”, new SslHandler(engine)) 即可。
java代码
public class NettyHttpSSLServer {
public static void main(String[] args) throws CertificateException, SSLException {
// Configure SSL. 这里使用自签名的SSL,用来测试
final SslContext sslCtx = ServerUtil.buildSslContext();
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup eventGroup = new NioEventLoopGroup();
new ServerBootstrap()
.group(bossGroup,eventGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer(){
@Override
protected void initChannel(Channel ch) throws Exception {
SSLEngine engine = sslCtx.newEngine(ch.alloc());
ch.pipeline().addLast("ssl", new SslHandler(engine));
// ch.pipeline().addLast("ssl", sslCtx.newHandler(ByteBufAllocator.DEFAULT));
ch.pipeline().addLast(new HttpServerCodec());
ch.pipeline().addLast(new HttpObjectAggregator(65535));
ch.pipeline().addLast(new SimpleChannelInboundHandler<FullHttpRequest>(){
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception {
// 处理请求信息,并返回json数据
String result = requestMappingHandle(fullHttpRequest);
ByteBuf byteBuf = Unpooled.wrappedBuffer(result.getBytes(StandardCharsets.UTF_8));
FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,byteBuf);
fullHttpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json;charset=UTF-8");
fullHttpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
ctx.writeAndFlush(fullHttpResponse).addListener(ChannelFutureListener.CLOSE);
}
});
}
})
.bind(8080)
;
}
private static String requestMappingHandle(FullHttpRequest fullHttpRequest) {
ByteBuf byteBuf = fullHttpRequest.content();
System.out.println("HTTP METHOD:"+fullHttpRequest.method().name());
System.out.println("HTTP URI:"+fullHttpRequest.uri());
System.out.println(HttpHeaderNames.CONTENT_TYPE+":"+fullHttpRequest.headers().get(HttpHeaderNames.CONTENT_TYPE));
System.out.println("接收JSON:\n"+byteBuf.toString(CharsetUtil.UTF_8));
return "{\"content\": \"测试 ,返回请求body\"}";
}
}
/**
* 证书生成
*/
public final class ServerUtil {
// 启动的时候通过 -Dssl=true 来切换是否使用ssl
private static final boolean SSL = System.getProperty("ssl") != null;
private ServerUtil() {
}
public static SslContext buildSslContext() throws CertificateException, SSLException {
if (!SSL) {
return null;
}
// 自签名的证书
SelfSignedCertificate ssc = new SelfSignedCertificate();
return SslContextBuilder
.forServer(ssc.certificate(), ssc.privateKey())
.build();
}
}
2.3、Netty编写WebSocket服务
2.3.1、普通的websocket服务
请求地址: ws://127.0.0.1:8080/websocket
public class WebSocketServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup eventGroup = new NioEventLoopGroup();
new ServerBootstrap()
.group(bossGroup,eventGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new WebSocketServerChannelInitializer())
.bind(8080).sync()
.channel().closeFuture().sync()
;
}
}
@Slf4j
public class WebSocketServerChannelInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(
new HttpServerCodec(),
new HttpObjectAggregator(65536),
new WebSocketServerProtocolHandler("/websocket"),
new TextFrameHandler(),
new BinaryFrameHandler(),
new ContinuationFrameHandler()
);
}
private class TextFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
log.info("TextFrameHandler {}",msg.text());
}
}
private class BinaryFrameHandler extends SimpleChannelInboundHandler<BinaryWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) throws Exception {
log.info("BinaryFrameHandler {}",2);
}
}
private class ContinuationFrameHandler extends SimpleChannelInboundHandler<ContinuationWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ContinuationWebSocketFrame msg) throws Exception {
log.info("ContinuationFrameHandler {}",3);
}
}
}
2.3.2、聊天室websocket服务
请求地址: ws://127.0.0.1:8080/ws
ChannelConfigs、ChatServer、ChatServerChannelInitializer、HttpRequestHandler(http服务,跳过websocket服务)
1. ChannelConfigs.java
public class ChannelConfigs {
/**
* 定义一个channel组,管理所有的channel * GlobalEventExecutor.INSTANCE 是全局的事件执行器,是一个单例
*/
private static ChannelGroup channelGroup;
/**
* 存放用户与Chanel的对应信息,用于给指定用户发送消息
*/
private static ConcurrentHashMap<String, Channel> userChannelMap = new ConcurrentHashMap<>();
private static Object object = new Object();
/**
* 获取channel组
*/
public static ChannelGroup getChannelGroup() {
if(channelGroup==null){
synchronized (object){
if(channelGroup==null){
channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
return channelGroup;
}else{
return channelGroup;
}
}
}else{
return channelGroup;
}
}
/**
* 获取用户channel map
*/
public static ConcurrentHashMap<String, Channel> getUserChannelMap() {
return userChannelMap;
}
}
2. ChatServer.java
public class ChatServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup eventGroup = new NioEventLoopGroup();
new ServerBootstrap()
.group(bossGroup,eventGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChatServerChannelInitializer())
.bind(8080).sync()
.channel().closeFuture().sync()
;
}
}
3. ChatServerChannelInitializer.java
public class ChatServerChannelInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(
new HttpServerCodec(),
new ChunkedWriteHandler(),
new HttpObjectAggregator(65536),
new HttpRequestHandler("/ws"),
new WebSocketServerProtocolHandler("/ws"),
new TextWebSocketFrameHandler()
);
}
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { //1
private final ChannelGroup group;
public TextWebSocketFrameHandler() {
group = ChannelConfigs.getChannelGroup();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { //2
if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {
ctx.pipeline().remove(HttpRequestHandler.class); //3
group.writeAndFlush(new TextWebSocketFrame("Client " + ctx.channel() + " joined"));//4
group.add(ctx.channel()); //5
} else {
super.userEventTriggered(ctx, evt);
}
}
@Override
public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
group.writeAndFlush(msg.retain()); //6
}
}
}
4. HttpRequestHandler.java
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> { //1
private final String wsUri;
private static final File INDEX;
static {
URL location = HttpRequestHandler.class.getProtectionDomain().getCodeSource().getLocation();
try {
String path = location.toURI() + "index.html";
path = !path.contains("file:") ? path : path.substring(5);
INDEX = new File(path);
} catch (URISyntaxException e) {
throw new IllegalStateException("Unable to locate index.html", e);
}
}
public HttpRequestHandler(String wsUri) {
this.wsUri = wsUri;
}
@Override
public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
if (wsUri.equalsIgnoreCase(request.getUri())) {
ctx.fireChannelRead(request.retain()); //2
} else {
if (HttpHeaders.is100ContinueExpected(request)) {
send100Continue(ctx); //3
}
RandomAccessFile file = new RandomAccessFile(INDEX, "r");//4
HttpResponse response = new DefaultHttpResponse(request.getProtocolVersion(), HttpResponseStatus.OK);
response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html; charset=UTF-8");
boolean keepAlive = HttpHeaders.isKeepAlive(request);
if (keepAlive) { //5
response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, file.length());
response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
}
ctx.write(response); //6
if (ctx.pipeline().get(SslHandler.class) == null) { //7
ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length()));
} else {
ctx.write(new ChunkedNioFile(file.getChannel()));
}
ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); //8
if (!keepAlive) {
future.addListener(ChannelFutureListener.CLOSE); //9
}
}
}
private static void send100Continue(ChannelHandlerContext ctx) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
ctx.writeAndFlush(response);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
2.4、Netty编写序列化
2.4.1、使用Protobuf序列化
引入的包
这里使用gradle构建
implementation 'com.google.protobuf:protobuf-java:4.26.0-RC3'
implementation 'com.google.protobuf:protobuf-java-util:4.26.0-RC3'
java代码
Student.proto
//未指定则使用proto2
syntax = "proto2";
//生成 proto 文件所在包路径
package com.wxw.notes.protobuf.proto;
//生成 proto 文件所在包路径
option java_package = "bean.proto";
//生成 proto 文件名
option java_outer_classname="StudentProto";
message Student{
//自身属性
optional string name = 1;
optional int32 age = 2;
optional string classes = 3;
}
NettyProtocolBuffersServer.java 服务端
public class NettyProtocolBuffersServer {
public static void main(String[] args) throws CertificateException, SSLException, InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup eventGroup = new NioEventLoopGroup();
new ServerBootstrap()
.group(bossGroup,eventGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
Protobuf 的编码器和解码器
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(StudentProto.Student.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new SimpleChannelInboundHandler<StudentProto.Student>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, StudentProto.Student msg) throws Exception {
System.out.println("name:"+msg.getName());
System.out.println("age:"+msg.getAge());
System.out.println("classes:"+msg.getClasses());
StudentProto.Student student = StudentProto.Student.newBuilder()
.setAge(100)
.setClasses("北京市西城区")
.setName("大江东去浪淘尽")
.build();
ctx.writeAndFlush(student);
}
});
}
})
.bind(8080).sync()
.channel().closeFuture().sync()
;
// System.out.println("Server start at port : " + "8080");
}
}
NettyProtocolBuffersClient.java 客户端
public class NettyProtocolBuffersClient {
public static void main(String[] args) throws CertificateException, SSLException, InterruptedException {
startServer();
}
private static void startServer() {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class) // 这里通道是客户端通道,而不是服务端的NioServerSocketChannel
.handler(new ChannelInitializer<Channel>(){
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//Protobuf 的编码器和解码器
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(StudentProto.Student.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new SimpleChannelInboundHandler<StudentProto.Student>() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("连通后,发送消息");
StudentProto.Student student = StudentProto.Student.newBuilder()
.setAge(28)
.setClasses("高三")
.setName("张三")
.build();
ctx.channel().writeAndFlush(student);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, StudentProto.Student msg) throws Exception {
System.out.println("接收消息");
System.out.println("name:" + msg.getName());
System.out.println("age:" + msg.getAge());
System.out.println("classes:" + msg.getClasses());
ctx.channel().close().addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
// Channel关闭成功
System.out.println("Channel关闭成功");
} else {
// Channel关闭失败
System.out.println("Channel关闭失败");
}
});
}
});
}
});
//与对应的url建立连接通道
ChannelFuture channelFuture = bootstrap.connect("localhost",8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
3、tomcat与Netty对比
我们了解了Netty,他的性能更强,那么我们是否可以替代tomcat呢?
实际上tomcat也有基于NIO的优化,并且还有个APR模型,那我们何必用Netty替代tomcat呢。
4.1、tomcat
参考文章:Tomcat的BIO、NIO、APR模式对比与性能测试结果
tomcat支持三种运行模式
- BIO 阻塞式(tomcat7以下默认)
- NIO 非阻塞式(tomcat8及以上默认, springboot默认)
- APR(Apache Portable Runtime)
APR:
Tomcat将以JNI(Java Native Interface)的形式调用Apache HTTP服务器的核心动态链接库来处理文件读取或网络传输操作,从而大大提高Tomcat对静态文件的处理性能。Tomcat apr是在Tomcat上运行高并发应用的首选模式。
3.2、tomcat与Netty区别
tomcat 是使用Http协议通讯的,它是一个容器。
Netty 是一个异步事件驱动的网络应用框架,可以使用自定义协议,或者用已有的Http协议等。
相关文章