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)等没有实际用处,可以自己替换。

要实现一个和Netty一样的功能Server端和client端,可以使用Java NIO来进行实现。下面是一个简单示例代码: ### Server端 ```java import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class NioServer { private Selector selector; private ByteBuffer readBuffer = ByteBuffer.allocate(1024); private ByteBuffer writeBuffer = ByteBuffer.allocate(1024); public NioServer(int port) { try { // 创建ServerSocketChannel对象并绑定端口 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(port)); serverSocketChannel.configureBlocking(false); // 创建Selector对象 selector = Selector.open(); // 将ServerSocketChannel注册到Selector上,并设置为监听OP_ACCEPT事件 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("Server started, listening on port " + port); } catch (IOException e) { e.printStackTrace(); System.exit(1); } } public void start() { try { while (true) { // 阻塞等待事件的发生 selector.select(); // 获取发生事件的SelectionKey集合 Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); iterator.remove(); if (selectionKey.isAcceptable()) { // ServerSocketChannel可以接收客户端连接 ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel(); SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); System.out.println("Client " + socketChannel.getRemoteAddress() + " connected."); } else if (selectionKey.isReadable()) { // SocketChannel可以读取数据 SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); readBuffer.clear(); int numRead = socketChannel.read(readBuffer); if (numRead == -1) { // 客户端关闭连接 selectionKey.cancel(); socketChannel.close(); System.out.println("Client " + socketChannel.getRemoteAddress() + " disconnected."); } else { // 处理读取到的数据 String request = new String(readBuffer.array(), 0, numRead); System.out.println("Received request from client " + socketChannel.getRemoteAddress() + ": " + request); socketChannel.register(selector, SelectionKey.OP_WRITE); } } else if (selectionKey.isWritable()) { // SocketChannel可以写入数据 SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); writeBuffer.clear(); String response = "Hello from server!"; writeBuffer.put(response.getBytes()); writeBuffer.flip(); socketChannel.write(writeBuffer); socketChannel.register(selector, SelectionKey.OP_READ); } } } } catch (IOException e) { e.printStackTrace(); System.exit(1); } } public static void main(String[] args) { NioServer server = new NioServer(8888); server.start(); } } ``` ### Client端 ```java import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class NioClient { private Selector selector; private ByteBuffer readBuffer = ByteBuffer.allocate(1024); private ByteBuffer writeBuffer = ByteBuffer.allocate(1024); public NioClient(String host, int port) { try { // 创建SocketChannel对象并连接服务器 SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress(host, port)); // 创建Selector对象 selector = Selector.open(); // 将SocketChannel注册到Selector上,并设置为监听OP_CONNECT事件 socketChannel.register(selector, SelectionKey.OP_CONNECT); System.out.println("Connecting to server " + host + ":" + port); } catch (IOException e) { e.printStackTrace(); System.exit(1); } } public void start() { try { while (true) { // 阻塞等待事件的发生 selector.select(); // 获取发生事件的SelectionKey集合 Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); iterator.remove(); if (selectionKey.isConnectable()) { // SocketChannel已连接到服务器 SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); if (socketChannel.isConnectionPending()) { socketChannel.finishConnect(); } socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_WRITE); System.out.println("Connected to server " + socketChannel.getRemoteAddress()); } else if (selectionKey.isReadable()) { // SocketChannel可以读取数据 SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); readBuffer.clear(); int numRead = socketChannel.read(readBuffer); if (numRead == -1) { // 服务器关闭连接 selectionKey.cancel(); socketChannel.close(); System.out.println("Server " + socketChannel.getRemoteAddress() + " disconnected."); } else { // 处理读取到的数据 String response = new String(readBuffer.array(), 0, numRead); System.out.println("Received response from server " + socketChannel.getRemoteAddress() + ": " + response); socketChannel.register(selector, SelectionKey.OP_WRITE); } } else if (selectionKey.isWritable()) { // SocketChannel可以写入数据 SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); writeBuffer.clear(); String request = "Hello from client!"; writeBuffer.put(request.getBytes()); writeBuffer.flip(); socketChannel.write(writeBuffer); socketChannel.register(selector, SelectionKey.OP_READ); } } } } catch (IOException e) { e.printStackTrace(); System.exit(1); } } public static void main(String[] args) { NioClient client = new NioClient("localhost", 8888); client.start(); } } ``` 这个示例代码实现了一个简单的NIO ServerClient,可以接收客户端连接,读取客户端发送的数据,并回复一条消息。虽然它没有Netty那么强大,但是可以作为一个参考来了解Java NIO的基本原理和使用方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值