Java实现在线聊天_java实现聊天功能,2024年最新附超全教程文档

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文


💌 使用介绍

🎁 业务数据绑定

资源绑定是指把业务相关的数据和Tcp连接(即ChannelContext)关联起来,譬如ChannelContext-A代表了用户张三,张三的userid是333

Tio.bindUser(ChannelContext-A, "333")  

t-io目前内置了4种资源绑定,譬如给group加前缀"ios- ",从而标记这个用户使用的是ios

Tio.bindGroup(ChannelContext-A, "333");
Tio.bindGroup(ChannelContext-A, "ios-" + "333");

内置的4种资源绑定方法中,一个ChannelContext是可以绑定到多个groupid的,其它三个绑定都是一对一或多对一的关系,也就是说一个ChannelContext可以同时属于group-a,group-b… …group-n

/**
   * 绑定业务id
   * @param channelContext
   * @param bsId
   */
  public static void bindBsId(ChannelContext channelContext, String bsId) {
      channelContext.tioConfig.bsIds.bind(channelContext, bsId);
  }
  /**
   * 绑定群组
   * @param channelContext
   * @param group
   */
  public static void bindGroup(ChannelContext channelContext, String group) {
      channelContext.tioConfig.groups.bind(group, channelContext);
  }
  /**
   * 绑定token
   * @param channelContext
   * @param token
   */
  public static void bindToken(ChannelContext channelContext, String token) {
      channelContext.tioConfig.tokens.bind(token, channelContext);
  }
  /**
   * 绑定用户
   * @param channelContext
   * @param userid
   */
  public static void bindUser(ChannelContext channelContext, String userid) {
      channelContext.tioConfig.users.bind(userid, channelContext);
  }

🤷‍♂️ 业务数据解绑

既然有绑定,就肯定会有解绑,这是个和绑定相反的操作

 /**
   * 解绑业务id
   * @param channelContext
   */
  public static void unbindBsId(ChannelContext channelContext) {
      channelContext.tioConfig.bsIds.unbind(channelContext);
  }
  /**
   * 与所有组解除解绑关系
   * @param channelContext
   */
  public static void unbindGroup(ChannelContext channelContext) {
      channelContext.tioConfig.groups.unbind(channelContext);
  }
  /**
   * 与指定组解除绑定关系
   * @param group
   * @param channelContext
   */
  public static void unbindGroup(String group, ChannelContext channelContext) {
      channelContext.tioConfig.groups.unbind(group, channelContext);
  }
  /**
   * 解除channelContext绑定的token
   * @param channelContext
   */
  public static void unbindToken(ChannelContext channelContext) {
      channelContext.tioConfig.tokens.unbind(channelContext);
  }
  //    org.tio.core.TioConfig.ipBlacklist
  /**
   * 解除channelContext绑定的userid
   * @param channelContext
   */
  public static void unbindUser(ChannelContext channelContext) {
      channelContext.tioConfig.users.unbind(channelContext);
  }
  /**
   * 解除userid的绑定。一般用于多地登录,踢掉前面登录的场景
   * @param tioConfig
   * @param userid
   */
  public static void unbindUser(TioConfig tioConfig, String userid) {
      tioConfig.users.unbind(tioConfig, userid);
  }

👀 异步发送

  • 异步发送,指的是业务层把Packet丢给t-io后立即返回,返回时Packet并没有被发送,而只是提交到了待发送队列
  • 异步发送都是以send开头的


🐱‍👤 阻塞发送

  • 阻塞发送:t-io把Packet送给对方后才返回
  • 阻塞发送都是以bSend开头的


🙌 获取ChannelContext

  • 前面的业务数据绑定,一个重要的目的就是要根据那些业务标识来获取ChannelContext,譬如你绑定了一个userid,那么后面就可以通过这个userid来获取ChannelContext
  • 获取ChannelContext的API都是以get开头的


🐱‍👓 断开连接和移除连接

断开连接都是以close开头的方法,指的是把当前已经连上的TCP连接断开掉

移除连接都是以remove开头的方法,指的是彻底抛弃这个连接

🚕Tio.remove()和Tio.close()的区别

**Tio.remove:**不管是用t-io做TCP服务器还是TCP客户端,调用Tio.remove()后,t-io都会彻底删除TCP连接并释放包括ChannelContext在内的所有和该条TCP连接对应的资源,当然那些和群组、Token的绑定关系也全部释放掉

Tio.close:

  • 如果是用t-io做TCP服务器,此方法等价于Tio.remove();
  • 如果是用t-io做TCP客户端
    • 该方法会断开当前TCP连接
    • 如果业务程序配置了重连策略(就是:ReconnConf):
      • t-io后面会进行重连操作,也就是说并不会抛弃该条TCP连接对应的ChannelContext对象
      • 如果该条TCP连接对应的ChannelContext对象绑定了groupid、userid、token、bsId,那么这些绑定关系会全部释放掉,在重连成功后,业务侧需要再次进行绑定
    • 如果业务程序没有配置重连策略(就是:ReconnConf),此方法等价于Tio.remove()

出现网络异常或其它异常时,业务需要主动调用这俩方法吗?

答:不需要的,出现任何网络异常,t-io都会释放掉该条TCP连接对应的全部资源,这也是t-io如此稳定的一大原因。网络编程的很多坑,都是源于资源没释放


🏴 拉黑IP

简单到极致,只需要一行代码

Tio.IpBlacklist.add(tioConfig, channelContext.getClientNode().getIp());

🚨 各种流量监控

🛴 ip的监控数据

ip的监控数据定义在IpStat中

private Date start = new Date();
/**
 * 当前统计了多久,单位:毫秒
 */
private long duration;
/**
 * 时长类型,单位:秒,譬如60,3600等
 */
private Long durationType;
/**
 * 客户端ip
 */
private String ip;
/**
 * 解码异常的次数
 */
private AtomicInteger decodeErrorCount = new AtomicInteger();
/**
 * 收到该IP连接请求的次数
 */
private AtomicInteger requestCount = new AtomicInteger();
/**
 * 本IP已发送的字节数
 */
private AtomicLong sentBytes = new AtomicLong();
/**
 * 本IP已发送的packet数
 */
private AtomicLong sentPackets = new AtomicLong();
/**
 * 本IP已处理的字节数
 */
private AtomicLong handledBytes = new AtomicLong();
/**
 * 本IP已处理的packet数
 */
private AtomicLong handledPackets = new AtomicLong();
/**
 * 处理消息包耗时,单位:毫秒
 */
private AtomicLong handledPacketCosts = new AtomicLong();
/**
 * 本IP已接收的字节数
 */
private AtomicLong receivedBytes = new AtomicLong();
/**
 * 本IP已接收了多少次TCP数据包
 */
private AtomicLong receivedTcps = new AtomicLong();
/**
 * 本IP已接收的packet数
 */
private AtomicLong receivedPackets = new AtomicLong();

使用步骤

  • 实现IpStatListener
package org.tio.showcase.websocket.server;
public class ShowcaseIpStatListener implements IpStatListener {
    @SuppressWarnings("unused")
    private static Logger log = LoggerFactory.getLogger(ShowcaseIpStatListener.class);
    public static final ShowcaseIpStatListener me = new ShowcaseIpStatListener();
    private ShowcaseIpStatListener() {
    }
    @Override
    public void onExpired(TioConfig tioConfig, IpStat ipStat) {
        //在这里把统计数据入库中或日志
//        if (log.isInfoEnabled()) {
//            log.info("可以把统计数据入库\r\n{}", Json.toFormatedJson(ipStat));
//        }
    }
    @Override
    public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect, IpStat ipStat) throws Exception {
//        if (log.isInfoEnabled()) {
//            log.info("onAfterConnected\r\n{}", Json.toFormatedJson(ipStat));
//        }
    }
    @Override
    public void onDecodeError(ChannelContext channelContext, IpStat ipStat) {
//        if (log.isInfoEnabled()) {
//            log.info("onDecodeError\r\n{}", Json.toFormatedJson(ipStat));
//        }
    }
    @Override
    public void onAfterSent(ChannelContext channelContext, Packet packet, boolean isSentSuccess, IpStat ipStat) throws Exception {
//        if (log.isInfoEnabled()) {
//            log.info("onAfterSent\r\n{}\r\n{}", packet.logstr(), Json.toFormatedJson(ipStat));
//        }
    }
    @Override
    public void onAfterDecoded(ChannelContext channelContext, Packet packet, int packetSize, IpStat ipStat) throws Exception {
//        if (log.isInfoEnabled()) {
//            log.info("onAfterDecoded\r\n{}\r\n{}", packet.logstr(), Json.toFormatedJson(ipStat));
//        }
    }
    @Override
    public void onAfterReceivedBytes(ChannelContext channelContext, int receivedBytes, IpStat ipStat) throws Exception {
//        if (log.isInfoEnabled()) {
//            log.info("onAfterReceivedBytes\r\n{}", Json.toFormatedJson(ipStat));
//        }
    }
    @Override
    public void onAfterHandled(ChannelContext channelContext, Packet packet, IpStat ipStat, long cost) throws Exception {
//        if (log.isInfoEnabled()) {
//            log.info("onAfterHandled\r\n{}\r\n{}", packet.logstr(), Json.toFormatedJson(ipStat));
//        }
    }
}
  • 初始化时添加监听器和监控时段
//注意的是:要保证下面两行代码的顺序,不能先addDuration()后setIpStatListener
serverTioConfig.setIpStatListener(ShowcaseIpStatListener.me);
serverTioConfig.ipStats.addDuration(Time.MINUTE_1 * 5);
  • OK了,什么时候拉黑IP以及把监控数据入库都在ShowcaseIpStatListener中实现哦

👀 获取TCP会话的流量数据

一个TCP会话对应一个ChannelContext对象,每个ChannelContext对象都有一个ChannelStat对象,定义如下

public final ChannelStat stat = new ChannelStat();

ChannelStat包含如下字段和方法(已经略过普通的getter和setter)

/**
     * 本次解码失败的次数
     */
    public int                    decodeFailCount                = 0;
    /**
     * 最近一次收到业务消息包的时间(一个完整的业务消息包,一部分消息不算)
     */
    public long                    latestTimeOfReceivedPacket    = SystemTimer.currTime;
    /**
     * 最近一次发送业务消息包的时间(一个完整的业务消息包,一部分消息不算)
     */
    public long                    latestTimeOfSentPacket        = SystemTimer.currTime;
    /**
     * 最近一次收到业务消息包的时间:收到字节就算
     */
    public long                    latestTimeOfReceivedByte    = SystemTimer.currTime;
    /**
     * 最近一次发送业务消息包的时间:发送字节就算
     */
    public long                    latestTimeOfSentByte        = SystemTimer.currTime;
    /**
     * ChannelContext对象创建的时间
     */
    public long                    timeCreated                    = SystemTimer.currTime;
    /**
     * 第一次连接成功的时间
     */
    public Long                    timeFirstConnected            = null;
    /**
     * 连接关闭的时间
     */
    public long                    timeClosed                    = SystemTimer.currTime;
    /**
     * 进入重连队列时间
     */
    public long                    timeInReconnQueue            = SystemTimer.currTime;
    /**
     * 本连接已发送的字节数
     */
    public final AtomicLong        sentBytes                    = new AtomicLong();
    /**
     * 本连接已发送的packet数
     */
    public final AtomicLong        sentPackets                    = new AtomicLong();
    /**
     * 本连接已处理的字节数
     */
    public final AtomicLong        handledBytes                = new AtomicLong();
    /**
     * 本连接已处理的packet数
     */
    public final AtomicLong        handledPackets                = new AtomicLong();
    /**
     * 处理消息包耗时,单位:毫秒
     * 拿这个值除以handledPackets,就是处理每个消息包的平均耗时
     */
    public final AtomicLong        handledPacketCosts            = new AtomicLong();
    /**
     * 本连接已接收的字节数
     */
    public final AtomicLong        receivedBytes                = new AtomicLong();
    /**
     * 本连接已接收了多少次TCP数据包
     */
    public final AtomicLong        receivedTcps                = new AtomicLong();
    /**
     * 本连接已接收的packet数
     */
    public final AtomicLong        receivedPackets                = new AtomicLong();
/**
     * 平均每次TCP接收到的字节数,这个可以用来监控慢攻击,配置PacketsPerTcpReceive定位慢攻击
     */
    public double getBytesPerTcpReceive() {
        if (receivedTcps.get() == 0) {
            return 0;
        }
        double ret = (double) receivedBytes.get() / (double) receivedTcps.get();
        return ret;
    }
    /**
     * 平均每次TCP接收到的业务包数,这个可以用来监控慢攻击,此值越小越有攻击嫌疑
     */
    public double getPacketsPerTcpReceive() {
        if (receivedTcps.get() == 0) {
            return 0;
        }
        double ret = (double) receivedPackets.get() / (double) receivedTcps.get();
        return ret;
    }
    /**
     * 处理packet平均耗时,单位:毫秒
     * @return
     */
    public double getHandledCostsPerPacket() {
        if (handledPackets.get() > 0) {
            return handledPacketCosts.get() / handledPackets.get();
        }
        return 0;
    }

🐱‍🚀 监听端口的流量和数据

TioConfig对象有个GroupStat成员,定义如下

public GroupStat groupStat = null;

GroupStat有如下一些字段和方法(去掉了简单的getter和setter)

/**
   * 关闭了多少连接
   */
  public final AtomicLong        closed                = new AtomicLong();
  /**
   * 接收到的消息包
   */
  public final AtomicLong        receivedPackets        = new AtomicLong();
  /**
   * 接收到的消息字节数
   */
  public final AtomicLong receivedBytes = new AtomicLong();
  /**
   * 处理了的消息包数
   */
  public final AtomicLong handledPackets = new AtomicLong();
  /**
   * 处理消息包耗时,单位:毫秒
   */
  public final AtomicLong handledPacketCosts = new AtomicLong();
  /**
   * 处理了多少字节
   */
  public final AtomicLong handledBytes = new AtomicLong();
  /**
   * 发送了的消息包数
   */
  public final AtomicLong sentPackets = new AtomicLong();
  /**
   * 发送了的字节数
   */
  public final AtomicLong sentBytes = new AtomicLong();
  /**
   * 本IP已接收了多少次TCP数据包
   */
  public final AtomicLong receivedTcps = new AtomicLong();
  /**
   * 平均每次TCP接收到的字节数,这个可以用来监控慢攻击,配置PacketsPerTcpReceive定位慢攻击
   */
  public double getBytesPerTcpReceive() {
      if (receivedTcps.get() == 0) {
          return 0;
      }
      double ret = (double) receivedBytes.get() / (double) receivedTcps.get();
      return ret;
  }
  /**
   * 平均每次TCP接收到的业务包数,这个可以用来监控慢攻击,此值越小越有攻击嫌疑
   */
  public double getPacketsPerTcpReceive() {
      if (receivedTcps.get() == 0) {
          return 0;
      }
      double ret = (double) receivedPackets.get() / (double) receivedTcps.get();
      return ret;
  }
  /**
   * 处理packet平均耗时,单位:毫秒
   * @return
   */
  public double getHandledCostsPerPacket() {
      if (handledPackets.get() > 0) {
          return handledPacketCosts.get() / handledPackets.get();
      }
      return 0;
  }

对于服务器端的groupStat,它是在ServerTioConfig类中的初始化代码在构造函数中,如下

this.groupStat = new ServerGroupStat();

对于客户端的groupStat,它是在ClientTioConfig类中的初始化代码在构造函数中,如下

this.groupStat = new ClientGroupStat();

获取GroupStat

GroupStat groupStat = tioConfig.groupStat;
//如果确认是服务器端,则可以用强转方式获得ServerGroupStat对象
ServerGroupStat serverGroupStat = (ServerGroupStat)tioConfig.groupStat;
//如果确认是客户端,则可以用强转方式获得ClientGroupStat对象
ClientGroupStat clientGroupStat = (ClientGroupStat)tioConfig.groupStat;

✨ T-io收发消息过程

Packet是用于表述业务数据结构的,我们通过继承Packet来实现自己的业务数据结构,对于各位而言,把Packet看作是一个普通的VO对象即可。

注意:不建议直接使用Packet对象,而是要继承Packet


🤣 TCP连接上下文

每一个tcp连接的建立都会产生一个ChannelContext对象,这是个抽象类,如果你是用t-io作tcp客户端,那么就是ClientChannelContext,如果你是用tio作tcp服务器,那么就是ServerChannelContext

用户可以把业务数据通过ChannelContext对象和TCP连接关联起来,像下面这样设置属性

ChannelContext.set(String key, Object value)

然后用下面的方式获取属性

ChannelContext.get(String key)

当然最最常用的还是用t-io提供的强到没对手的bind功能,譬如用下面的代码绑定userid

Tio.bindUser(ChannelContext channelContext, String userid)

然后可以通过userid进行操作,示范代码如下

//获取某用户的ChannelContext集合
SetWithLock<ChannelContext> set = Tio.getChannelContextsByUserid(tioConfig, userid);
//给某用户发消息
Tio.sendToUser(TioConfig, userid, Packet)

除了可以绑定userid,t-io还内置了如下绑定API

  • 绑定业务id
Tio.bindBsId(ChannelContext channelContext, String bsId)
  • 绑定token
Tio.bindToken(ChannelContext channelContext, String token)
  • 绑定群组
Tio.bindGroup(ChannelContext channelContext, String group)

ChannelContext对象包含的信息非常多,主要对象见下图

ChannelContext是t-io中非常重要的类,他是业务和连接的沟通桥梁!


📕 TioConfig

  • 场景:我们在写TCP Server时,都会先选好一个端口以监听客户端连接,再创建N组线程池来执行相关的任务,譬如发送消息、解码数据包、处理数据包等任务,还要维护客户端连接的各种数据,为了和业务互动,还要把这些客户端连接和各种业务数据绑定起来,譬如把某个客户端绑定到一个群组,绑定到一个userid,绑定到一个token等。
  • TioConfig

就是解决以上场景的:配置线程池、监听端口,维护客户端各种数据等的。

  • TioConfig是个抽象类
    • 如果你是用tio作tcp客户端,那么你需要创建ClientTioConfig对象
      • 服务器端对应一个ClientTioConfig对象
    • 如果你是用tio作tcp服务器,那么你需要创建ServerTioConfig
      • 一个监听端口对应一个ServerTioConfig ,一个jvm可以监听多个端口,所以一个jvm可以有多个ServerTioConfig对象

😀 消息来往监听

TioListener是处理消息的核心接口,它有两个子接口:TioClientListener和TioServerListener

  • 当用tio作tcp客户端时需要实现TioClientListener
  • 当用tio作tcp服务器时需要实现TioServerListener

它主要定义了如下方法

public interface TioListener {
    /**
     * 建链后触发本方法,注:建链不一定成功,需要关注参数isConnected
     * 
     * @param channelContext
     * @param isConnected    是否连接成功,true:表示连接成功,false:表示连接失败
     * @param isReconnect    是否是重连, true: 表示这是重新连接,false: 表示这是第一次连接
     * @throws Exception
     */
    public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect) throws Exception;
    /**
     * 原方法名:onAfterDecoded 解码成功后触发本方法
     * 


**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
![img](https://img-blog.csdnimg.cn/img_convert/3cead7735152de0207d98df989e7b931.png)

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

作tcp服务器时需要实现TioServerListener


它主要定义了如下方法



public interface TioListener {
/**
* 建链后触发本方法,注:建链不一定成功,需要关注参数isConnected
*
* @param channelContext
* @param isConnected 是否连接成功,true:表示连接成功,false:表示连接失败
* @param isReconnect 是否是重连, true: 表示这是重新连接,false: 表示这是第一次连接
* @throws Exception
/
public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect) throws Exception;
/
*
* 原方法名:onAfterDecoded 解码成功后触发本方法
*

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
[外链图片转存中…(img-QPmiUw4S-1713301872718)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 9
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值