使用Netty编写一个多人聊天程序(下)-客户端实现和验收

引言

上篇文章中,详细阐述了服务端核心功能的设计思路和实现逻辑。与服务端相比起来,客户端的实现就比较简单了。Netty为开发者尽最大可能保证了编程模型的一致性。服务端和客户端的区别仅仅只是在于Channel具体实现类和BootStrap引导类的区别。

客户端的代码托管于:https://gitee.com/eric_ds/learnNetty/tree/master/client

命令编码

和服务端一样,客户端需要发送消息,首要考虑的问题也是对命令对象Command的二进制编码问题。每个命令对象本身内部最清晰自身结构,因此这里采用和服务端编码Receive对象一样的思路,在Command接口上增加writeToBuf方法,将编码的部分职责下放到具体的Command对象中,因此,命令编码的handler可以编写为如下形式。

public class CommandEncoder extends ChannelOutboundHandlerAdapter
{
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception
    {
        if (msg instanceof Command)
        {
            Command command = (Command) msg;
            ByteBuf buffer  = ctx.alloc().buffer();
            buffer.writerIndex(4);
            command.writeToBuf(buffer);
            int writerIndex = buffer.writerIndex();
            //消息协议前四个字节是整型变量,需要计算报文体长度
            buffer.writerIndex(0).writeInt(writerIndex - 4).writerIndex(writerIndex);
            ctx.write(buffer, promise);
        }
        else
        {
            throw new IllegalArgumentException();
        }
    }
}

响应解码

客户端的响应解码的职能和服务端对命令的解码职能很接近。在服务端的处理方式中,采用了一个命令解码器,在这个类中根据协议类型字段而对后续的字节进行解析处理构建Command对象。这种写法的缺点主要在于需要硬编码所有的命令类型,在新增或者修改命令对象的时候不太方便。参考命令编码的思路,我们可以将响应解码的具体内容放在具体的Receive对象中进行处理。也就是为Receive接口新增方法readFromBuf。在这个基础上,还需要解决的问题就是如何根据协议头的消息类型构建正确的Receive实现类。解决办法倒也简单,可以创建一个EnumMap,放入ReceiveType和对应Receive实现类的class对象,通过反射来构建。而这EnumMap可以在初始化的时候被传入。这样,后续消息格式变更或者消息新增,可以简单添加元素到EnumMap中实现,解码器则无需更改。

按照上面的思路,解码器的代码可以写为

public class ReceiveDecoder extends ChannelInboundHandlerAdapter
{
    private EnumMap<ReceiveType, Class<? extends Receive>> enumMap;


    public ReceiveDecoder(EnumMap<ReceiveType, Class<? extends Receive>> enumMap)
    {
        this.enumMap = enumMap;
    }


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
    {
        ByteBuf                  buffer      = (ByteBuf) msg;
        byte                     b           = buffer.readByte();
        ReceiveType              receiveType = ReceiveType.value(b);
        Class<? extends Receive> aClass      = enumMap.get(receiveType);
        Receive                  receive     = aClass.newInstance();
        receive.readFromBuf(buffer);
        ctx.fireChannelRead(receive);
    }
}

除了响应解码器外,在响应解码之前,首先是进行报文的拆包处理。这个拆包的思路和服务端中对命令的报文拆包思路是相同的,都是使用LengthFieldBasedFrameDecoder,从报文头读取长度,进而确定报文体的内容。

注册和登录示例

在完成命令编码和响应解码的基础上,我们就可以先完成一个注册和登录的小例子了。首先来看下服务端的引导应用,如下

public class Server
{
    public static void main(String[] args) throws InterruptedException
    {
        DAOFactory      daoFactory      = new MemDAOFactory();
        final Router    router          = new RouterImpl(daoFactory.getGroupDAO(), daoFactory.getRelationDAO());
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup());
        //handler方法传入的处理器工作在服务端监听链接上
        serverBootstrap.handler(new ChannelInboundHandlerAdapter()
        {
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
            {
                System.out.println("msg对象是一个SocketChannel:" + (msg instanceof SocketChannel));
                ctx.fireChannelRead(msg);
            }
        });
        final EnumMap<CommandType, CommandHandler.CommandProcessor> enumMap = new EnumMap<CommandType, CommandHandler.CommandProcessor>(CommandType.class);
        enumMap.put(CommandType.LOGIN, new LoginProcessor(daoFactory.getClientDAO(), router));
        enumMap.put(CommandType.REGISTER, new RegisterProcessor(daoFactory.getClientDAO(), router));
        enumMap.put(CommandType.CREATE_GROUP, new CreateGroupProcessor(daoFactory.getGroupDAO(), daoFactory.getRelationDAO(), ro

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

<p> 通过本专刊的学习,对网络开发所需掌握的基础理论知识会更加牢固,对网络应用涉及的线程模型,设计模式,高性能架构等更加明确。通过对Netty的源码深入讲解,使得读者对Netty达到“知其然更之所以然”的程度。在遇到一些线上的问题时,具备了扎实理论功底的情况,可以有的放矢而不会显得盲目。 本专刊购买后即可解锁所有章节,故不可以退换哦~ </p> <p> <br /> </p>

全部评论

相关推荐

链接
海梨花:我说话难听,你这简历跟没写没啥区别,搜搜别人的简历,用心写,不要随随便便就结束了
点赞 评论 收藏
分享
10-22 12:03
山东大学 Java
程序员小白条:26届一般都得有实习,项目可以随便写的,如果不是开源社区的项目,随便包装,技术栈也是一样,所以本质应该找学历厂,多投投央国企和银行,技术要求稍微低一点的,或者国企控股那种,纯互联网一般都得要干活
应届生简历当中,HR最关...
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务