Netty 简单server和client示例

Netty 基于java NIO 网络通信框架,具有高效、简单、快速的应用特点。在当下互联网高并发场景下得到很好地应用,现在用java写的高并发产品(如dubbo 、zookeeper、hadoop、rocketmq)大都应用了netty作为底层的通信技术。

下面简单的server和client的示例代码:

一、 服务端

ServerNetty:服务端的netty类
ServerHandler :处理某个客户端请求的类
MarshallingCodefactory:把信息序列化对象用

package com.test.thread.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;

// Constant里面用到的几个参数自己可以直接写死
import com.test.thread.utils.Constant;

/**
 * tcp/ip 服务端用netty实现
 * @author zhb
 *
 */
public class ServerNetty {
    
    private int port;   
    public ServerNetty(int port){
        this.port = port;
    }
    
    // netty 服务端启动
    public void action() throws InterruptedException{
        
        // 用来接收进来的连接
        EventLoopGroup bossGroup = new NioEventLoopGroup(); 
        // 用来处理已经被接收的连接,一旦bossGroup接收到连接,就会把连接信息注册到workerGroup上
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        
        try {
            // nio服务的启动类
            ServerBootstrap sbs = new ServerBootstrap();
            // 配置nio服务参数
            sbs.group(bossGroup, workerGroup)
               .channel(NioServerSocketChannel.class) // 说明一个新的Channel如何接收进来的连接
               .option(ChannelOption.SO_BACKLOG, 128) // tcp最大缓存链接个数
               .childOption(ChannelOption.SO_KEEPALIVE, true) //保持连接
               .handler(new LoggingHandler(LogLevel.INFO)) // 打印日志级别
               .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        
                        // marshalling 序列化对象的解码
//                      socketChannel.pipeline().addLast(MarshallingCodefactory.buildDecoder());
                        // marshalling 序列化对象的编码
//                      socketChannel.pipeline().addLast(MarshallingCodefactory.buildEncoder());
                         // 网络超时时间
//                      socketChannel.pipeline().addLast(new ReadTimeoutHandler(5));
                        // 处理接收到的请求
                        socketChannel.pipeline().addLast(new ServerHandler()); // 这里相当于过滤器,可以配置多个
                    }
               });
            
            System.err.println("server 开启--------------");
            // 绑定端口,开始接受链接
            ChannelFuture cf = sbs.bind(port).sync();
            
            // 开多个端口
//          ChannelFuture cf2 = sbs.bind(3333).sync();
//          cf2.channel().closeFuture().sync();
            
            // 等待服务端口的关闭;在这个例子中不会发生,但你可以优雅实现;关闭你的服务
            cf.channel().closeFuture().sync();
        } finally{
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }           
    }
    
        
    // 开启netty服务线程
    public static void main(String[] args) throws InterruptedException {
        new ServerNetty(Constant.serverSocketPort).action();
    }
        
    /**
     * 
     * 解决数据传输中中的拆包和粘包问题方案:
     * 
     * 一 . 用特定字符当做分隔符,例如:$_ 
     *  (1) 将下列代码添加到 initChannel方法内
        //将双方约定好的分隔符转成buf
        ByteBuf bb = Unpooled.copiedBuffer("$_".getBytes(Constant.charset));
        socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, bb));
        //将接收到信息进行解码,可以直接把msg转成字符串
        socketChannel.pipeline().addLast(new StringDecoder());
        
        (2) 在 ServerHandler中的 channelRead方法中应该替换内容为
        // 如果把msg直接转成字符串,必须在服务中心添加 socketChannel.pipeline().addLast(new StringDecoder());
        String reqStr = (String)msg;
        System.err.println("server 接收到请求信息是:"+reqStr);
        String respStr = new StringBuilder("来自服务器的响应").append(reqStr).append("$_").toString();
        // 返回给客户端响应
        ctx.writeAndFlush(Unpooled.copiedBuffer(respStr.getBytes()));
        
        (3) 因为分隔符是双方约定好的,在ClientNetty和channelRead中也应该有响应的操作
                
        
     二. 双方约定好是定长报文   
         // 双方约定好定长报文为6,长度不足时服务端会一直等待直到6个字符,所以客户端不足6个字符时用空格补充;其余操作,参考分隔符的情况 
         socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(6));
         
         
    三. 请求分为请求头和请求体,请求头放的是请求体的长度;一般生产上常用的
      
          (1)通信双方约定好报文头的长度,先截取改长度,
          (2)根据报文头的长度读取报文体的内容
     * 
     * 
     */
}

package com.test.thread.netty;

import java.io.UnsupportedEncodingException;

import com.test.thread.utils.Constant;
import com.test.thread.xlh.Student;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;

/**
 * 处理某个客户端的请求
 * @author zhb
 */
public class ServerHandler extends ChannelInboundHandlerAdapter {

    // 读取数据
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {       
        // 普通的处理 及过滤器不多
        simpleRead(ctx, msg);       
        // 有分隔符处理信息
//      Delimiterread(ctx, msg);
    }
    
    
    /**
     * 最简单的处理
     * @param ctx
     * @param msg
     * @throws UnsupportedEncodingException
     */
    public void simpleRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException{

        ByteBuf bb = (ByteBuf)msg;
        // 创建一个和buf同等长度的字节数组
        byte[] reqByte = new byte[bb.readableBytes()];
        // 将buf中的数据读取到数组中
        bb.readBytes(reqByte);
        String reqStr = new String(reqByte, Constant.charset);
        System.err.println("server 接收到客户端的请求: " + reqStr);
        String respStr = new StringBuilder("来自服务器的响应").append(reqStr).append("$_").toString();
        
        // 返回给客户端响应                                                                                                                                                       和客户端链接中断即短连接,当信息返回给客户端后中断
        ctx.writeAndFlush(Unpooled.copiedBuffer(respStr.getBytes()));//.addListener(ChannelFutureListener.CLOSE);
        // 有了写操作(writeAndFlush)下面就不用释放msg
//      ReferenceCountUtil.release(msg);
    }
    
    /**
     * 有分隔符的请求信息分包情况处理,包含了转码
     * @param ctx
     * @param msg
     */
    private void Delimiterread(ChannelHandlerContext ctx, Object msg) {
        // 如果把msg直接转成字符串,必须在服务中心添加 socketChannel.pipeline().addLast(new StringDecoder());
        String reqStr = (String)msg;
        System.err.println("server 接收到请求信息是:"+reqStr);
        String respStr = new StringBuilder("来自服务器的响应").append(reqStr).append("$_").toString();
        
        // 返回给客户端响应                                                                                                                                                       和客户端链接中断即短连接,当信息返回给客户端后中断
        ctx.writeAndFlush(Unpooled.copiedBuffer(respStr.getBytes())).addListener(ChannelFutureListener.CLOSE);
    }

    
    // 数据读取完毕的处理
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.err.println("服务端读取数据完毕");
    }
    
    // 出现异常的处理
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.err.println("server 读取数据出现异常");
        ctx.close();
    }
    
    /**
     * 将请求信息直接转成对象
     * @param ctx
     * @param msg
     */
    private void handlerObject(ChannelHandlerContext ctx, Object msg) {
        // 需要序列化 直接把msg转成对象信息,一般不会用,可以用json字符串在不同语言中传递信息
        Student student = (Student)msg;     
        System.err.println("server 获取信息:"+student.getId()+student.getName());
        student.setName("李四");      
        ctx.write(student);
    }
    
    
}

package com.test.thread.netty;

import io.netty.handler.codec.marshalling.DefaultMarshallerProvider;
import io.netty.handler.codec.marshalling.DefaultUnmarshallerProvider;
import io.netty.handler.codec.marshalling.MarshallerProvider;
import io.netty.handler.codec.marshalling.MarshallingDecoder;
import io.netty.handler.codec.marshalling.MarshallingEncoder;
import io.netty.handler.codec.marshalling.UnmarshallerProvider;

import org.jboss.marshalling.MarshallerFactory;
import org.jboss.marshalling.Marshalling;
import org.jboss.marshalling.MarshallingConfiguration;
/**
 * 使用netty 把信息序列化成对象时使用
 * @author zhb
 */
public class MarshallingCodefactory {
    

    /**
     * 创建 marshalling 解码器
     * @return
     */
    public static MarshallingDecoder buildDecoder() {
        
        // 先通过marshalling的工具类提供的方法实例化marshalling对象,参数serial是创建Java序列化工厂对象 serial
        MarshallerFactory  factory = Marshalling.getProvidedMarshallerFactory("serial");
        
        // 创建MarshallingConfiguration对象,设置版本为5
        final MarshallingConfiguration config = new MarshallingConfiguration();
        config.setVersion(5);
        
        // 根据 marshalling 的factory 和 config创建 provider;
        UnmarshallerProvider provider = new DefaultUnmarshallerProvider(factory, config);
        
        // 构建netty的MarshallingDecoder对象,两个参数分别是provide和单个信息序列后的最大长度
        MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024*1204*1);
        
        return decoder;
    }

    
    /**
     * 创建 marshalling 编码器
     * @return
     */
    public static MarshallingEncoder buildEncoder() {
        
        // 先通过marshalling的工具类提供的方法实例化marshalling对象,参数serial是创建Java序列化工厂对象
        final MarshallerFactory  factory = Marshalling.getProvidedMarshallerFactory("serial");
        
        // 创建MarshallingConfiguration对象,设置版本为5
        final MarshallingConfiguration config = new MarshallingConfiguration();
        config.setVersion(5);
        
        // 根据 marshalling 的factory 和 config创建 provider;
        MarshallerProvider provider = new DefaultMarshallerProvider(factory, config);
        
        // 构建netty 的编码对象, 用于实现序列化接口的pojo对象序列化为二进制数组
        MarshallingEncoder encoder = new MarshallingEncoder(provider);
        
        return encoder;
    }
    
}

二、客户端

ClientNetty : netty客户端启动类
ClientHandler: 处理服务端返回的信息

package com.test.thread.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;

import java.io.UnsupportedEncodingException;

import com.test.thread.utils.Constant;

/**
 * 客户端发送请求
 * @author zhb
 *
 */
public class ClientNetty {
    
    // 要请求的服务器的ip地址
    private String ip;
    // 服务器的端口
    private int port;
    
    public ClientNetty(String ip, int port){
        this.ip = ip;
        this.port = port;
    }
    
    // 请求端主题
    private void action() throws InterruptedException, UnsupportedEncodingException {
        
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        
        Bootstrap bs = new Bootstrap();
        
        bs.group(bossGroup)
          .channel(NioSocketChannel.class)
          .option(ChannelOption.SO_KEEPALIVE, true)
          .handler(new ChannelInitializer<SocketChannel>() {
              @Override
              protected void initChannel(SocketChannel socketChannel) throws Exception {              
                    // marshalling 序列化对象的解码
//                  socketChannel.pipeline().addLast(MarshallingCodefactory.buildDecoder());
                    // marshalling 序列化对象的编码
//                  socketChannel.pipeline().addLast(MarshallingCodefactory.buildEncoder());
                  
                    // 处理来自服务端的响应信息
                    socketChannel.pipeline().addLast(new ClientHandler());
              }
         });
        
        // 客户端开启
        ChannelFuture cf = bs.connect(ip, port).sync();
        
        String reqStr = "我是客户端请求1$_";
        
        // 发送客户端的请求
        cf.channel().writeAndFlush(Unpooled.copiedBuffer(reqStr.getBytes(Constant.charset)));
//      Thread.sleep(300);
//      cf.channel().writeAndFlush(Unpooled.copiedBuffer("我是客户端请求2$_---".getBytes(Constant.charset)));
//      Thread.sleep(300);
//      cf.channel().writeAndFlush(Unpooled.copiedBuffer("我是客户端请求3$_".getBytes(Constant.charset)));
        
//      Student student = new Student();
//      student.setId(3);
//      student.setName("张三");
//      cf.channel().writeAndFlush(student);
        
        // 等待直到连接中断
        cf.channel().closeFuture().sync();      
    }
            
    public static void main(String[] args) throws UnsupportedEncodingException, InterruptedException {
        new ClientNetty("127.0.0.1", Constant.serverSocketPort).action();
    }
        
}

package com.test.thread.netty;

import com.test.thread.utils.Constant;
import com.test.thread.xlh.Student;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
/**
 * 读取服务器返回的响应信息
 * @author zhb
 *
 */
public class ClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
       
        try {
            ByteBuf bb = (ByteBuf)msg;
            byte[] respByte = new byte[bb.readableBytes()];
            bb.readBytes(respByte);
            String respStr = new String(respByte, Constant.charset);
            System.err.println("client--收到响应:" + respStr);
            
            // 直接转成对象
//          handlerObject(ctx, msg);
            
        } finally{
            // 必须释放msg数据
            ReferenceCountUtil.release(msg);
            
        }
        
    }

    private void handlerObject(ChannelHandlerContext ctx, Object msg) {
        
        Student student = (Student)msg;
        System.err.println("server 获取信息:"+student.getId()+student.getName());
    }
    
    
    // 数据读取完毕的处理
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.err.println("客户端读取数据完毕");
    }
    
    // 出现异常的处理
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.err.println("client 读取数据出现异常");
        ctx.close();
    }

}

注意:

中间有参数(Constant)或者实体类(Stduent)等没有实际用处,可以自己替换。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270