Netty编程案例
准备:引入netty依赖
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.8.Final</version> </dependency>
入门案例:
1.服务端业务处理类
package com.tenpower.netty.basic; 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; //服务器端的业务处理类 public class NettyServerHandler extends ChannelInboundHandlerAdapter { //读取数据事件 public void channelRead(ChannelHandlerContext ctx,Object msg){ System.out.println("Server:"+ctx); ByteBuf buf=(ByteBuf) msg; System.out.println("客户端发来的消息:"+buf.toString(CharsetUtil.UTF_8)); } //数据读取完毕事件 public void channelReadComplete(ChannelHandlerContext ctx){ ctx.writeAndFlush(Unpooled.copiedBuffer("就是没钱",CharsetUtil.UTF_8)); } //异常发生事件 public void exceptionCaught(ChannelHandlerContext ctx,Throwable t){ ctx.close(); } }
2.服务端程序。配置线程组、自定义业务处理类,并绑定了端口号启动
package com.tenpower.netty.basic; 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 NettyServer { public static void main(String[] args) throws Exception{ //1. 创建一个线程组:接收客户端连接 EventLoopGroup bossGroup =new NioEventLoopGroup(); //2. 创建一个线程组:处理网络操作 EventLoopGroup workerGroup =new NioEventLoopGroup(); //3. 创建服务器端启动助手来配置参数 ServerBootstrap b=new ServerBootstrap(); b.group(bossGroup,workerGroup) //4.设置两个线程组 .channel(NioServerSocketChannel.class) //5.使用NioServerSocketChannel作为服务器端通道的实现 .option(ChannelOption.SO_BACKLOG,128) //6.设置线程队列中等待连接的个数 .childOption(ChannelOption.SO_KEEPALIVE,true) //7.保持活动连接状态 .childHandler(new ChannelInitializer<SocketChannel>() { //8. 创建一个通道初始化对象 public void initChannel(SocketChannel sc){ //9. 往Pipeline链中添加自定义的handler类 sc.pipeline().addLast(new NettyServerHandler()); } }); System.out.println("......Server is ready......"); ChannelFuture cf=b.bind(9999).sync(); //10. 绑定端口 bind方法是异步的 sync方法是同步阻塞的 System.out.println("......Server is starting......"); //11. 关闭通道,关闭线程组 cf.channel().closeFuture().sync(); //异步 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } }
3.客户端业务处理类
package com.tenpower.netty.basic; 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; //客户端业务处理类 public class NettyClientHandler extends ChannelInboundHandlerAdapter { //通道就绪事件 public void channelActive(ChannelHandlerContext ctx){ System.out.println("Client:"+ctx); ctx.writeAndFlush(Unpooled.copiedBuffer("老板,还钱吧",CharsetUtil.UTF_8)); } //读取数据事件 public void channelRead(ChannelHandlerContext ctx,Object msg){ ByteBuf buf=(ByteBuf) msg; System.out.println("服务器端发来的消息:"+buf.toString(CharsetUtil.UTF_8)); } }
4.客户端程序。配置线程组、自定义业务处理类,并启动连接服务端
package com.tenpower.netty.basic; 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; //网络客户端 public class NettyClient { public static void main(String[] args) throws Exception{ //1. 创建一个线程组 EventLoopGroup group=new NioEventLoopGroup(); //2. 创建客户端的启动助手,完成相关配置 Bootstrap b=new Bootstrap(); b.group(group) //3. 设置线程组 .channel(NioSocketChannel.class) //4. 设置客户端通道的实现类 .handler(new ChannelInitializer<SocketChannel>() { //5. 创建一个通道初始化对象 @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new NettyClientHandler()); //6.往Pipeline链中添加自定义的handler } }); System.out.println("......Client is ready......"); //7.启动客户端去连接服务器端 connect方法是异步的 sync方法是同步阻塞的 ChannelFuture cf=b.connect("127.0.0.1",9999).sync(); //8.关闭连接(异步非阻塞) cf.channel().closeFuture().sync(); } }
运行结果如下图:
网络聊天案例:
1.自定义服务端业务处理类。通道就绪时,输出在线;通道未就绪时,输出下线;通道发数据时,读数据
package com.tenpower.netty.chat; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.util.concurrent.GlobalEventExecutor; import java.util.ArrayList; import java.util.List; //自定义一个服务器端业务处理类 public class ChatServerHandler extends SimpleChannelInboundHandler<String> { public static List<Channel> channels = new ArrayList<>(); @Override //通道就绪 public void channelActive(ChannelHandlerContext ctx) { Channel inChannel=ctx.channel(); channels.add(inChannel); System.out.println("[Server]:"+inChannel.remoteAddress().toString().substring(1)+"上线"); } @Override //通道未就绪 public void channelInactive(ChannelHandlerContext ctx) { Channel inChannel=ctx.channel(); channels.remove(inChannel); System.out.println("[Server]:"+inChannel.remoteAddress().toString().substring(1)+"离线"); } @Override //读取数据 protected void channelRead0(ChannelHandlerContext ctx, String s) { Channel inChannel=ctx.channel(); for(Channel channel:channels){ if(channel!=inChannel){ channel.writeAndFlush("["+inChannel.remoteAddress().toString().substring(1)+"]"+"说:"+s+"\n"); } } } }
2.服务端程序。往Pipeline链中添加的处理字符串的编码器和解码器加入Pipeline后会自动工作,这样在服务端读写字符串数据时无需人工处理ByteBuf
package com.tenpower.netty.chat; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; //聊天程序服务器端 public class ChatServer { private int port; //服务器端端口号 public ChatServer(int port) { this.port = port; } public void run() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ChannelPipeline pipeline=ch.pipeline(); //往pipeline链中添加一个解码器 pipeline.addLast("decoder",new StringDecoder()); //往pipeline链中添加一个编码器 pipeline.addLast("encoder",new StringEncoder()); //往pipeline链中添加自定义的handler(业务处理类) pipeline.addLast(new ChatServerHandler()); } }); System.out.println("Netty Chat Server启动......"); ChannelFuture f = b.bind(port).sync(); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); System.out.println("Netty Chat Server关闭......"); } } public static void main(String[] args) throws Exception { new ChatServer(9999).run(); } }
3.客户端业务处理类。读取服务端发来的数据
package com.tenpower.netty.chat; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; //自定义一个客户端业务处理类 public class ChatClientHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception { System.out.println(s.trim()); } }
4.客户端程序。也添加了编解码器
package com.tenpower.netty.chat; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.Scanner; //聊天程序客户端 public class ChatClient { private final String host; //服务器端IP地址 private final int port; //服务器端端口号 public ChatClient(String host, int port) { this.host = host; this.port = port; } public void run(){ EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch){ ChannelPipeline pipeline=ch.pipeline(); //往pipeline链中添加一个解码器 pipeline.addLast("decoder",new StringDecoder()); //往pipeline链中添加一个编码器 pipeline.addLast("encoder",new StringEncoder()); //往pipeline链中添加自定义的handler(业务处理类) pipeline.addLast(new ChatClientHandler()); } }); ChannelFuture cf=bootstrap.connect(host,port).sync(); Channel channel=cf.channel(); System.out.println("------"+channel.localAddress().toString().substring(1)+"------"); Scanner scanner=new Scanner(System.in); while (scanner.hasNextLine()){ String msg=scanner.nextLine(); channel.writeAndFlush(msg+"\r\n"); } } catch (Exception e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } } public static void main(String[] args) throws Exception { new ChatClient("127.0.0.1",9999).run(); } }
可同时运行多个客户端,运行结果如下:
自定义RPC案例:
● 服务提供方,两个接口和两个实现类共消费方调用
public interface HelloNetty { String hello(); } public class HelloNettyImpl implements HelloNetty { @Override public String hello() { return "hello,netty"; } } public interface HelloRPC { String hello(String name); } public class HelloRPCImpl implements HelloRPC { @Override public String hello(String name) { return "hello," + name; } }
● Server Stub
1.封装消费方发起远程调用时传给服务方的数据
package com.tenpower.rpc.serverStub; import java.io.Serializable; //封装类信息 public class ClassInfo implements Serializable { private static final long serialVersionUID = 1L; private String className; //类名 private String methodName;//方法名 private Class<?>[] types; //参数类型 private Object[] objects;//参数列表 public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public String getMethodName() { return methodName; } public void setMethodName(String methodName) { this.methodName = methodName; } public Class<?>[] getTypes() { return types; } public void setTypes(Class<?>[] types) { this.types = types; } public Object[] getObjects() { return objects; } public void setObjects(Object[] objects) { this.objects = objects; } }
2.业务处理类。读取消费方发来的数据并调用本地,然后把结果返回消费方
package com.tenpower.rpc.serverStub; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import org.reflections.Reflections; import java.lang.reflect.Method; import java.util.Set; //服务器端业务处理类 public class InvokeHandler extends ChannelInboundHandlerAdapter { //得到某接口下某个实现类的名字 private String getImplClassName(ClassInfo classInfo) throws Exception{ //服务方接口和实现类所在的包路径 String interfacePath="com.tenpower.rpc.server"; int lastDot = classInfo.getClassName().lastIndexOf("."); String interfaceName=classInfo.getClassName().substring(lastDot); Class superClass=Class.forName(interfacePath+interfaceName); Reflections reflections = new Reflections(interfacePath); //得到某接口下的所有实现类 Set<Class> ImplClassSet=reflections.getSubTypesOf(superClass); if(ImplClassSet.size()==0){ System.out.println("未找到实现类"); return null; }else if(ImplClassSet.size()>1){ System.out.println("找到多个实现类,未明确使用哪一个"); return null; }else { //把集合转换为数组 Class[] classes=ImplClassSet.toArray(new Class[0]); return classes[0].getName(); //得到实现类的名字 } } @Override //读取客户端发来的数据并通过反射调用实现类的方法 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ClassInfo classInfo = (ClassInfo) msg; Object clazz = Class.forName(getImplClassName(classInfo)).newInstance(); Method method = clazz.getClass().getMethod(classInfo.getMethodName(), classInfo.getTypes()); //通过反射调用实现类的方法 Object result = method.invoke(clazz, classInfo.getObjects()); ctx.writeAndFlush(result); } }
3.网络服务器。采用Netty自带的ObjectEncoder和ObjectDecoder作为编解码器
package com.tenpower.rpc.serverStub; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.serialization.ClassResolvers; import io.netty.handler.codec.serialization.ObjectDecoder; import io.netty.handler.codec.serialization.ObjectEncoder; //网络处理服务器 public class NettyRPCServer { private int port; public NettyRPCServer(int port) { this.port = port; } public void start() { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true) .localAddress(port).childHandler( new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); //编码器 pipeline.addLast("encoder", new ObjectEncoder()); //解码器 pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null))); //服务器端业务处理类 pipeline.addLast(new InvokeHandler()); } }); ChannelFuture future = serverBootstrap.bind(port).sync(); System.out.println("......server is ready......"); future.channel().closeFuture().sync(); } catch (Exception e) { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { new NettyRPCServer(9999).start(); } }
● Client Stub
1.客户端业务处理类。读取远程调用返回的数据
package com.tenpower.rpc.clientStub; import io.netty.channel.*; //客户端业务处理类 public class ResultHandler extends ChannelInboundHandlerAdapter { private Object response; public Object getResponse() { return response; } @Override //读取服务器端返回的数据(远程调用的结果) public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { response = msg; ctx.close(); } }
2.客户端代理类
package com.tenpower.rpc.clientStub; import com.tenpower.rpc.serverStub.ClassInfo; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.serialization.ClassResolvers; import io.netty.handler.codec.serialization.ObjectDecoder; import io.netty.handler.codec.serialization.ObjectEncoder; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; //客户端代理类 public class NettyRPCProxy { //根据接口创建代理对象 public static Object create(Class target) { return Proxy.newProxyInstance(target.getClassLoader(), new Class[]{target}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //封装ClassInfo ClassInfo classInfo = new ClassInfo(); classInfo.setClassName(target.getName()); classInfo.setMethodName(method.getName()); classInfo.setObjects(args); classInfo.setTypes(method.getParameterTypes()); //开始用Netty发送数据 EventLoopGroup group = new NioEventLoopGroup(); ResultHandler resultHandler = new ResultHandler(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); //编码器 pipeline.addLast("encoder", new ObjectEncoder()); //解码器 构造方法第一个参数设置二进制数据的最大字节数 第二个参数设置具体使用哪个类解析器 pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null))); //客户端业务处理类 pipeline.addLast("handler", resultHandler); } }); ChannelFuture future = b.connect("127.0.0.1", 9999).sync(); future.channel().writeAndFlush(classInfo).sync(); future.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } return resultHandler.getResponse(); } }); } }
● Client
package com.tenpower.rpc.client; import com.tenpower.rpc.clientStub.NettyRPCProxy; //服务调用方 public class TestNettyRPC { public static void main(String [] args){ //第1次远程调用 HelloNetty helloNetty=(HelloNetty) NettyRPCProxy.create(HelloNetty.class); System.out.println(helloNetty.hello()); //第2次远程调用 HelloRPC helloRPC = (HelloRPC) NettyRPCProxy.create(HelloRPC.class); System.out.println(helloRPC.hello("RPC")); } }
运行结果如下:
使用google的ProtoBuf作为编解码器:
1.引入依赖
<dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>3.6.1</version> </dependency>
2.proto文件
3.通过protoc.exe生成java文件
注意:不要编辑这个类,该类的内部类才是真正的POJO
4.客户端程序。在Pipeline链中添加ProtoBufEncoder编码器对象
public class NettyClient { public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel sc) { sc.pipeline().addLast("encoder",new ProtobufEncoder()); sc.pipeline().addLast(new NettyClientHandler()); } }); // 启动客户端 ChannelFuture cf = b.connect("127.0.0.1", 9999).sync(); // (5) // 等待连接关闭 cf.channel().closeFuture().sync(); } }
5.往服务器端发送POJO时可以使用生成的BookMessage
public class NettyClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) { BookMessage.Book book=BookMessage.Book.newBuilder().setId(1).setName("Java从入门到精通").build(); ctx.writeAndFlush(book); } }
6.服务端程序。要向Pipeline链中添加ProtoBufDecoder解码器对象
public class NettyServer { public static void main(String[] args) throws Exception{ EventLoopGroup pGroup = new NioEventLoopGroup(); //线程组:用来处理网络事件处理(接受客户端连接) EventLoopGroup cGroup = new NioEventLoopGroup(); //线程组:用来进行网络通讯读写 ServerBootstrap b = new ServerBootstrap(); b.group(pGroup, cGroup) .channel(NioServerSocketChannel.class) //注册服务端channel .option(ChannelOption.SO_BACKLOG, 128) . childOption(ChannelOption.SO_KEEPALIVE, true) .childHandler(new ChannelInitializer<SocketChannel>() { public void initChannel(SocketChannel sc) throws Exception { sc.pipeline().addLast("decoder",new ProtobufDecoder(BookMessage.Book.getDefaultInstance())); sc.pipeline().addLast(new NettyServerHandler()); } }); ChannelFuture cf = b.bind(9999).sync(); System.out.println("......Server is Starting......"); //释放 cf.channel().closeFuture().sync(); pGroup.shutdownGracefully(); cGroup.shutdownGracefully(); } }
7.服务器端接收数据可直接把数据转换成POJO
public class NettyServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { BookMessage.Book book=(BookMessage.Book)msg; System.out.println("客户端发来数据:"+book.getName()); } }