zoukankan      html  css  js  c++  java
  • 【转】Netty入门二:开发第一个Netty应用程序

    Netty入门二:开发第一个Netty应用程序

        既然是入门,那我们就在这里写一个简单的Demo,客户端发送一个字符串到服务器端,服务器端接收字符串后再发送回客户端。

    2.1、配置开发环境

    1.安装JDK

    2.去官网下载jar包

    (或者通过pom构建)

    2.2、认识下Netty的Client和Server

         一个Netty应用模型,如下图所示,但需要明白一点的是,我们写的Server会自动处理多客户端请求,理论上讲,处理并发的能力决定于我们的系统配置及JDK的极限。    

    1. Client连接到Server端
    2. 建立链接发送/接收数据
    3. Server端处理所有Client请求

         这里有一个形象的比喻来形容Netty客户端和服务器端的交互模式,把你比作一个Client,把山比作一个Server,你走到山旁,就是和山建立了链接,你向山大喊了一声,就代表向山发送了数据,你的喊声经过山的反射形成了回声,这个回声就是服务器的响应数据。如果你可以离开,就代表断开了链接,当然你也可以再回来。一次可以后好多人向山大喊,他们的喊声也一定会得到山的回应。

    2.3 写一个Netty Server

         一个NettyServer程序主要由两部分组成:

    • BootsTrapping:配置服务器端基本信息
    • ServerHandler:真正的业务逻辑处理

    2.3.1 BootsTrapping的过程:

    package NettyDemo.echo.server;
    
    import io.netty.bootstrap.ServerBootstrap;
    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.NioServerSocketChannel;
    import java.net.InetSocketAddress;
    import NettyDemo.echo.handler.EchoServerHandler;
    public class EchoServer {
    	private static final int port = 8080;
    	public void start() throws InterruptedException {
    		ServerBootstrap b = new ServerBootstrap();// 引导辅助程序
    		EventLoopGroup group = new NioEventLoopGroup();// 通过nio方式来接收连接和处理连接
    		try {
    			b.group(group);
    			b.channel(NioServerSocketChannel.class);// 设置nio类型的channel
    			b.localAddress(new InetSocketAddress(port));// 设置监听端口
    			b.childHandler(new ChannelInitializer<SocketChannel>() {//有连接到达时会创建一个channel
    						protected void initChannel(SocketChannel ch) throws Exception {
    							// pipeline管理channel中的Handler,在channel队列中添加一个handler来处理业务
    							ch.pipeline().addLast("myHandler", new EchoServerHandler());
    						}
    					});
    			ChannelFuture f = b.bind().sync();// 配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功
    			System.out.println(EchoServer.class.getName() + " started and listen on " + f.channel().localAddress());
    			f.channel().closeFuture().sync();// 应用程序会一直等待,直到channel关闭
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			group.shutdownGracefully().sync();//关闭EventLoopGroup,释放掉所有资源包括创建的线程
    		}
    	}
    	public static void main(String[] args) {
    		try {
    			new EchoServer().start();
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    	}
    }

         1. 创建一个ServerBootstrap实例 
         2. 创建一个EventLoopGroup来处理各种事件,如处理链接请求,发送接收数据等。 
         3. 定义本地InetSocketAddress( port)好让Server绑定 
         4. 创建childHandler来处理每一个链接请求    
         5. 所有准备好之后调用ServerBootstrap.bind()方法绑定Server

    2.3.2 业务逻辑ServerHandler:

         要想处理接收到的数据,我们必须继承ChannelInboundHandlerAdapter接口,重写里面的MessageReceive方法,每当有数据到达,此方法就会被调用(一般是Byte类型数组),我们就在这里写我们的业务逻辑:

    package NettyDemo.echo.handler;
    
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelFutureListener;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.channel.ChannelHandler.Sharable;
    /**
     * Sharable表示此对象在channel间共享
     * handler类是我们的具体业务类
     * */
    @Sharable//注解@Sharable可以让它在channels间共享
    public class EchoServerHandler extends ChannelInboundHandlerAdapter{
    	public void channelRead(ChannelHandlerContext ctx, Object msg) { 
    		System.out.println("server received data :" + msg); 
    		ctx.write(msg);//写回数据,
    	} 
    	public void channelReadComplete(ChannelHandlerContext ctx) { 
    		ctx.writeAndFlush(Unpooled.EMPTY_BUFFER) //flush掉所有写回的数据
    		.addListener(ChannelFutureListener.CLOSE); //当flush完成后关闭channel
    	} 
    	public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) { 
    		cause.printStackTrace();//捕捉异常信息
    		ctx.close();//出现异常时关闭channel 
    	} 	
    }

    2.3.3关于异常处理:


         我们在上面程序中也重写了exceptionCaught方法,这里就是对当异常出现时的处理。

     

    2.4 写一个Netty Client

     

    一般一个简单的Client会扮演如下角色:

    1. 连接到Server
    2. 向Server写数据
    3. 等待Server返回数据
    4. 关闭连接

    4.4.1  BootsTrapping的过程:

     

         和Server端类似,只不过Client端要同时指定连接主机的IP和Port。

    package NettyDemo.echo.client;
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelFutureListener;
    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;
    
    import java.net.InetSocketAddress;
    
    import NettyDemo.echo.handler.EchoClientHandler;
    
    public class EchoClient {
    	private final String host;
    	private final int port;
    
    	public EchoClient(String host, int port) {
    		this.host = host;
    		this.port = port;
    	}
    
    	public void start() throws Exception {
    		EventLoopGroup group = new NioEventLoopGroup();
    		try {
    			Bootstrap b = new Bootstrap();
    			b.group(group);
    			b.channel(NioSocketChannel.class);
    			b.remoteAddress(new InetSocketAddress(host, port));
    			b.handler(new ChannelInitializer<SocketChannel>() {
    
    				public void initChannel(SocketChannel ch) throws Exception {
    					ch.pipeline().addLast(new EchoClientHandler());
    				}
    			});
    			ChannelFuture f = b.connect().sync();
    			f.addListener(new ChannelFutureListener() {
    				
    				public void operationComplete(ChannelFuture future) throws Exception {
    					if(future.isSuccess()){
    						System.out.println("client connected");
    					}else{
    						System.out.println("server attemp failed");
    						future.cause().printStackTrace();
    					}
    					
    				}
    			});
    			f.channel().closeFuture().sync();
    		} finally {
    			group.shutdownGracefully().sync();
    		}
    	}
    
    	public static void main(String[] args) throws Exception {
    	
    		new EchoClient("127.0.0.1", 3331).start();
    	}
    }
         1. 创建一个ServerBootstrap实例 
         2. 创建一个EventLoopGroup来处理各种事件,如处理链接请求,发送接收数据等。 
         3. 定义一个远程InetSocketAddress好让客户端连接 
         4. 当连接完成之后,Handler会被执行一次    
         5. 所有准备好之后调用ServerBootstrap.connect()方法连接Server

    4.4.2 业务逻辑ClientHandler:

     

      我们同样继承一个SimpleChannelInboundHandler来实现我们的Client,我们需要重写其中的三个方法:

    package NettyDemo.echo.handler;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.ByteBufUtil;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.channel.ChannelHandler.Sharable;
    import io.netty.util.CharsetUtil;
    
    @Sharable
    public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    	/**
    	 *此方法会在连接到服务器后被调用 
    	 * */
    	public void channelActive(ChannelHandlerContext ctx) {
    		ctx.write(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
    	}
    	/**
    	 *此方法会在接收到服务器数据后调用 
    	 * */
    	public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
    		System.out.println("Client received: " + ByteBufUtil.hexDump(in.readBytes(in.readableBytes())));
    	}
    	/**
    	 *捕捉到异常 
    	 * */
    	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
    		cause.printStackTrace();
    		ctx.close();
    	}
    
    }
        其中需要注意的是 channelRead0()方法,此方法接收到的可能是一些数据片段,比如服务器发送了5个字节数据,Client端不能保证一次全部收到,比如第一次收到3个字节,第二次收到2个字节。我们可能还会关心收到这些片段的顺序是否可发送顺序一致,这要看具体是什么协议,比如基于TCP协议的字节流是能保证顺序的。

        还有一点,在Client端我们的业务Handler继承的是SimpleChannelInboundHandler,而在服务器端继承的是ChannelInboundHandlerAdapter,那么这两个有什么区别呢?最主要的区别就是SimpleChannelInboundHandler在接收到数据后会自动release掉数据占用的Bytebuffer资源(自动调用Bytebuffer.release())。而为何服务器端不能用呢,因为我们想让服务器把客户端请求的数据发送回去,而服务器端有可能在channelRead方法返回前还没有写完数据,因此不能让它自动release。

  • 相关阅读:
    Spring Boot (20) 拦截器
    Spring Boot (19) servlet、filter、listener
    Spring Boot (18) @Async异步
    Spring Boot (17) 发送邮件
    Spring Boot (16) logback和access日志
    Spring Boot (15) pom.xml设置
    Spring Boot (14) 数据源配置原理
    Spring Boot (13) druid监控
    Spring boot (12) tomcat jdbc连接池
    Spring Boot (11) mybatis 关联映射
  • 原文地址:https://www.cnblogs.com/cheng2015/p/6548039.html
Copyright © 2011-2022 走看看