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<String> 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<String> MESSAGE_LIST = new LinkedList<String>();

    //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<String> getlist(){
        return MESSAGE_LIST;
    }
    
}


pom.java

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>netty</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>7</source>
                    <target>7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/io.netty/netty -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.37.Final</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.0</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.2.0</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


    </dependencies>
    <repositories>
        <repository>
            <id>alimaven</id>
            <name>aliyun maven</name>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        </repository>
    </repositories>
</project>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值