Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
简单来说,Netty是一个对java nio的封装,用于快速简单的开发高性能网络应用程序的工具。
参考资料: netty官网: http://netty.io/
权威书籍:《Netty In Action》 (https://download.csdn.net/download/weixin_37068368/10377062)
官方样例:https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example
参考博客:https://blog.csdn.net/h348592532/article/details/52816148#
样例代码github地址:https://github.com/WeedOutWorld/techDemo/tree/master
配置服务端与客户端,自定义协议。配置完成项目后,先启动服务端EchoServer.java,再启动客户端EchoClient.java
工程结构:
maven依赖:
io.netty
netty-all
4.1.10.Final
Netty服务端主要有两部分构成:服务端配置启动类,业务逻辑处理类(异步回调接口)
服务端配置启动类:
package org.ych.techDemo.netty.nettyServer;
import org.ych.techDemo.netty.encoder.MessageDecoder;
import org.ych.techDemo.netty.encoder.MessageEncoder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
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.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
/**
* Echoes back any received data from a client.
*/
public final class EchoServer {
static final boolean SSL = System.getProperty("ssl") != null;
static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
public static void main(String[] args) throws Exception {
System.out.println("EchoServer.main start");
// Configure SSL.
final SslContext sslCtx;
if (SSL) {
SelfSignedCertificate ssc = new SelfSignedCertificate();
sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
} else {
sslCtx = null;
}
// Configure the server.
/*步骤
* 创建一个ServerBootstrap b实例用来配置启动服务器
* b.group指定NioEventLoopGroup来接收处理新连接
* b.channel指定通道类型
* b.option设置一些参数
* b.handler设置日志记录
* b.childHandler指定连接请求,后续调用的channelHandler
* b.bind设置绑定的端口
* b.sync阻塞直至启动服务
*/
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
//.option(ChannelOption.SO_BACKLOG, 100)
//.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
p.addLast(new MessageDecoder());
p.addLast(new MessageEncoder());
//p.addLast(new LoggingHandler(LogLevel.INFO));
//p.addLast("encoder", new MessageEncoder());
//p.addLast("decoder", new MessageDecoder());
//p.addFirst(new LineBasedFrameDecoder(65535));
p.addLast(new EchoServerHandler());
}
});
// Start the server.
ChannelFuture f = b.bind(PORT).sync();
System.out.println("EchoServer.main ServerBootstrap配置启动完成");
// Wait until the server socket is closed.
f.channel().closeFuture().sync();
System.out.println("EchoServer.main end");
} finally {
// Shut down all event loops to terminate all threads.
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
服务端业务逻辑处理类:
package org.ych.techDemo.netty.nettyServer;
import java.io.UnsupportedEncodingException;
import org.ych.techDemo.netty.encoder.Header;
import org.ych.techDemo.netty.encoder.Message;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/**
* Handler implementation for the echo server.
*
*/
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
//接收请求后的处理类
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Message msg1=(Message)msg;
System.out.println(msg1.getData());
//此处写接收到客户端请求后的业务逻辑
String content="hello world,this is nettyServer";
Header header=new Header((byte)0, (byte)1, (byte)1, (byte)1, (byte)0, "713f17ca614361fb257dc6741332caf2",content.getBytes("UTF-8").length, 1);
Message message=new Message(header,content);
ctx.writeAndFlush(message);
//ctx.writeAndFlush(Unpooled.copiedBuffer("hello world,this is nettyServer",CharsetUtil.UTF_8));
}
//读取完成后处理方法
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
System.out.println("EchoServerHandler.channelReadComplete");
//ctx.flush();
}
//异常捕获处理方法
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
客户端启动配置类:
package org.ych.techDemo.netty.nettyClient;
import org.ych.techDemo.netty.encoder.MessageDecoder;
import org.ych.techDemo.netty.encoder.MessageEncoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
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 io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
/**
* Sends one message when a connection is open and echoes back any received
* data to the server. Simply put, the echo client initiates the ping-pong
* traffic between the echo client and server by sending the first message to
* the server.
*/
public final class EchoClient {
static final boolean SSL = System.getProperty("ssl") != null;
static final String HOST = System.getProperty("host", "127.0.0.1");
static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));
public static void main(String[] args) throws Exception {
System.out.println("EchoClient.main");
// Configure SSL.git
final SslContext sslCtx;
if (SSL) {
sslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE).build();
} else {
sslCtx = null;
}
// Configure the client.
/*创建一个Bootstrap b实例用来配置启动客户端
* b.group指定NioEventLoopGroup来处理连接,接收数据
* b.channel指定通道类型
* b.option配置参数
* b.handler客户端成功连接服务器后就会执行
* b.connect客户端连接服务器
* b.sync阻塞配置完成并启动
*/
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
//.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
}
//p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(new MessageDecoder());
p.addLast(new MessageEncoder());
//p.addLast("encoder", new MessageEncoder());
//p.addLast("decoder", new MessageDecoder());
//p.addFirst(new LineBasedFrameDecoder(65535));
p.addLast(new EchoClientHandler());
}
});
// Start the client.
ChannelFuture f = b.connect(HOST, PORT).sync();
System.out.println("EchoClient.main ServerBootstrap配置启动完成");
// Wait until the connection is closed.
f.channel().closeFuture().sync();
System.out.println("EchoClient.end");
} finally {
// Shut down the event loop to terminate all threads.
group.shutdownGracefully();
}
}
}
客户端业务逻辑处理类
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.ych.techDemo.netty.nettyClient;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import org.ych.techDemo.netty.encoder.Header;
import org.ych.techDemo.netty.encoder.Message;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/**
* Handler implementation for the echo client. It initiates the ping-pong
* traffic between the echo client and server by sending the first message to
* the server.
*/
public class EchoClientHandler extends ChannelInboundHandlerAdapter {
// private final ByteBuf firstMessage;
/**
* Creates a client-side handler.
* @throws Exception
*/
//客户端连接服务器后调用,简单的就ctx.writeAndFlush(ByteBuf),复杂点就自定义编解码器
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive");
//ctx.writeAndFlush(Unpooled.copiedBuffer("hello world",CharsetUtil.UTF_8));
String content="hello world,this is netty client";
Header header=new Header((byte)0, (byte)1, (byte)1, (byte)1, (byte)0, "713f17ca614361fb257dc6741332caf2",content.getBytes("UTF-8").length, 1);
Message message=new Message(header,content);
ctx.writeAndFlush(message);
}
//接收到数据后调用
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
Message msg1=(Message)msg;
System.out.println(msg1.getData());
}
//完成时调用
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
System.out.println("channelReadComplete");
ctx.flush();
}
//发生异常时调用
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
Header.java
package org.ych.techDemo.netty.encoder;
/**
* Header.java
* 自定义协议包头
*/
public class Header {
private byte tag;
/* 编码*/
private byte encode;
/*加密*/
private byte encrypt;
/*其他字段*/
private byte extend1;
/*其他2*/
private byte extend2;
/*会话id*/
private String sessionid;
/*包的长度*/
private int length = 1024;
/*命令*/
private int cammand;
public Header() {
}
public Header(String sessionid) {
this.encode = 0;
this.encrypt = 0;
this.sessionid = sessionid;
}
public Header(byte tag, byte encode, byte encrypt, byte extend1, byte extend2, String sessionid, int length, int cammand) {
this.tag = tag;
this.encode = encode;
this.encrypt = encrypt;
this.extend1 = extend1;
this.extend2 = extend2;
this.sessionid = sessionid;
this.length = length;
this.cammand = cammand;
}
@Override
public String toString() {
return "header [tag=" + tag + "encode=" + encode + ",encrypt=" + encrypt + ",extend1=" + extend1 + ",extend2=" + extend2 + ",sessionid=" + sessionid + ",length=" + length + ",cammand="
+ cammand + "]";
}
public byte getTag() {
return tag;
}
public void setTag(byte tag) {
this.tag = tag;
}
public byte getEncode() {
return encode;
}
public void setEncode(byte encode) {
this.encode = encode;
}
public byte getEncrypt() {
return encrypt;
}
public void setEncrypt(byte encrypt) {
this.encrypt = encrypt;
}
public byte getExtend1() {
return extend1;
}
public void setExtend1(byte extend1) {
this.extend1 = extend1;
}
public byte getExtend2() {
return extend2;
}
public void setExtend2(byte extend2) {
this.extend2 = extend2;
}
public String getSessionid() {
return sessionid;
}
public void setSessionid(String sessionid) {
this.sessionid = sessionid;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getCammand() {
return cammand;
}
public void setCammand(int cammand) {
this.cammand = cammand;
}
}
Message.java
package org.ych.techDemo.netty.encoder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
/**
* Message.java
*/
public class Message {
private Header header;
private String data;
public Header getHeader() {
return header;
}
public void setHeader(Header header) {
this.header = header;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public Message(Header header) {
this.header = header;
}
public Message(Header header, String data) {
this.header = header;
this.data = data;
}
public byte[] toByte() {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(MessageDecoder.PACKAGE_TAG);
out.write(header.getEncode());
out.write(header.getEncrypt());
out.write(header.getExtend1());
out.write(header.getExtend2());
byte[] bb = new byte[32];
byte[] bb2 = header.getSessionid().getBytes();
for (int i = 0; i < bb2.length; i++) {
bb[i] = bb2[i];
}
try {
out.write(bb);
byte[] bbb = data.getBytes("UTF-8");
out.write(intToBytes2(bbb.length));
out.write(intToBytes2(header.getCammand()));
out.write(bbb);
out.write('\n');
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return out.toByteArray();
}
public static byte[] intToByte(int newint) {
byte[] intbyte = new byte[4];
intbyte[3] = (byte) ((newint >> 24) & 0xFF);
intbyte[2] = (byte) ((newint >> 16) & 0xFF);
intbyte[1] = (byte) ((newint >> 8) & 0xFF);
intbyte[0] = (byte) (newint & 0xFF);
return intbyte;
}
public static int bytesToInt(byte[] src, int offset) {
int value;
value = (int) ((src[offset] & 0xFF) | ((src[offset + 1] & 0xFF) << 8) | ((src[offset + 2] & 0xFF) << 16) | ((src[offset + 3] & 0xFF) << 24));
return value;
}
public static byte[] intToBytes2(int value) {
byte[] src = new byte[4];
src[0] = (byte) ((value >> 24) & 0xFF);
src[1] = (byte) ((value >> 16) & 0xFF);
src[2] = (byte) ((value >> 8) & 0xFF);
src[3] = (byte) (value & 0xFF);
return src;
}
public static void main(String[] args) {
ByteBuf heapBuffer = Unpooled.buffer(8);
System.out.println(heapBuffer);
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
out.write(intToBytes2(2));
} catch (IOException e) {
e.printStackTrace();
}
byte[] data = out.toByteArray();
heapBuffer.writeBytes(data);
System.out.println(heapBuffer);
int a = heapBuffer.readInt();
System.out.println(a);
}
}
MessageDecoder.java
package org.ych.techDemo.netty.encoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
import java.util.List;
/**
* HeaderDecoder.java
*
*/
public class MessageDecoder extends ByteToMessageDecoder {
/**包长度志头**/
public static final int HEAD_LENGHT = 45;
/**标志头**/
public static final byte PACKAGE_TAG = 0x01;
//从ByteBuf中获取字节,转换成对象,写入到List中
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List
MessageEncoder.java
package org.ych.techDemo.netty.encoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* MessageEncoder.java
*
* @version 1.0
*/
public class MessageEncoder extends MessageToByteEncoder {
//从Message中获取数据,解析成字节后,写入到ByteBuff中
@Override
protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
Header header = msg.getHeader();
out.writeByte(MessageDecoder.PACKAGE_TAG);
out.writeByte(header.getEncode());
out.writeByte(header.getEncrypt());
out.writeByte(header.getExtend1());
out.writeByte(header.getExtend2());
out.writeBytes(header.getSessionid().getBytes());
out.writeInt(header.getLength());
out.writeInt(header.getCammand());
out.writeBytes(msg.getData().getBytes("UTF-8"));
}
}
服务端
客户端
Bootstrap: 负责客户端的配置与启动
ServerBootstrap: 负责服务端的配置与启动
ChannelHandler:过滤器模式对netty中入站和出站数据进行处理,入站依照ChannelHandler在ChannelPipeline中的添加顺序对数据进行依次处理,出站逆序。入站处理类实现接口ChannelInBoundHandler,出栈处理类实现接口ChannelOutBoundHandler(通常继承简单实现类ChannelHandlerAdapter,ChannelInBoundHandlerAdapter和ChannelOutBoundHandler即可)
代码中b.channel(NioServerSocketChannel.class)一行代码用来指定数据传输方式,有四种:
NIO,io.netty.channel.socket.nio,基于java.nio.channels的工具包,使用选择器作为基础的方法。
OIO,io.netty.channel.socket.oio,基于java.net的工具包,使用阻塞流。
Local,io.netty.channel.local,用来在虚拟机之间本地通信。
Embedded,io.netty.channel.embedded,嵌入传输,它允许在没有真正网络的运输中使用ChannelHandler,可以非常有用的来测试ChannelHandler的实现。
也属于channelHandler,通常有下面几个基类可用于继承:
ByteToMessageDecoder :入站编码器,字节到消息
MessageToByteEncoder :出站解码器,消息到字节
MessageToMessageEncoder :出站解码器,消息到消息
ByteToMessageCodec :出入站编解码器,综合MessageToByteEncoder 和ByteToMessageDecoder
MessageToMessageCodec :出入站编解码器,实现两个方法,不常用
netty作为客户端或服务端,内置了许多的channelHandler,广泛支持各种通信协议,包括Http、Https、WebSocket
等,也可以如同demo一样,自定义私有协议。
对Http支持的内置channelHandler包括 :
HttpResponseDecoder ,HttpRequestEncoder (Http编解码)
HttpContentDecompressor(Http压缩)
HttpObjectAggregator(用于聚合http消息)