Netty简单应用

来源:互联网 发布:淘宝店铺规划方案 编辑:程序博客网 时间:2024/05/25 19:56

netty是目前最好的nio框架,一般用来做数据通信.为什么用netty?因为nio编程太麻烦,netty做了很好的封装,是操作更简单,下面我写一个简单的netty框架的应用

首先要使用netty必须引入netty的包,pom文件加入如下代码段

<dependency>    <groupId>io.netty</groupId>    <artifactId>netty-all</artifactId>    <version>5.0.0.Alpha2</version></dependency>

先看服务端

package com.kevindai.netty;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 Server {    private int port;    public Server(int port){        this.port = port;    }    public void run() throws Exception {        EventLoopGroup bossGroup = new NioEventLoopGroup();//用于接收进来客户端的连接        EventLoopGroup workerGroup = new NioEventLoopGroup();//用于处理已经被接收的连接        try {            ServerBootstrap b = new ServerBootstrap();//启动NIO服务的辅助启动类            b.group(bossGroup, workerGroup)            .channel(NioServerSocketChannel.class)            .childHandler(new ChannelInitializer<SocketChannel>() {                @Override                public void initChannel(SocketChannel ch) throws Exception {                    ch.pipeline().addLast(new ServerHandler());//以上及以下很多都是模板化的代码,主要要更改的是ServerHandler()的实现                }            })            .option(ChannelOption.SO_BACKLOG, 128)            .childOption(ChannelOption.SO_KEEPALIVE, true);            ChannelFuture f = b.bind(port).sync();            f.channel().closeFuture().sync();        } finally {            workerGroup.shutdownGracefully();            bossGroup.shutdownGracefully();        }    }    public static void main(String[] args) throws Exception {        int port;        if (args.length > 0) {            port = Integer.parseInt(args[0]);        } else {            port = 8080;        }        new Server(port).run();    }}

然后看看服务端的处理类,从客户端接受的数据如何处理都在这里定义,在实际工作中要做修改的一般也是这个地方

package com.kevindai.netty;import io.netty.buffer.ByteBuf;import io.netty.buffer.Unpooled;import io.netty.channel.ChannelFutureListener;import io.netty.channel.ChannelHandlerAdapter;import io.netty.channel.ChannelHandlerContext;public class ServerHandler extends ChannelHandlerAdapter{    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception{        //获得接收客户端的数据并进行处理        ByteBuf buf = (ByteBuf)msg;        byte[] data = new byte[buf.readableBytes()];        buf.readBytes(data);        String str = new String(data,"UTF-8");        System.out.println("server get:" + str);        //回写给客户端数据        ctx.write(Unpooled.copiedBuffer("msg handled".getBytes()))            .addListener(ChannelFutureListener.CLOSE);//发送完数据之后关闭跟客户端的连接        ctx.flush();    }    @Override    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)        cause.printStackTrace();        ctx.close();    }}

下面看看客户端如何启动

package com.kevindai.netty;import io.netty.bootstrap.Bootstrap;import io.netty.buffer.Unpooled;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.NioSocketChannel;public class Client {    public static void main(String[] args) throws Exception {        String host = "localhost";        int port = 8080;        EventLoopGroup workerGroup = new NioEventLoopGroup();        try {            Bootstrap b = new Bootstrap();            b.group(workerGroup);            b.channel(NioSocketChannel.class);            b.option(ChannelOption.SO_KEEPALIVE, true);            b.handler(new ChannelInitializer<SocketChannel>() {                @Override                public void initChannel(SocketChannel ch) throws Exception {                    ch.pipeline().addLast(new ClientHandler());//客户端接受服务端发送的数据进行的处理逻辑                }            });            ChannelFuture f = b.connect(host, port).sync();            f.channel().write(Unpooled.copiedBuffer("test".getBytes()));//发送给服务端数据            f.channel().flush();//发送之后清空缓存            //f.channel().writeAndFlush(Unpooled.copiedBuffer("test".getBytes()));//这行代码和上面两行意思是一样的            f.channel().closeFuture().sync();        } finally {            workerGroup.shutdownGracefully();        }    }}

看看客户端处理数据的逻辑,这里的逻辑都很简单,实际情况实际处理

package com.kevindai.netty;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerAdapter;import io.netty.channel.ChannelHandlerContext;import io.netty.util.ReferenceCountUtil;public class ClientHandler extends ChannelHandlerAdapter{    @Override    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {        try {            //接收从服务端发送的数据            ByteBuf buf = (ByteBuf)msg;            byte[] data = new byte[buf.readableBytes()];            buf.readBytes(data);            String request = new String(data, "utf-8");            System.out.println("Client get: " + request);        } finally {            ReferenceCountUtil.release(msg);        }    }//  @Override//  public void channelActive(final ChannelHandlerContext ctx) { // (1)//      final ByteBuf time = ctx.alloc().buffer(4); // (2)//      time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L));//      final ChannelFuture f = ctx.writeAndFlush(time); // (3)//      f.addListener(new ChannelFutureListener() {//          @Override//          public void operationComplete(ChannelFuture future) {//              assert f == future;//              ctx.close();//          }//      }); // (4)//  }    @Override    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {        cause.printStackTrace();        ctx.close();    }}

1、在实际项目中客户端和服务器端都不在一台服务器中,那么如何如何启动呢?第一种办法是让程序集成在web容器中,比如tomcat,在web容器启动时即启动.第二种办法打成Jar包来运行(这两种办法我这里都不做介绍了)
2、这里存在一个tcp 黏包、拆包的问题,一般有三种解决方案

  • 消息定长,例如每个报文的大小固定为一定长度,如果不够就补空格;如果过长会被截取(不推荐)
  • 在包尾部增加特殊字符进行分割(推荐,一般通过DelimiterBasedFrameDecoder自定义分隔符实现)
  • 将消息分为消息头和消息体,在消息头中包含表示消息总长度的字段,然后再进行业务逻辑处理.(类似自定义协议,比较麻烦,我没试过)

用特殊字符分割的应用如下,跟上面代码最大的区别是服务端加了以下两行
这里写图片描述
客户端发送信息格式如下
这里写图片描述

0 0
原创粉丝点击