基于Netty,从零开发IM(三):编码实践篇(群聊功能)

本文由作者“大白菜”分享,有较多修订和改动。注意:本系列是给IM初学者的文章,IM老油条们还望海涵,勿喷!

1、引言

接上两篇《IM系统设计篇》、《编码实践篇(单聊功能)》,本篇主要讲解的是通过实战编码实现IM的群聊功能,内容涉及群聊技术实现原理、编码实践等知识。

2、写在前面

建议你在阅读本文之前,务必先读本系列的前两篇《IM系统设计篇》、《编码实践篇(单聊功能)》,在着重理解IM系统的理论设计思路之后,再来阅读实战代码则效果更好。

最后,在开始本文之前,请您务必提前了解Netty的相关基础知识,可从本系列首篇《IM系统设计篇》中的“知识准备”一章开始。

3、系列文章

本文是系列文章的第3篇,以下是系列目录:

4、本篇概述

在上篇《编码实践篇(单聊功能)》中,我们主要实现了IM的单聊功能,本节主要是实现IM群聊功能。

本篇涉及的群聊核心功能,大致如下所示:

  • 1)登录:每个客户端连接服务端的时候,都需要输入自己的账号信息,以便和连接通道进行绑定;
  • 2)创建群组:输入群组 ID 和群组名称进行创建群组。需要先根据群组 ID 进行校验,判断是否已经存在了;
  • 3)查看群组:查看目前已经创建的群组列表;
  • 4)加入群组:主要参数是群组 ID 和用户 ID,用户 ID 只需从 Channel 的绑定属性里面获取即。主要是判断群组 ID 是否存在,如果存在还需要判断该用户 ID 是否已经在群组里面了;
  • 5)退出群组:主要是判断群组 ID 是否存在,如果存在则删除相应的关系;
  • 6)查看组成员:根据群组 ID 去查询对应的成员列表;
  • 7)群发消息:选择某个群进行消息发送,该群下的成员都能收到信息。主要判断群组 ID 是否存在,如果存在再去获取其对应的成员列表。

5、群聊原理

其实群聊和单聊,整体上原理是一样的,只是做了一下细节上的升级。

在首篇《IM系统设计篇》的“6、IM群聊思路设计”设计部分也做了详细的说明了。

群聊的大概流程就是:根据群组 ID 查找到所有的成员集合,然后再遍历找到每个成员对应的连接通道。

具体的群聊架构思路如下图:

如上图所示,群聊通讯流程技术原理如下:

  • 1)群聊和单聊整体上的思路一致:需要保存每个用户和通道的对应关系,方便后期通过用户 ID 去查找到对应的通道,再跟进通道推送消息;
  • 2)群聊把消息发送给群员的原理:其实很简单,服务端再保存另外一份映射关系,那就是聊天室和成员的映射关系。发送消息时,首先根据聊天室 ID 找到对应的所有成员,然后再跟进各个成员的 ID 去查找到对应的通道,最后由每个通道进行消息的发送;
  • 3)群成员加入某个群聊聊的时候:往映射表新增一条记录,如果成员退群的时候则删除对应的映射记录。

6、运行效果

补充说明:因为本系列文章主要目的是引导IM初学者在基于Netty的情况下,如何一步一步从零写出IM的逻辑和思维能力,因而为了简化编码实现,本篇中编码实现的客户端都是基于控制台实现的(希望不要被嫌弃),因为理解技术的本质显然比炫酷的外在表现形式更为重要。

用户登录效果图:

群组操作效果图:

7、实体定义实战

7.1 服务端实体

服务端映射关系的管理,分别是:

  • 1)登录信息(用户 ID 和通道);
  • 2)群组信息(群组 ID 和群组成员关系)。

主要通过两个 Map 去维护,具体如下:

public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

    private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

    private static Map<Integer, Group> groups=new HashMap<Integer, Group>();

}

//组和成员列表关系实体

@Data

public class Group implements Serializable {

    private String groupName;

    private List<GroupMember> members=new ArrayList<GroupMember>();

}

//成员和连接通道的关系实体

public class GroupMember implements Serializable {

    private Integer userid;

    private Channel channel;

}

7.2 实体和指令关系

我们准备好相应的实体,以及实体和指令的映射关系,具体如下所示:

private static Map<Byte, Class<? extends BaseBean>> map=new HashMap<Byte,Class<? extends BaseBean>>();

    static{

        //登录的请求和响应实体

        map.put(1, LoginReqBean.class);

        map.put(2, LoginResBean.class);

        //创建群组的请求和响应实体

        map.put(3, GroupCreateReqBean.class);

        map.put(4, GroupCreateResBean.class);

        //查看群组的请求和响应实体

        map.put(5, GroupListReqBean.class);

        map.put(6, GroupListResBean.class);

        //加入群组的请求和响应实体

        map.put(7,GroupAddReqBean.class);

        map.put(8,GroupAddResBean.class);

        //退出群组的请求和响应实体

        map.put(9,GroupQuitReqBean.class);

        map.put(10,GroupQuitResBean.class);

        //查看成员列表的请求和响应实体

        map.put(11,GroupMemberReqBean.class);

        map.put(12,GroupMemberResBean.class);

        //发送响应的实体(发送消息、发送响应、接受消息)

        map.put(13,GroupSendMsgReqBean.class);

        map.put(14,GroupSendMsgResBean.class);

        map.put(15,GroupRecMsgBean.class);

    }

通过下面这张图,能看的更清晰一些:

8、Handler定义实战

IM群聊功能的实现,我们需要两个两个业务 Handler:

  • 1)分别是客户端(ClientChatGroupHandler);
  • 2)服务端(ServerChatGroupHandler)。

8.1 客户端 Handler

客户端 Handler,主要是通过判断实体类型来做不同的业务操作,当然也可以使用 SimpleChannelInboundHandler 去进行 Handler 拆分。

public class ClientChatGroupHandler extends ChannelInboundHandlerAdapter {

    @Override

    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        //在链接就绪时登录

        login(ctx.channel());

    }

    //主要是“接受服务端”的响应信息

    @Override

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

        if(msg instanceof LoginResBean){

            LoginResBean res=(LoginResBean) msg;

            System.out.println("登录响应:"+res.getMsg());

            if(res.getStatus()==0){

                //登录成功

                //1.给通道绑定身份

                ctx.channel().attr(AttributeKey.valueOf("userid")).set(res.getUserid());

                //2.显示操作类型【请看下面】

                deal(ctx.channel());

            }else{

                //登录失败,继续登录

                login(ctx.channel());

            }

        }else if(msg instanceof GroupCreateResBean){

            GroupCreateResBean res=(GroupCreateResBean)msg;

            System.out.println("创建响应群组:"+res.getMsg());

        }else if(msg instanceofGroupListResBean){

            GroupListResBean res=(GroupListResBean)msg;

            System.out.println("查看群组列表:"+res.getLists());

        }elseif(msg instanceofGroupAddResBean){

            GroupAddResBean res=(GroupAddResBean)msg;

            System.out.println("加入群组响应:"+res.getMsg());

        }elseif(msg instanceof GroupQuitResBean){

            GroupQuitResBean res=(GroupQuitResBean)msg;

            System.out.println("退群群组响应:"+res.getMsg());

        }else if(msg instanceof GroupMemberResBean){

            GroupMemberResBean res=(GroupMemberResBean)msg;

            if(res.getCode()==1){

                System.out.println("查看成员列表:"+res.getMsg());

            }else{

                System.out.println("查看成员列表:"+res.getLists());

            }

        }else if(msg instanceof GroupSendMsgResBean){

            GroupSendMsgResBean res=(GroupSendMsgResBean)msg;

            System.out.println("群发消息响应:"+res.getMsg());

        }else if(msg instanceof GroupRecMsgBean){

            GroupRecMsgBean res=(GroupRecMsgBean)msg;

            System.out.println("收到消息fromuserid="+

                               res.getFromuserid()+

                               ",msg="+res.getMsg());

        }

    }

}

通过子线程循环向输出控制台输出操作类型的方法,以下方法目前都是空方法,下面将详细讲解。

private void deal(final Channel channel){

        final Scanner scanner=new Scanner(System.in);

        new Thread(new Runnable() {

            public void run() {

                while(true){

                    System.out.println("请选择类型:0创建群组,1查看群组,2加入群组,3退出群组,4查看群成员,5群发消息");

                    int type=scanner.nextInt();

                    switch(type){

                        case 0:

                            createGroup(scanner,channel);

                            break;

                        case 1:

                            listGroup(scanner,channel);

                            break;

                        case 2:

                            addGroup(scanner,channel);

                            break;

                        case 3:

                            quitGroup(scanner,channel);

                            break;

                        case 4:

                            listMembers(scanner,channel);

                            break;

                        case 5:

                            sendMsgToGroup(scanner,channel);

                            break;

                        default:

                            System.out.println("输入的类型不存在!");

                    }

                }

            }

        }).start();

    }

8.2 服务端 Handler

服务端 Handler,主要是通过判断实体类型来做不同的业务操作,当然也可以使用 SimpleChannelInboundHandler 去进行 Handler 拆分。

以下方法目前都是空方法,下面将详细讲解。

public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

    private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

    private static Map<Integer, Group> groups=new HashMap<Integer, Group>();

    @Override

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

        if(msg instanceof LoginReqBean) {

            //登录

            login((LoginReqBean) msg, ctx.channel());

        }else if(msg instanceof GroupCreateReqBean){

            //创建群组

            createGroup((GroupCreateReqBean)msg,ctx.channel());

        }else if(msg instanceof GroupListReqBean){

            //查看群组列表

            listGroup((GroupListReqBean)msg,ctx.channel());

        }else if(msg instanceof GroupAddReqBean){

            //加入群组

            addGroup((GroupAddReqBean)msg,ctx.channel());

        }else if(msg instanceof GroupQuitReqBean){

            //退出群组

            quitGroup((GroupQuitReqBean)msg,ctx.channel());

        }else if(msg instanceof GroupMemberReqBean){

            //查看成员列表

            listMember((GroupMemberReqBean)msg,ctx.channel());

        }else if(msg instanceof GroupSendMsgReqBean){

            //消息发送

            sendMsg((GroupSendMsgReqBean) msg,ctx.channel());

        }

    }

}

9、具体功能编码实战

9.1 创建群组

客户端请求:

private void createGroup(Scanner scanner,Channel channel){

        System.out.println("请输入群组ID");

        Integer groupId=scanner.nextInt();

        System.out.println("请输入群组名称");

        String groupName=scanner.next();

        GroupCreateReqBean bean=new GroupCreateReqBean();

        bean.setGroupId(groupId);

        bean.setGroupName(groupName);

        channel.writeAndFlush(bean);

    }

服务端处理:

public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

    private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

    private static Map<Integer, Group> groups=new HashMap<Integer, Group>();

    private void createGroup(GroupCreateReqBean bean,Channel channel){

        //定义一个响应实体

        GroupCreateResBean res=new GroupCreateResBean();

        //查询groups是否已经存在

        Group group=groups.get(bean.getGroupId());

        //判断是否已经存在

        if(group==null){

            //定义群组实体

            Group g=new Group();

            //定义一个集合,专门存储成员

            List<GroupMember> members=new ArrayList<GroupMember>();

            //属性赋值

            g.setGroupName(bean.getGroupName());

            g.setMembers(members);

            //添加到Map里面

            groups.put(bean.getGroupId(),g);

            //响应信息

            res.setCode(0);

            res.setMsg("创建群组成功");

        }else{

            res.setCode(1);

            res.setMsg("该群组已经存在!");

        }

        channel.writeAndFlush(res);

    }

}

9.2 查看群组

客户端请求:

private void listGroup(Scanner scanner,Channel channel){

    GroupListReqBean bean=new GroupListReqBean();

    bean.setType("list");

    channel.writeAndFlush(bean);

}

服务端处理:

public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

    private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

    private static Map<Integer, Group> groups=new HashMap<Integer, Group>();

    private void listGroup(GroupListReqBean bean,Channel channel){

        if("list".equals(bean.getType())){

            //定义一个响应实体

            GroupListResBean res=new GroupListResBean();

            //定义一个集合

            List<GroupInfo> lists=new ArrayList<GroupInfo>();

            //变量groups Map集合

            for(Map.Entry<Integer, Group> entry : groups.entrySet()){

                Integer mapKey = entry.getKey();

                Group mapValue = entry.getValue();

                GroupInfo gi=new GroupInfo();

                gi.setGroupId(mapKey);

                gi.setGroupName(mapValue.getGroupName());

                lists.add(gi);

            }

            //把集合添加到响应实体里面

            res.setLists(lists);

            //开始写到客户端

            channel.writeAndFlush(res);

        }

    }

}

9.3 加入群组

客户端请求:

private void addGroup(Scanner scanner,Channel channel){

    System.out.println("请输入加入的群组ID");

    int groupId=scanner.nextInt();

    Integer userId=(Integer) channel.attr(AttributeKey.valueOf("userid")).get();

    GroupAddReqBean bean=new GroupAddReqBean();

    bean.setUserId(userId);

    bean.setGroupId(groupId);

    channel.writeAndFlush(bean);

}

服务端处理:

public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

    private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

    private static Map<Integer, Group> groups=new HashMap<Integer, Group>();

    private void addGroup(GroupAddReqBean bean,Channel channel){

        GroupAddResBean res=new GroupAddResBean();

        //1.根据“群组ID”获取对应的“组信息”

        Group group=groups.get(bean.getGroupId());

        //2.“群组”不存在

        if(group==null){

            res.setCode(1);

            res.setMsg("groupId="+bean.getGroupId()+",不存在!");

            channel.writeAndFlush(res);

            return;

        }

        //3.“群组”存在,则获取其底下的“成员集合”

        List<GroupMember> members=group.getMembers();

        boolean flag=false;

        //4.遍历集合,判断“用户”是否已经存在了

        for(GroupMember gm:members){

            if(gm.getUserid()==bean.getUserId()){

                flag=true;

                break;

            }

        }

        if(flag){

            res.setCode(1);

            res.setMsg("已经在群组里面,无法再次加入!");

        }else{

            //1.用户信息

            GroupMember gm=new GroupMember();

            gm.setUserid(bean.getUserId());

            gm.setChannel(channel);

            //2.添加到集合里面

            members.add(gm);

            //3.给“群组”重新赋值

            group.setMembers(members);

            res.setCode(0);

            res.setMsg("加入群组成功");

        }

        channel.writeAndFlush(res);

    }

}

9.4 退出群组

客户端请求:

private void quitGroup(Scanner scanner,Channel channel){

    System.out.println("请输入退出的群组ID");

    int groupId=scanner.nextInt();

    Integer userId=(Integer) channel.attr(AttributeKey.valueOf("userid")).get();

    GroupQuitReqBean bean=new GroupQuitReqBean();

    bean.setUserId(userId);

    bean.setGroupId(groupId);

    channel.writeAndFlush(bean);

}

服务端处理:

public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

    private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

    private static Map<Integer, Group> groups=new HashMap<Integer, Group>();

    private void quitGroup(GroupQuitReqBean bean,Channel channel){

        GroupQuitResBean res=new GroupQuitResBean();

        //1.根据“群组ID”获取对应的“组信息”

        Group group=groups.get(bean.getGroupId());

        if(group==null){

            //2.群组不存在

            res.setCode(1);

            res.setMsg("groupId="+bean.getGroupId()+",不存在!");

            channel.writeAndFlush(res);

            return;

        }

        //3.群组存在,则获取其底下“成员集合”

        List<GroupMember> members=group.getMembers();

        //4.遍历集合,找到“当前用户”在集合的序号

        int index=-1;

        for(inti=0;i<members.size();i++){

            if(members.get(i).getUserid()==bean.getUserId()){

                index=i;

                break;

            }

        }

        //5.如果序号等于-1,则表示“当前用户”不存在集合里面

        if(index==-1){

            res.setCode(1);

            res.setMsg("userid="+bean.getUserId()+",不存在该群组里面!");

            channel.writeAndFlush(res);

            return;

        }

        //6.从集合里面删除“当前用户”

        members.remove(index);

        //7.给“群组”的“成员列表”重新赋值

        group.setMembers(members);

        res.setCode(0);

        res.setMsg("退出群组成功");

        channel.writeAndFlush(res);

    }

}

9.5 查看群组成员

客户端请求:

private void listMembers(Scanner scanner,Channel channel){

    System.out.println("请输入群组ID:");

    int groupId=scanner.nextInt();

    GroupMemberReqBean bean=new GroupMemberReqBean();

    bean.setGroupId(groupId);

    channel.writeAndFlush(bean);

}

服务端处理:

public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

    private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

    private static Map<Integer, Group> groups=new HashMap<Integer, Group>();

    private void listMember(GroupMemberReqBean bean,Channel channel){

        GroupMemberResBean res=new GroupMemberResBean();

        List<Integer> lists=new ArrayList<Integer>();

        //1.根据“群组ID”获取对应的“组信息”

        Group group=groups.get(bean.getGroupId());

        if(group==null){

            //2.查询的群组不存在

            res.setCode(1);

            res.setMsg("groupId="+bean.getGroupId()+",不存在!");

            channel.writeAndFlush(res);

        }else{

            //3.群组存在,则变量其底层的成员

            for(Map.Entry<Integer, Group> entry : groups.entrySet()){

                Group g = entry.getValue();

                List<GroupMember> members=g.getMembers();

                for(GroupMember gm:members){

                    lists.add(gm.getUserid());

                }

            }

            res.setCode(0);

            res.setMsg("查询成功");

            res.setLists(lists);

            channel.writeAndFlush(res);

         }

    }

}

9.6 群发消息

客户端请求:

private void sendMsgToGroup(Scanner scanner,Channel channel){

    System.out.println("请输入群组ID:");

    int groupId=scanner.nextInt();

    System.out.println("请输入发送消息内容:");

    String msg=scanner.next();

    Integer userId=(Integer) channel.attr(AttributeKey.valueOf("userid")).get();

    GroupSendMsgReqBean bean=new GroupSendMsgReqBean();

    bean.setFromuserid(userId);

    bean.setTogroupid(groupId);

    bean.setMsg(msg);

    channel.writeAndFlush(bean);

}

服务端处理:

public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

    private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

    private static Map<Integer, Group> groups=new HashMap<Integer, Group>();

    privatevoidsendMsg(GroupSendMsgReqBean bean,Channel channel){

        GroupSendMsgResBean res=new GroupSendMsgResBean();

        //1.根据“群组ID”获取对应的“组信息”

        Group group=groups.get(bean.getTogroupid());

        //2.给“发送人”响应,通知其发送的消息是否成功

        if(group==null){

            res.setCode(1);

            res.setMsg("groupId="+bean.getTogroupid()+",不存在!");

            channel.writeAndFlush(res);

            return;

        }else{

            res.setCode(0);

            res.setMsg("群发消息成功");

            channel.writeAndFlush(res);

        }

        //3.根据“组”下面的“成员”,变量并且逐个推送消息

        List<GroupMember> members=group.getMembers();

        for(GroupMember gm:members){

            GroupRecMsgBean rec=new GroupRecMsgBean();

            rec.setFromuserid(bean.getFromuserid());

            rec.setMsg(bean.getMsg());

            gm.getChannel().writeAndFlush(rec);

        }

    }

}

10、本篇小结

本篇中涉及的功能点稍微有点多,主要是实现了群聊的几个核心功能,分别是:创建群组、查看群组列表、加入群组、退出群组、查看成员列表、群发消息。

这些功能经过拆解,看起来就不是那么复杂了,希望大家都可以亲自动手实现一遍,加深理解,提高学习效果。

实际上,真正的产品级IM中,群聊涉及的技术细节是非常多的,有兴趣可以详读下面这几篇:

11、参考资料

[1] 手把手教你用Netty实现心跳机制、断线重连机制

[2] 自已开发IM很难?手把手教你撸一个Andriod版IM

[3] 基于Netty,从零开发一个IM服务端

[4] 拿起键盘就是干,教你徒手开发一套分布式IM系统

[5] 正确理解IM长连接、心跳及重连机制,并动手实现

[6] 手把手教你用Go快速搭建高性能、可扩展的IM系统

[7] 手把手教你用WebSocket打造Web端IM聊天

[8] 万字长文,手把手教你用Netty打造IM聊天

[9] 基于Netty实现一套分布式IM系统

[10] 基于Netty,搭建高性能IM集群(含技术思路+源码)

[11] SpringBoot集成开源IM框架MobileIMSDK,实现即时通讯IM聊天功能

学习交流:

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK(备用地址点此)

(本文已同步发布于:http://www.52im.net/thread-3981-1-1.html)

相关推荐

鸿蒙原生即时通讯/IM系统RainbowTalk v2.8版发布,基于MobileIMSDK框架、ArkTS编写

💧 此版本解决了若干bug,以及ui和功能细节优化,同时支持最新鸿蒙7.0系统。

hellojackjiang2011的博客 165

深度剖析IM消息乱序的根本原因及优化方法

本文深度剖析即时通讯IM消息乱序的问题,梳理IM客户端时间戳不可信、多端并发、群消息扇出重试三大核心陷阱,拆解微信、钉钉、企业微信的时序设计方案,并从锚点设计、多端去交错、幂等兜底、可观测性等维度,给出IM项目消息时序性保证的落地优化策略。

hellojackjiang2011的博客 293

IM聊天不掉线?生产级WebSocket断线重连、心跳保活实战

本文将给出一套生产级Web端即时通信方案,包括心跳检测保活 + 指数退避重连,代码直接能跑。

hellojackjiang2011的博客 318

IM全文检索技术(五):vivo敏感词匹配系统的技术实践

本文将从算法选型入手,结合两个实际场景来介绍谛听系统的敏感词匹配算法。包括底层算法选型思路和两种实际场景下提升敏感词精准度、降低漏杀率的方案。

hellojackjiang2011的博客 189

Nginx生产级WebSocket实时长连接负载均衡,一文搞定

本文从基础配置、生产配置、Docker / K8s、安全、排障、性能调优,系统性讲清 WebSocket 在 Nginx 中的正确打开方式。

hellojackjiang2011的博客 308

即时通讯IM离线消息该怎么存?全面盘点主流存储方案

离线消息是IM系统的核心能力,当用户不在线时,消息必须可靠存储,待用户上线后完整推送。本文将对比主流的离线消息存储方案,帮你找到最适合业务场景的架构。

hellojackjiang2011的博客 310

移动端实时音视频聊天框架 RainbowAV v2.2.7发布

1)

hellojackjiang2011的博客 247

都HTML5了,Web端即时通讯技术到底该用什么?一文即懂!

本文分享的是 Web 端最常用即时通讯技术(包括 WebSocket、SSE、WebRTC、轮询等)的原理、优缺点与适用场景等,以及生产环境下的高并发场景下的进阶实践。

hellojackjiang2011的博客 341

基于开源MobileIMSDK框架,即时通讯IM产品RainbowChat v12.1发布

此版本新增了短信验证码的注册和登录功能,解决了Android 16手机上发送文件失败等问题。

hellojackjiang2011的博客 209

IM千人群聊该不该用消息写扩散?详细算算成本账!

本文将为你详解 IM 群聊消息的写扩散技术原理以及写扩散过程中的服务端详细性能成本,同时对比了市面上IM大厂的技术方案,给出分规模架构选型与以及性能优化策略等。

hellojackjiang2011的博客 402

直播系统聊天技术(十):基于实时消息总线的活动系统架构设计

本文分享的是网易云音乐技术团队基于实时消息总线技术,解决了直播活动系统的模块灵活组合、消息治理与异步履约等问题,希望能给你带来启发。

hellojackjiang2011的博客 369

轻量级Web端IM即时通讯框架MobileIMSDK-Web,基于Socket.io库

MobileIMSDK-Web是一套Web端即时通讯框架,超轻量级、极少依赖,高度提炼、简单易用。

hellojackjiang2011的博客 286

socket长连接在手游场景下的技术实践

本文介绍了37手游基于B站goim框架自研长连接系统的实践。系统采用分层设计,支持多协议和发布/订阅机制,用于直播弹幕、实时推送等场景,实现了高性能与业务适配。

hellojackjiang2011的博客 404

基于开源MobileIMSDK框架,即时通讯IM产品RainbowChat v12.0发布

RainbowChat 是一套基于 MobileIMSDK 开源通信框架的产品级移动端IM系统。可能是市面上开放源码的,唯一一款同时支持TCP、UDP、WebSocket三种网络通信协议的IM产品。

hellojackjiang2011的博客 518

社交场景下的统一即时通讯im消息流交互层模块化技术实践

这些IM场景,在消息流的展示形式上是极为相似的,同时每个业务又有着自己特殊的交互需求。基于此,我们对IM消息流能力做了标准化的构建,来减少IM功能的业务接入成本;同时也是为了统一各个业务的技术方案,减少跨业务开发的理解和维护成本。

hellojackjiang2011的博客 571

现代IM系统中聊天消息的同步和存储方案探讨

本文内容主要涉及IM系统中的消息系统架构,探讨一种适用于大用户量的消息同步以及存储系统的架构实现,能够支持消息系统中的高级特性“多端同步”以及“消息漫游”。在性能和规模上,能够做到全量消息云端存储,百万TPS以及毫秒级延迟的消息同步能力。

hellojackjiang2011的博客 75

即时通讯安全篇(十六):对称加密 vs 非对称加密?一文搞懂

本文将以通俗案例讲解加密本质,介绍对称加密(含 AES、迪菲–赫尔曼密钥交换)与非对称加密(RSA)原理、特点及应用,并阐释其数学基础。

hellojackjiang2011的博客 397

网络编程入门如此简单(五):UDP跟TCP相比,到底差了什么?

本文将通俗易懂地为你类比解释UDP与TCP的核心差异,包括如何基于UDP实现TCP的可靠传输:通过模拟三次握手、添加序列号与确认机制解决顺序和丢包问题,利用滑动窗口控制流量,并引入拥塞控制算法来动态调整发送速率等。

hellojackjiang2011的博客 448

基于WebSocket的IM即时通信方案在H5游戏场景下的技术实践

本文将从H5游戏场景下的实时信息更新需求切入,探讨下在类似这种高并发、低延迟的业务需求中,如何使用 IM 方案来解决频繁的数据更新问题,也顺便介绍下 WebSocket 的基本运作流程等。

hellojackjiang2011的博客 1069

鸿蒙Next原生IM即时通讯RainbowTalk,纯ArkTS编写,基于开源MobileIMSDK框架

RainbowTalk 是一套基于 MobileIMSDK 开源通信框架的产品级纯血鸿蒙NEXT端IM系统。RainbowTalk 由纯ArkTS编写、全新开发,没有套壳、也没走捷径,原生“纯血”。

hellojackjiang2011的博客 1229

如何保障分布式IM聊天系统的消息可靠性(即消息不丢)

本文主要聚焦分布式IM聊天系统消息可靠性问题,即如何保证消息不丢失。

hellojackjiang2011的博客 732

如何保障分布式IM聊天系统的消息有序性(即消息不乱)

本篇主要总结和分享分布式IM聊天系统架构中关于消息有序性的设计和实践。

hellojackjiang2011的博客 835

B站即时通讯IM消息系统的新架构升级实践

本文要分享的是B站IM消息系统的新架构升级实践总结,内容包括原架构的问题分析,新架构的整体设计以及具体的升级实现等。

hellojackjiang2011的博客 714

AI大模型爆火的SSE技术到底是什么?万字长文,一篇读懂SSE!

本文从SSE(Server-Sent Events)技术的原理到示例代码,为你通俗易懂的讲解SSE技术的方方面面。

hellojackjiang2011的博客 786

腾讯新闻APP的消息推送Push架构技术重构实践

本文主要分享的是腾讯技术团队近年来对腾讯新闻消息推送PUSH系统做的架构优化和技术实践。

hellojackjiang2011的博客 843

移动端弱网优化专题(十五):字节跳动移动端网络HttpDNS优化实践

本文要分享的是字节跳动团队针对火山HTTPDNS Cache2.0通过自研网段库与动态划分算法,将缓存粒度从“城市-运营商”细化为“网段”,解决了传统方案的城市级调度污染问题。配合缓存分级、预取等优化,在提升调度精准度的同时保证了高命中率,最终实现了服务端调度准确性提升和客户端性能优化。

hellojackjiang2011的博客 720

转转客服IM聊天系统背后的技术挑战和实践分享

在当今互联网时代,高效的用户服务是提升用户体验的关键。转转自研的客服IM聊天系统作为用户与客服沟通的桥梁,承担着传递信息、解决问题的关键角色。然而,消息数据的流转并非一帆风顺,本文将深入探讨IM系统在消息传递过程中遇到的问题和挑战,以及相应的技术解决方案。

hellojackjiang2011的博客 1096

ChatGPT如何实现聊天一样的实时交互?快速读懂SSE实时“推”技术

本文将带你快速认识SSE实时通信协议,包括它的技术原理、常见使用场景、与同类技术的对比以及简单的示例代码等。

hellojackjiang2011的博客 124

全面适配iOS 26液态玻璃,基于开源IM即时通讯框架MobileIMSDK:RainbowChat-iOS端v10.2发布

RainbowChat是一套基于开源IM即时通讯聊天框架 MobileIMSDK 的产品级移动端IM系统。

hellojackjiang2011的博客 1157

可靠传输的TCP协议send成功就意味着数据一定发出去了?

本篇文章我们以TCP协议的网络编程逻辑,从Socket缓冲区的角度去拆解,为什么号称可靠传输的TCP协议,在代码中调用send并成功发出数据,并不意味着这个数据就一定通过物理网络发出去了。

hellojackjiang2011的博客 125
上一篇: 基于Netty,从零开发IM(二):编码实践篇(im单聊功能)
下一篇: 开源轻量级 IM 框架 MobileIMSDK v6.2 发布
jackjiang2025
jackjiang2025 网际时代技术分享账号 网际时代技术分享账号
博客等级 码龄8年 700粉丝 318原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值