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

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

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

1、引言

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

学习交流:

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

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

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

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聊天功能

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

目录
相关文章
|
3月前
|
数据采集 监控 测试技术
大型IM稳定性监测实践:手Q客户端性能防劣化系统的建设之路
本文以iOS端为例,详细分享了手 Q 客户端性能防劣化系统从0到1的构建之路,相信对业界和IM开发者们都有较高的借鉴意义。
129 2
|
4月前
|
Ubuntu Linux vr&ar
IM跨平台技术学习(十二):万字长文详解QQ Linux端实时音视频背后的跨平台实践
本文详细记录了新版QQ音视频通话在 Linux 平台适配开发过程中的技术方案与实现细节,希望能帮助大家理解在 Linux 平台从 0 到 1 实现音视频通话能力的过程。
168 2
|
5月前
|
资源调度 JavaScript 前端开发
IM跨平台技术学习(十一):环信基于Electron打包Web IM桌面端的技术实践
这次借着论证 Web IM端 SDK 是否可以在 Electron 生成的桌面端正常稳定使用,我决定把官方新推出的 webim-vue3-demo,打包到桌面端,并记录了这次验证的过程以及所遇到的问题和解决方法。
92 2
|
3月前
|
人工智能 自然语言处理 Serverless
阿里云百炼应用实践系列-让微信公众号成为智能客服
本文主要介绍如何基于百炼平台快速在10分钟让您的微信公众号(订阅号)变成 AI 智能客服。我们基于百炼平台的能力,以官方帮助文档为参考,让您的微信公众号(订阅号)成 为AI 智能客服,以便全天候(7x24)回应客户咨询,提升用户体验,介绍了相关技术方案和主要代码,供开发者参考。
阿里云百炼应用实践系列-让微信公众号成为智能客服
|
3月前
|
小程序 前端开发 Java
携程技术分享:亿级流量的办公IM及开放平台技术实践
本文总结了携程办公IM这些年的发展历程及未来的演进方向,并着重从高可用、高性能和可扩展的角度,探讨开放式平台的技术实现及发展方向。
55 0
携程技术分享:亿级流量的办公IM及开放平台技术实践
|
3月前
|
前端开发 网络协议
Netty实战巅峰:从零构建高性能IM即时通讯系统,解锁并发通信新境界
【8月更文挑战第3天】Netty是一款高性能、异步事件驱动的网络框架,适用于开发高并发网络应用,如即时通讯(IM)系统。本文将指导你利用Netty从零构建高性能IM程序,介绍Netty基础及服务器/客户端设计。服务器端使用`ServerBootstrap`启动,客户端通过`Bootstrap`连接服务器。示例展示了简单的服务器启动过程。通过深入学习,可进一步实现用户认证等功能,打造出更完善的IM系统。
141 1
|
3月前
|
编解码 NoSQL Redis
(十一)Netty实战篇:基于Netty框架打造一款高性能的IM即时通讯程序
关于Netty网络框架的内容,前面已经讲了两个章节,但总归来说难以真正掌握,毕竟只是对其中一个个组件进行讲解,很难让诸位将其串起来形成一条线,所以本章中则会结合实战案例,对Netty进行更深层次的学习与掌握,实战案例也并不难,一个非常朴素的IM聊天程序。
|
4月前
|
Rust 前端开发 JavaScript
IM跨平台技术学习(十三):从理论到实践,详细对比Electron和Tauri的优劣
本文主要介绍了目前比较流行的桌面应用跨平台开发技术及其架构,并以实战的方式对比了 Electron 和 Tauri 的优势和劣势,以及桌面跨平台应用开发的技术趋势。
61 0
|
5月前
|
边缘计算 JSON 网络协议
移动端IM开发者必读(三):爱奇艺移动端跨国弱网通信的优化实践
本次分享的文章内容,基于爱奇艺面向全球用户推出的国际版,在海外跨国网络环境复杂的前提下,针对性地做了一系列弱网优化实践,取得了不错的效果,在此总结分享我们的一些做法和优化思路,希望对你有所帮助。
72 1
|
4月前
|
小程序
【微信小程序-原生开发】客服
【微信小程序-原生开发】客服
112 0

热门文章

最新文章