netty编程之helloworld
一: 前言
Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
二: 为什么学?
netty封装了socket,简化了socket编程的操作,因为项目中要对接硬件,所以苦逼的我就来学了......
三: 正文
3.1 netty是建立在服务端与客户端之间的,所以需要建立两端的代码
3.2 建立工程,导入maven依赖,这里导入的为5.x版本,如果导入的是4.x版本,下面的代码中无法@Override channelRead方法,即4.x版本与5.x版本的差异
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>5.0.0.Alpha2</version> </dependency>
3.3 建立服务端代码,监听对应的端口
3.3.1 建立服务端处理逻辑
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandler.Sharable; import java.nio.charset.StandardCharsets; /** * 服务端处理逻辑 */ @Sharable public class Server4HelloWorldHandler extends ChannelHandlerAdapter { /** * 用户处理读取数据请求的逻辑 * @param ctx 包含与客户端建立连接的资源,如: 对应的channel * @param msg 读取到的数据,默认类型为ByteBuf.是对ByteBuffer的封装 * @throws Exception */ @Override public void channelRead(ChannelHandlerContext ctx,Object msg) throws Exception { ByteBuf readBuffer = (ByteBuf) msg; byte[] tempDatas = new byte[readBuffer.readableBytes()]; readBuffer.readBytes(tempDatas); String message = new String(tempDatas, StandardCharsets.UTF_8); System.out.println("from client :" + message); if("exit".equals(message)){ ctx.close(); return; } String line = "server message to client"; //如果调用的是write方法,不会刷新缓冲,缓冲中的数据不会发送到客户端,必须再次调用flush方法 ctx.writeAndFlush(Unpooled.copiedBuffer(line.getBytes(StandardCharsets.UTF_8))); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("server exceptionCaught method run ..... "); ctx.close(); } }
3.3.2 建立服务端,监听对应的端口
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; /** * netty 服务端 */ public class Server4HelloWorld { /** * 监听线程组,监听客户端请求 */ private EventLoopGroup acceptorGroup; /** * 处理客户端相关操作线程组,负责处理与客户端的数据通讯 */ private EventLoopGroup clientGroup; private ServerBootstrap bootstrap; public Server4HelloWorld(){ init(); } private void init() { acceptorGroup = new NioEventLoopGroup(); clientGroup = new NioEventLoopGroup(); bootstrap = new ServerBootstrap(); //绑定线程组 bootstrap.group(acceptorGroup,clientGroup); //设置通讯模式为NIO bootstrap.channel(NioServerSocketChannel.class); //设置缓冲区大小 bootstrap.option(ChannelOption.SO_BACKLOG,1024); //SO_SNDBUF 发送缓冲区,SO_RCVBUF接收缓冲区, //SO_KEEPALIVE 开启心跳监测(保证连接有效) bootstrap.option(ChannelOption.SO_SNDBUF,16*1024) .option(ChannelOption.SO_RCVBUF,16*1024) .option(ChannelOption.SO_KEEPALIVE,true); } /** * 监听处理逻辑 * @param port 监听端口 * @param acceptorHandlers 处理器,如何处理客户端请求 * @return ChannelFuture * @throws InterruptedException */ public ChannelFuture doAccept(int port, final ChannelHandler ... acceptorHandlers) throws InterruptedException { /** * childHandler 使bootstrap 独有的方法,是用于提供处理对象的 * 可以一次性添加若干个处理逻辑,是类似责任链模式处理方式。 * 增加A,B两个处理逻辑,在处理客户端请求数据的时候,根据A->B 顺序依次处理 * ChannelInitializer 用于提供处理器的模型对象 * 其中定义了 initChannel方法,用于初始化处理逻辑责任链的。 * 可以保证服务端的bootstrap只初始化一次处理器,提供处理逻辑的重用,避免反复创建处理器对象,节约资源 */ bootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(acceptorHandlers); } }); //bind 绑定端口,可以绑定多个端口 //sync 开启监听,返回ChannelFuture,代表监听成功后的一个对应的未来结果 //可以使用此对象实现后续的服务器和客户端的交互 return bootstrap.bind(port).sync(); } /** * shutdownGracefully 安全关闭方法,保证不抛弃任何一个已接收的客户端请求 */ public void release(){ this.acceptorGroup.shutdownGracefully(); this.clientGroup.shutdownGracefully(); } public static void main(String[] args) { ChannelFuture future = null; Server4HelloWorld server = null; try { server = new Server4HelloWorld(); future = server.doAccept(9999,new Server4HelloWorldHandler()); System.out.println("server started"); //关闭连接 //future.channel().close().sync(); } catch (InterruptedException e) { e.printStackTrace(); }finally { if(null!=future){ try { future.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } } if(null!=server){ server.release(); } } } }
3.4 建立客户端代码,连接服务器对应的端口
3.4.1 建立客户端处理逻辑
import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.util.ReferenceCountUtil; import java.lang.ref.Reference; import java.nio.charset.StandardCharsets; public class Client4HelloWorldHandler extends ChannelHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { try { ByteBuf readBuffer = (ByteBuf) msg; byte[] tempDatas = new byte[readBuffer.readableBytes()]; readBuffer.readBytes(tempDatas); String message = new String(tempDatas, StandardCharsets.UTF_8); System.out.println(" from server " + message); }finally { //用于释放缓存,防止内存溢出 ReferenceCountUtil.release(msg); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("client exceptionCaught method run ....."); ctx.close(); } }
3.4.2 建立客户端,连接对应的服务器端口
public class Client4HelloWorld { private EventLoopGroup group; private Bootstrap bootstrap; public Client4HelloWorld(){ init(); } private void init(){ group = new NioEventLoopGroup(); bootstrap = new Bootstrap(); bootstrap.group(group); bootstrap.channel(NioSocketChannel.class); } public ChannelFuture doRequest(String host, int port, final ChannelHandler ... handlers) throws InterruptedException { /** * 客户端的bootstrap没有childHandler,只有handler * * 在客户端必须绑定处理器,也就是必须绑定handler方法。 * 服务器必须绑定处理器,必须绑定childHandler方法 */ this.bootstrap.handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(handlers); } }); //connect 连接 return this.bootstrap.connect(host, port).sync(); } public void release(){ this.group.shutdownGracefully(); } public static void main(String[] args) { Client4HelloWorld client = null; ChannelFuture future = null; try{ client = new Client4HelloWorld(); future = client.doRequest("localhost",9999,new Client4HelloWorldHandler()); Scanner s = null; while (true){ s = new Scanner(System.in); System.out.println("enter the message 'exit' to exit"); String line = s.nextLine(); if("exit".equals(line)){ //addListener 增加监听,当条件满足,触发监听器 //ChannelFutureListener.CLOSE 关闭的监听器,代表ChannelFuture执行返回后,关闭连接 future.channel().writeAndFlush(Unpooled.copiedBuffer(line.getBytes(StandardCharsets.UTF_8))) .addListener(ChannelFutureListener.CLOSE); break; } future.channel().writeAndFlush(Unpooled.copiedBuffer(line.getBytes(StandardCharsets.UTF_8))); TimeUnit.SECONDS.sleep(1); } }catch (Exception e){ e.printStackTrace(); }finally { if(null != future){ try { future.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } } if(null != client){ client.release(); } } } }
3.5 运行测试