通讯框架 t-io 学习——给初学者的Demo:ShowCase设计分析

前言

最近闲暇时间研究Springboot,正好需要用到即时通讯部分了,虽然springboot 有websocket,但是我还是看中了 t-io框架。看了部分源代码和示例,先把helloworld敲了一遍,又把showcase代码敲了一遍,决定做一个总结。本篇文章并不会解释T-io是如何通讯的,而是从showcase这个给t-io初学者写的demo分析showcase的设计思路,以及为什么这么设计等。不过都是我个人理解,疏漏之处在所难免。

T-io简单介绍

t-io 源代码:https://gitee.com/tywo45/t-io/

代码结构很简单,首先我们知道通讯有客户端(client)和服务端(server).它们之间又会存在一些重复的业务处理逻辑,于是就有common的存在。那么整体的showcase下包含三部分:client,server,common。在代码分析之前呢,先简单介绍一下关于使用tio实现通讯的基础思路。我从tio官方截了两个图:

server端,我们只看红色部分。没错,要实现AioHandler中的encode,decode,handler方法。然后创建ServerGroupContext,最后调用start方法开启服务端。
在这里插入图片描述

client端,同样也需要实现AioHandler中的encode,decode,handler方法。不过客户端可以看到,多了一个心跳包(heartbeatPacket)。在这里插入图片描述

基础分析

下图是本demo的项目结构图:
在这里插入图片描述
  我们知道,最基本的通讯流程就是:客户端发送消息到服务端,服务端处理之后,返回响应结果到客户端;或者服务端主动推送消息到客户端
  需要说明的两点:

  1. 因为客户端发送的消息格式不固定,所以t-io把编解码的权利交给开发者,开发者可以系统业务需要自定义消息结构。
  2. 一般来说,在同一个系统中,请求数据肯定都是完全的数据结构和编解码方式,所以就往往就可以在common中定义一个消息包类一个基础的消息处理类。

当然,由于消息类型的不同,具体的handler方法实现还是得区分不同的处理。

基础消息包类 ShowcasePacket :

它是贯穿整个通讯流程的统一数据结构。我们看一下代码:

public class ShowcasePacket extends Packet {
        
    private byte type;//消息类型(用于消息处理)
    private byte[] body;//消息体
}

其中,type消息类型是对应在common中的Type接口,它定义了不同的消息类型。

 1 public interface Type {
 2 
 3     /**
 4      * 登录消息请求
 5      */
 6     byte LOGIN_REQ = 1;
 7     /**
 8      * 登录消息响应
 9      */
10     byte LOGIN_RESP = 2;
11 
12     /**
13      * 进入群组消息请求
14      */
15     byte JOIN_GROUP_REQ = 3;
		//。。。省略部分代码
44 }

基础消息处理器ShowcaseAbsAioHander:

代码不少,主要是为了分析结构,所以直接上图了。具体的参见下面的处理器分析章节。
在这里插入图片描述

处理器分析

1. t-io的处理器的接口、类关系图:

在这里插入图片描述
首先,AioHandler,ClientAioHandler,ServerAioHandler 都是t-io中的Hander接口。
ShowcaseAbsAioHander抽象了在服务端和客户端中分别有一个对应的实现类:ShowcaseClientAioHandlerShowCaseServerAioHandler
ShowCaseServerAioHandler的代码:

   @Override
    public void handler(Packet packet, ChannelContext channelContext) throws Exception {
        //接收到的消息包
        ShowcasePacket showcasePacket = (ShowcasePacket) packet;
        //获取消息类型
        Byte type = showcasePacket.getType();
        //从handleMap中获取到具体的消息处理器
        AbsShowcaseBsHandler<?> showcaseBsHandler = handlerMap.get(type);
     //服务端的处理可能由于type类型不正确拿不到相应的消息处理器,直接return不给客户端响应。(或者统一返回错误消息)
        //处理消息
        showcaseBsHandler.handler(showcasePacket, channelContext);
        return;
    }

ShowcaseClientAioHandler的代码:

	private static ShowcasePacket heartbeatPacket = new ShowcasePacket(Type.HEART_BEAT_REQ, null);

	/**
	 * 处理消息
	 * @param packet //接收到的消息包
	 * @param channelContext
	 * @return
	 * @throws Exception
	 */
	@Override
	public Object handler(ShowcasePacket packet, ChannelContext<ShowcaseSessionContext, ShowcasePacket, Object> channelContext) throws Exception
	{
		//获取消息类型
		Byte type = packet.getType();
		//从handleMap中获取到具体的消息处理器
		AbsShowcaseBsHandler<?> showcaseBsHandler = handlerMap.get(type);
		showcaseBsHandler.handler(packet, channelContext);
		return null;
	}

	/** 
	 * 此方法如果返回null,框架层面则不会发心跳;如果返回非null,框架层面会定时发本方法返回的消息包
	 */
	@Override
	public ShowcasePacket heartbeatPacket()
	{
		return heartbeatPacket;
	}

说明:

我们可以看到这两个类的实现差不多,都是做基础消息处理,并且根据消息类型创建(获取)不同的消息处理器(handler)
client的处理器比server的处理器多了一个心跳包。

2. AbsShowcaseBsHandler的实现类、接口体系

在这里插入图片描述
  可以看到,消息处理类使用了泛型。AbsShowcaseBsHandler 实现了ShowcaseBsHandlerIntf 中的handle方法,且定义并调用了另一个抽象的 handle方法,方法入参中多了 T bsBody 参数。
  由此可以知道,对消息的ling实现方式——将消息字符转换为具体的消息对象,然后在调用具体的消息处理器处理相应的消息逻辑。代码如下:

public abstract class AbsShowcaseBsHandler<T extends BaseBody> implements ShowcaseBsHandlerIntf {
    private static Logger log = LoggerFactory.getLogger(AbsShowcaseBsHandler.class);

    /**
     *
     * @author tanyaowu
     */
    public AbsShowcaseBsHandler() {
    }
    //抽象方法,具体是什么类型的由子类实现
    public abstract Class<T> bodyClass();

    @Override
    public Object handler(ShowcasePacket packet, ChannelContext channelContext) throws Exception {
    	//省略部分代码
        //调用具体的消息处理的实现
        return handler(packet, bsBody, channelContext);
    }

    //抽象方法,由每个消息处理类来实现具体的消息处理逻辑
    public abstract Object handler(ShowcasePacket packet, T bsBody, ChannelContext channelContext) throws Exception;
}

以登录消息为例,分析具体消息处理流程

  1. 首先客户端发起登录请求。(比如用户名:panzi,密码:123123)
	//封装登录请求体对象
	LoginReqBody loginReqBody = new LoginReqBody();
    loginReqBody.setLoginname(loginname);
    loginReqBody.setPassword(password);
    
  //具体的消息都会包装在ShowcasePacket中(byte[] body)
    ShowcasePacket reqPacket = new ShowcasePacket();
  //这里呢就是传相应的消息类型
    reqPacket.setType(Type.LOGIN_REQ);
    reqPacket.setBody(Json.toJson(loginReqBody).getBytes(ShowcasePacket.CHARSET));
    
  //调用 t-io 发送消息方法
    Aio.send(clientChannelContext, reqPacket);
  1. 服务端收到消息。
    就这两个步骤。
    这时候我们回过头看 ShowcaseServerAioHandler 中的 handle方法。(上文中有介绍)此时消息类型为Type.LOGIN_REQ
    //初始化定義handleMap,存儲具体的消息处理器
    private static Map<Byte, AbsShowcaseBsHandler<?>> handlerMap = new HashMap<>();
    static {
        handlerMap.put(Type.GROUP_MSG_REQ, new GroupMsgReqHandler());
        handlerMap.put(Type.HEART_BEAT_REQ, new HeartbeatReqHandler());
        handlerMap.put(Type.JOIN_GROUP_REQ, new JoinGroupReqHandler());
        handlerMap.put(Type.LOGIN_REQ, new LoginReqHandler());
        handlerMap.put(Type.P2P_REQ, new P2PReqHandler());
    }

看到上面这段代码,大家可以很容易的想到,程序中肯定会从map中取出LoginReqHandler这个处理器的操作,并使用这个处理器来处理请求的消息。
我们继续看LoginReqHandler的具体实现:

@Override
public Object handler(ShowcasePacket packet, LoginReqBody bsBody, ChannelContext channelContext) throws Exception {
    log.info("收到登录请求消息:{}", Json.toJson(bsBody));
    //定义响应对象
    LoginRespBody loginRespBody = new LoginRespBody();
    //模拟登录,直接给Success
    loginRespBody.setCode(JoinGroupRespBody.Code.SUCCESS);
    //返回一个模拟的token
    loginRespBody.setToken(newToken());
    
    //登录成功之后绑定用户
    String userid = bsBody.getLoginname();
    Aio.bindUser(channelContext, userid);

    //给全局Context设置用户ID
    ShowcaseSessionContext showcaseSessionContext = (ShowcaseSessionContext) channelContext.getAttribute();
    showcaseSessionContext.setUserid(userid);

    //构造响应消息包
    ShowcasePacket respPacket = new ShowcasePacket();
    //响应消息类型为 Type.LOGIN_RESP
    respPacket.setType(Type.LOGIN_RESP);
    //将loginRespBody转化为byte[]
    respPacket.setBody(Json.toJson(loginRespBody).getBytes(ShowcasePacket.CHARSET));
    //发送响应到客户端(告诉客户端登录结果)
    Aio.send(channelContext, respPacket);

    return null;
}

再往后就到了客户端处理。客户端处理时也可以拿到具体的处理器(LoginRespHandler)。我们看一下客户端消息处理代码:

  @Override
public Object handler(ShowcasePacket packet, LoginRespBody bsBody, ChannelContext channelContext) throws Exception {
    System.out.println("收到登录响应消息:" + Json.toJson(bsBody));
    if (LoginRespBody.Code.SUCCESS.equals(bsBody.getCode())) {
        ShowcaseSessionContext showcaseSessionContext = (ShowcaseSessionContext) channelContext.getAttribute();
        showcaseSessionContext.setToken(bsBody.getToken());
        System.out.println("登录成功,token是:" + bsBody.getToken());
    }
    return null;
}

这样,整个消息流程就结束了。为了更清晰一点,我们将它以流程图的形式展现。
在这里插入图片描述
参考自作者:丶Pz的文章:通讯框架 t-io 学习——给初学者的Demo:ShowCase设计分析

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
192.168.85.1 - - [26/Jun/2022:06:07:07 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 24 192.168.85.1 - - [26/Jun/2022:06:07:11 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 24 192.168.85.1 - - [26/Jun/2022:06:07:11 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 24 192.168.85.1 - - [26/Jun/2022:06:07:11 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 200 12925 192.168.85.1 - - [26/Jun/2022:06:07:11 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 200 12925 192.168.85.1 - - [26/Jun/2022:06:07:11 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 200 12925 192.168.85.1 - - [26/Jun/2022:06:07:11 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 200 14 192.168.85.1 - - [26/Jun/2022:06:08:06 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 15 192.168.85.1 - - [26/Jun/2022:06:08:16 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 1227 192.168.85.1 - - [26/Jun/2022:06:10:15 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 79 192.168.85.1 - - [26/Jun/2022:06:13:25 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 404 752 192.168.85.1 - - [26/Jun/2022:06:16:42 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 35 192.168.85.1 - - [26/Jun/2022:06:16:57 -0400] "GET //struts2-showcase/hhh.jsp HTTP/1.1" 403 642 192.168.85.1 - - [26/Jun/2022:06:18:55 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 35 192.168.85.1 - - [26/Jun/2022:06:19:02 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 35 192.168.85.1 - - [26/Jun/2022:06:19:09 -0400] "GET //struts2-showcase/hhh1.jsp HTTP/1.1" 403 642 192.168.85.1 - - [26/Jun/2022:06:19:34 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 400 192.168.85.1 - - [26/Jun/2022:06:20:37 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 5 192.168.85.1 - - [26/Jun/2022:06:20:42 -0400] "GET //struts2-showcase/hhh1.jsp HTTP/1.1" 403 642 192.168.85.1 - - [26/Jun/2022:06:20:46 -0400] "GET //struts2-showcase/hhh.jsp HTTP/1.1" 403 642 192.168.85.1 - - [26/Jun/2022:06:20:51 -0400] "GET /struts2-showcase/hhh.jsp HTTP/1.1" 403 642
07-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值