1-4 基于Netty实现简单的聊天功能

主要功能

1、(Clinet)断点要能重连

2、Server接收Client发送的消息

3、人数统计

 

主要使用 

ChannelInitializer、ChannelInboundHandlerAdapter

 

注意点

1、在pipeline()使用Handler处理消息时需要使用正确的Handler顺序(错误的顺序会读不出来 可能不会走到channelRead()方法)

2、在channelRead中使用 ctx.write/writeAndFlush方法时 不要复用Unpooled.buffer() 每次都重新写入 复用会报引数错误rcf:0

正确:

ctx.writeAndFlush(Unpooled.buffer().writeBytes("aaa".getBytes()));

错误:

ByteBuf buf = Unpooled.buffer();
ctx.writeAndFlush(buf.writeBytes("aaa".getBytes()));

3、Client使用EventLoop.addListenter时复用之前的eventLoop对象 如果不复用客户端会达到最大的线程数就不新增了

// 使用futureListener.channel(),eventLoop()的EventLoop对象
final EventLoop eventLoop = futureListener.channel().eventLoop();

4、也可以继承SimpleChannelInboundHandler在channelRead0()中通过ctx.channel()得到Channel,然后就通过ThreadLocal变量或其他方法,只要能把这个Channel保存住就行

顺便提一下 SimpleChannelInboundHandler 继承 ChannelInboundHandlerAdapter 

主要区别在于在使用channelRead方法时 Simple类的channelRead多了一个finally块 release了Bytebuf对象(可能会造成某些情况具体还没遇到过)

ReferenceCountUtil.release(msg);

IMClient.java

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j
public class IMClient {

    static final int PORT = 8001;
    static final String HOST = "127.0.0.1";

    private static EventLoopGroup GROUP = new NioEventLoopGroup();
    private static IMClientChannelInitializer imClientChannelInitializer= new IMClientChannelInitializer();

    //1
    public void start() throws InterruptedException {
        connection(new Bootstrap(),GROUP);
    }

    public void connection(Bootstrap bootstrap, final EventLoopGroup group) throws InterruptedException {
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.SO_KEEPALIVE,true)
                .remoteAddress(HOST,PORT)
                .handler(imClientChannelInitializer);
        ;


        ChannelFuture future = bootstrap.connect().addListener((ChannelFuture futureListener)->{
            final EventLoopGroup eventLoopGroup = futureListener.channel().eventLoop();
            if(futureListener.isSuccess()){
                log.info("客户端 "+futureListener.channel().localAddress()+" 连接成功!");
            }else {
                log.info("客户端 "+futureListener.channel().localAddress()+" 连接失败! 10s 后重连 ... ...");

            };
            eventLoopGroup.schedule(()->{
                // 使用futureListener.channel(),eventLoop()的EventLoop对象
                final EventLoop eventLoop = futureListener.channel().eventLoop();
                try {
                    connection(new Bootstrap(),eventLoop);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },10,TimeUnit.SECONDS);
        });
        future.channel().closeFuture().sync();
    }

    public static void main(String ...args) throws InterruptedException {
        new IMClient().start();
    }
}

IMClientChannelInitializer.java

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class IMClientChannelInitializer  extends ChannelInitializer {
    int i= 0;

    private static  IMClientHandler imClientHandler= new  IMClientHandler();
    @Override
    protected void initChannel(Channel ch) throws Exception {
//        ch.pipeline().addLast("decoder", new StringDecoder());
//        ch.pipeline().addLast("encoder", new StringEncoder());
        ch.pipeline().write(ch.localAddress()+": 已上线");
        ch.pipeline().write(ch.localAddress()+"Hello");
        ch.write(("客户端发出第"+(++i)+"条消息").getBytes());
        log.info("客户端发出第"+i+"条消息");
        ch.pipeline().addLast("handler",imClientHandler);
        log.info("Client init !");
        ch.flush();
    }
}

IMClientHandler.java

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@ChannelHandler.Sharable
public class IMClientHandler extends ChannelInboundHandlerAdapter {
    private int i =0;
    User user = new User();

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

    if(!(msg instanceof ByteBuf)){
        log.error("未知数据 : "+msg.toString());
        return;
    }
    ByteBuf in = (ByteBuf) msg;
    log.info("Client received: "+ByteBufUtil.hexDump(in.readBytes(in.readableBytes())));
    ReferenceCountUtil.release(msg);
}

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        user.chat("Tom");
        Long firstTime = System.currentTimeMillis();
        Long secondTime =firstTime;
        int i = 0;
        //当消息在5条或间隔时间不大于5s时继续读
        while(i<5&&(secondTime-firstTime<=500)){
            if(user.getlist().iterator().hasNext()){
                //不要复用对象 ByteBuf buf = Unpooled.buffer()会出现计数rcf0问题
                ByteBuf buf = Unpooled.buffer();
                ctx.writeAndFlush(Unpooled.buffer().writeBytes(user.getlist().getFirst().getBytes()));
                user.getlist().removeFirst();
            }
            i++;
            secondTime=System.currentTimeMillis();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

IMServer.java

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class IMServer {
    static final int PORT = 8001;
    private static EventLoopGroup BOSS_GROUP = new NioEventLoopGroup();
    private static EventLoopGroup WORKER_GROUP = new NioEventLoopGroup();
    static IMServerHandler IMSERVER_HANDLER= new IMServerHandler();
    private static IMServerChannelInitializer IMSERVER_CHANNEL_INITIALIZER= new IMServerChannelInitializer();

    public void start() throws InterruptedException {
        connection(new ServerBootstrap(),BOSS_GROUP,WORKER_GROUP);
    }

    public void connection(ServerBootstrap bootstrap,EventLoopGroup boss,EventLoopGroup worker) throws InterruptedException {
        bootstrap.localAddress(PORT)
        .group(boss,worker)
        .channel(NioServerSocketChannel.class)
        .childHandler(new ChannelInitializer() {
            @Override
            protected void initChannel(Channel ch) throws Exception {
                ch.pipeline().addLast(IMSERVER_CHANNEL_INITIALIZER);
            }
        });
        //可设置是否开启自动读
        //.childOption(ChannelOption.AUTO_READ,true);

        ChannelFuture future =bootstrap.bind().sync();
        future.channel().closeFuture().sync();
        BOSS_GROUP.shutdownGracefully();
        WORKER_GROUP.shutdownGracefully();
    }

    public static void main(String ...args) throws InterruptedException {
        new IMServer().start();
    }
}

IMServerChannelInitializer.java

import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class IMServerChannelInitializer extends ChannelInitializer {
    @Override
    protected void initChannel(Channel ch) throws Exception {
        //方法一
        //ch.pipeline().addLast("decoder", new StringDecoder());
        //ch.pipeline().addLast("encoder", new StringEncoder());
        //需要改写handler对接受msg的处理方式
        // ch.pipeline().addLast(IMServer.IMSERVER_HANDLER);

        //方法二
        ch.pipeline().addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE,Unpooled.copiedBuffer("$_".getBytes())));
        ch.pipeline().addLast(IMServer.IMSERVER_HANDLER);
    }

IMServerHandler.java

import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.Set;

@Slf4j
@ChannelHandler.Sharable
public class IMServerHandler extends ChannelInboundHandlerAdapter {
    //存储客户状态
    private static Set set = new HashSet<>();
    private int i =0;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        byte[] bytes = new byte[buf.readableBytes()];
        buf.readBytes(bytes);
        log.info("Server 收到 : "+new String(bytes,"UTF-8")+"在线 "+set.size()+" 人");
        ++i;
        ctx.writeAndFlush(("Server accept "+i+" message").getBytes());;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        set.add(insocket.getAddress().toString());
    }

}

User.java

import lombok.extern.slf4j.Slf4j;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;

@Slf4j
public class User {
    int i = 0;
    //消息列表
    public static LinkedList MESSAGE_LIST = new LinkedList();

    //DateFormat
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    //生产消息
    public void chat(String userName ) throws InterruptedException {
        while (i!=100){
            MESSAGE_LIST.add(DATE_FORMAT.format(new Date())+"\t"+userName+" ; "+(++i)+"$_");
            System.out.println(MESSAGE_LIST.size());
        }
    }


    public LinkedList getlist(){
        return MESSAGE_LIST;
    }
    
}


pom.java



    4.0.0

    com.example
    netty
    1.0-SNAPSHOT
    
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                
                    7
                    7
                
            
        
    

    
        
        
            io.netty
            netty-all
            4.1.37.Final
        

        
            org.slf4j
            slf4j-api
            1.7.21
        
        
            org.projectlombok
            lombok
            1.18.0
        
        
            org.junit.jupiter
            junit-jupiter-api
            5.2.0
            test
        
        
        
            ch.qos.logback
            logback-classic
            1.2.3
        
            
                org.springframework.boot
                spring-boot-starter-web
            
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            org.springframework.boot
            spring-boot-starter-web
        


    
    
        
            alimaven
            aliyun maven
            http://maven.aliyun.com/nexus/content/groups/public/
        
    

 

你可能感兴趣的:(Netty)