Jack Jiang

          我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
          posts - 499, comments - 13, trackbacks - 0, articles - 1

          本文由作者“大白菜”分享,有較多修訂和改動(dòng)。注意:本系列是給IM初學(xué)者的文章,IM老油條們還望海涵,勿噴!

          1、引言

          接上兩篇《IM系統(tǒng)設(shè)計(jì)篇》、《編碼實(shí)踐篇(單聊功能)》,本篇主要講解的是通過實(shí)戰(zhàn)編碼實(shí)現(xiàn)IM的群聊功能,內(nèi)容涉及群聊技術(shù)實(shí)現(xiàn)原理、編碼實(shí)踐等知識(shí)。

          學(xué)習(xí)交流:

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

          2、寫在前面

          建議你在閱讀本文之前,務(wù)必先讀本系列的前兩篇《IM系統(tǒng)設(shè)計(jì)篇》、《編碼實(shí)踐篇(單聊功能)》,在著重理解IM系統(tǒng)的理論設(shè)計(jì)思路之后,再來閱讀實(shí)戰(zhàn)代碼則效果更好。

          最后,在開始本文之前,請(qǐng)您務(wù)必提前了解Netty的相關(guān)基礎(chǔ)知識(shí),可從本系列首篇《IM系統(tǒng)設(shè)計(jì)篇》中的“知識(shí)準(zhǔn)備”一章開始。

          3、系列文章

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

          4、本篇概述

          在上篇《編碼實(shí)踐篇(單聊功能)》中,我們主要實(shí)現(xiàn)了IM的單聊功能,本節(jié)主要是實(shí)現(xiàn)IM群聊功能。

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

          • 1)登錄:每個(gè)客戶端連接服務(wù)端的時(shí)候,都需要輸入自己的賬號(hào)信息,以便和連接通道進(jìn)行綁定;
          • 2)創(chuàng)建群組:輸入群組 ID 和群組名稱進(jìn)行創(chuàng)建群組。需要先根據(jù)群組 ID 進(jìn)行校驗(yàn),判斷是否已經(jīng)存在了;
          • 3)查看群組:查看目前已經(jīng)創(chuàng)建的群組列表;
          • 4)加入群組:主要參數(shù)是群組 ID 和用戶 ID,用戶 ID 只需從 Channel 的綁定屬性里面獲取即。主要是判斷群組 ID 是否存在,如果存在還需要判斷該用戶 ID 是否已經(jīng)在群組里面了;
          • 5)退出群組:主要是判斷群組 ID 是否存在,如果存在則刪除相應(yīng)的關(guān)系;
          • 6)查看組成員:根據(jù)群組 ID 去查詢對(duì)應(yīng)的成員列表;
          • 7)群發(fā)消息:選擇某個(gè)群進(jìn)行消息發(fā)送,該群下的成員都能收到信息。主要判斷群組 ID 是否存在,如果存在再去獲取其對(duì)應(yīng)的成員列表。

          5、群聊原理

          其實(shí)群聊和單聊,整體上原理是一樣的,只是做了一下細(xì)節(jié)上的升級(jí)。

          在首篇《IM系統(tǒng)設(shè)計(jì)篇》的“6、IM群聊思路設(shè)計(jì)”設(shè)計(jì)部分也做了詳細(xì)的說明了。

          群聊的大概流程就是:根據(jù)群組 ID 查找到所有的成員集合,然后再遍歷找到每個(gè)成員對(duì)應(yīng)的連接通道。

          具體的群聊架構(gòu)思路如下圖:

          如上圖所示,群聊通訊流程技術(shù)原理如下:

          • 1)群聊和單聊整體上的思路一致:需要保存每個(gè)用戶和通道的對(duì)應(yīng)關(guān)系,方便后期通過用戶 ID 去查找到對(duì)應(yīng)的通道,再跟進(jìn)通道推送消息;
          • 2)群聊把消息發(fā)送給群?jiǎn)T的原理:其實(shí)很簡(jiǎn)單,服務(wù)端再保存另外一份映射關(guān)系,那就是聊天室和成員的映射關(guān)系。發(fā)送消息時(shí),首先根據(jù)聊天室 ID 找到對(duì)應(yīng)的所有成員,然后再跟進(jìn)各個(gè)成員的 ID 去查找到對(duì)應(yīng)的通道,最后由每個(gè)通道進(jìn)行消息的發(fā)送;
          • 3)群成員加入某個(gè)群聊聊的時(shí)候:往映射表新增一條記錄,如果成員退群的時(shí)候則刪除對(duì)應(yīng)的映射記錄。

          6、運(yùn)行效果

          補(bǔ)充說明:因?yàn)楸鞠盗形恼轮饕康氖且龑?dǎo)IM初學(xué)者在基于Netty的情況下,如何一步一步從零寫出IM的邏輯和思維能力,因而為了簡(jiǎn)化編碼實(shí)現(xiàn),本篇中編碼實(shí)現(xiàn)的客戶端都是基于控制臺(tái)實(shí)現(xiàn)的(希望不要被嫌棄),因?yàn)槔斫饧夹g(shù)的本質(zhì)顯然比炫酷的外在表現(xiàn)形式更為重要。

          用戶登錄效果圖:

          群組操作效果圖:

          7、實(shí)體定義實(shí)戰(zhàn)

          7.1 服務(wù)端實(shí)體

          服務(wù)端映射關(guān)系的管理,分別是:

          • 1)登錄信息(用戶 ID 和通道);
          • 2)群組信息(群組 ID 和群組成員關(guān)系)。

          主要通過兩個(gè) Map 去維護(hù),具體如下:

          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>();

          }

          //組和成員列表關(guān)系實(shí)體

          @Data

          public class Group implements Serializable {

              private String groupName;

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

          }

          //成員和連接通道的關(guān)系實(shí)體

          public class GroupMember implements Serializable {

              private Integer userid;

              private Channel channel;

          }

          7.2 實(shí)體和指令關(guān)系

          我們準(zhǔn)備好相應(yīng)的實(shí)體,以及實(shí)體和指令的映射關(guān)系,具體如下所示:

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

              static{

                  //登錄的請(qǐng)求和響應(yīng)實(shí)體

                  map.put(1, LoginReqBean.class);

                  map.put(2, LoginResBean.class);

           

                  //創(chuàng)建群組的請(qǐng)求和響應(yīng)實(shí)體

                  map.put(3, GroupCreateReqBean.class);

                  map.put(4, GroupCreateResBean.class);

           

                  //查看群組的請(qǐng)求和響應(yīng)實(shí)體

                  map.put(5, GroupListReqBean.class);

                  map.put(6, GroupListResBean.class);

           

                  //加入群組的請(qǐng)求和響應(yīng)實(shí)體

                  map.put(7,GroupAddReqBean.class);

                  map.put(8,GroupAddResBean.class);

           

                  //退出群組的請(qǐng)求和響應(yīng)實(shí)體

                  map.put(9,GroupQuitReqBean.class);

                  map.put(10,GroupQuitResBean.class);

           

                  //查看成員列表的請(qǐng)求和響應(yīng)實(shí)體

                  map.put(11,GroupMemberReqBean.class);

                  map.put(12,GroupMemberResBean.class);

           

                  //發(fā)送響應(yīng)的實(shí)體(發(fā)送消息、發(fā)送響應(yīng)、接受消息)

                  map.put(13,GroupSendMsgReqBean.class);

                  map.put(14,GroupSendMsgResBean.class);

                  map.put(15,GroupRecMsgBean.class);

              }

          通過下面這張圖,能看的更清晰一些:

           

          8、Handler定義實(shí)戰(zhàn)

          IM群聊功能的實(shí)現(xiàn),我們需要兩個(gè)兩個(gè)業(yè)務(wù) Handler:

          • 1)分別是客戶端(ClientChatGroupHandler);
          • 2)服務(wù)端(ServerChatGroupHandler)。

          8.1 客戶端 Handler

          客戶端 Handler,主要是通過判斷實(shí)體類型來做不同的業(yè)務(wù)操作,當(dāng)然也可以使用 SimpleChannelInboundHandler 去進(jìn)行 Handler 拆分。

          public class ClientChatGroupHandler extends ChannelInboundHandlerAdapter {

              @Override

              public void channelActive(ChannelHandlerContext ctx) throws Exception {

                  //在鏈接就緒時(shí)登錄

                  login(ctx.channel());

              }

           

              //主要是“接受服務(wù)端”的響應(yīng)信息

              @Override

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

                  if(msg instanceof LoginResBean){

                      LoginResBean res=(LoginResBean) msg;

                      System.out.println("登錄響應(yīng):"+res.getMsg());

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

                          //登錄成功

           

                          //1.給通道綁定身份

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

           

                          //2.顯示操作類型【請(qǐng)看下面】

                          deal(ctx.channel());

                      }else{

                          //登錄失敗,繼續(xù)登錄

                          login(ctx.channel());

                      }

                  }else if(msg instanceof GroupCreateResBean){

                      GroupCreateResBean res=(GroupCreateResBean)msg;

                      System.out.println("創(chuàng)建響應(yīng)群組:"+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("加入群組響應(yīng):"+res.getMsg());

           

                  }elseif(msg instanceof GroupQuitResBean){

                      GroupQuitResBean res=(GroupQuitResBean)msg;

                      System.out.println("退群群組響應(yīng):"+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("群發(fā)消息響應(yīng):"+res.getMsg());

           

                  }else if(msg instanceof GroupRecMsgBean){

                      GroupRecMsgBean res=(GroupRecMsgBean)msg;

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

                                         res.getFromuserid()+

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

                  }

              }

          }

          通過子線程循環(huán)向輸出控制臺(tái)輸出操作類型的方法,以下方法目前都是空方法,下面將詳細(xì)講解。

          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("請(qǐng)選擇類型:0創(chuàng)建群組,1查看群組,2加入群組,3退出群組,4查看群成員,5群發(fā)消息");

                              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 服務(wù)端 Handler

          服務(wù)端 Handler,主要是通過判斷實(shí)體類型來做不同的業(yè)務(wù)操作,當(dāng)然也可以使用 SimpleChannelInboundHandler 去進(jìn)行 Handler 拆分。

          以下方法目前都是空方法,下面將詳細(xì)講解。

          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){

                      //創(chuàng)建群組

                      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){

                      //消息發(fā)送

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

                  }

              }

          }

          9、具體功能編碼實(shí)戰(zhàn)

          9.1 創(chuàng)建群組

          客戶端請(qǐng)求:

          private void createGroup(Scanner scanner,Channel channel){

                  System.out.println("請(qǐng)輸入群組ID");

                  Integer groupId=scanner.nextInt();

                  System.out.println("請(qǐng)輸入群組名稱");

                  String groupName=scanner.next();

           

                  GroupCreateReqBean bean=new GroupCreateReqBean();

                  bean.setGroupId(groupId);

                  bean.setGroupName(groupName);

                  channel.writeAndFlush(bean);

              }

          服務(wù)端處理:

          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){

                  //定義一個(gè)響應(yīng)實(shí)體

                  GroupCreateResBean res=new GroupCreateResBean();

                  //查詢groups是否已經(jīng)存在

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

                  //判斷是否已經(jīng)存在

                  if(group==null){

                      //定義群組實(shí)體

                      Group g=new Group();

                      //定義一個(gè)集合,專門存儲(chǔ)成員

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

                      //屬性賦值

                      g.setGroupName(bean.getGroupName());

                      g.setMembers(members);

                      //添加到Map里面

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

           

                      //響應(yīng)信息

                      res.setCode(0);

                      res.setMsg("創(chuàng)建群組成功");

                  }else{

                      res.setCode(1);

                      res.setMsg("該群組已經(jīng)存在!");

                  }

                  channel.writeAndFlush(res);

              }

          }

          9.2 查看群組

          客戶端請(qǐng)求:

          private void listGroup(Scanner scanner,Channel channel){

              GroupListReqBean bean=new GroupListReqBean();

              bean.setType("list");

              channel.writeAndFlush(bean);

          }

          服務(wù)端處理:

          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())){

                      //定義一個(gè)響應(yīng)實(shí)體

                      GroupListResBean res=new GroupListResBean();

                      //定義一個(gè)集合

                      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);

                      }

                      //把集合添加到響應(yīng)實(shí)體里面

                      res.setLists(lists);

                      //開始寫到客戶端

                      channel.writeAndFlush(res);

                  }

              }

          }

          9.3 加入群組

          客戶端請(qǐng)求:

          private void addGroup(Scanner scanner,Channel channel){

              System.out.println("請(qǐng)輸入加入的群組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);

          }

          服務(wù)端處理:

          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.根據(jù)“群組ID”獲取對(duì)應(yīng)的“組信息”

                  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.遍歷集合,判斷“用戶”是否已經(jīng)存在了

                  for(GroupMember gm:members){

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

                          flag=true;

                          break;

                      }

                  }

                  if(flag){

                      res.setCode(1);

                      res.setMsg("已經(jīng)在群組里面,無法再次加入!");

                  }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 退出群組

          客戶端請(qǐng)求:

          private void quitGroup(Scanner scanner,Channel channel){

              System.out.println("請(qǐng)輸入退出的群組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);

          }

          服務(wù)端處理:

          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.根據(jù)“群組ID”獲取對(duì)應(yīng)的“組信息”

                  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.遍歷集合,找到“當(dāng)前用戶”在集合的序號(hào)

                  int index=-1;

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

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

                          index=i;

                          break;

                      }

                  }

                  //5.如果序號(hào)等于-1,則表示“當(dāng)前用戶”不存在集合里面

                  if(index==-1){

                      res.setCode(1);

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

                      channel.writeAndFlush(res);

                      return;

                  }

                  //6.從集合里面刪除“當(dāng)前用戶”

                  members.remove(index);

                  //7.給“群組”的“成員列表”重新賦值

                  group.setMembers(members);

                  res.setCode(0);

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

                  channel.writeAndFlush(res);

              }

          }

          9.5 查看群組成員

          客戶端請(qǐng)求:

          private void listMembers(Scanner scanner,Channel channel){

              System.out.println("請(qǐng)輸入群組ID:");

              int groupId=scanner.nextInt();

           

              GroupMemberReqBean bean=new GroupMemberReqBean();

              bean.setGroupId(groupId);

              channel.writeAndFlush(bean);

          }

          服務(wù)端處理:

          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.根據(jù)“群組ID”獲取對(duì)應(yīng)的“組信息”

                  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 群發(fā)消息

          客戶端請(qǐng)求:

          private void sendMsgToGroup(Scanner scanner,Channel channel){

              System.out.println("請(qǐng)輸入群組ID:");

              int groupId=scanner.nextInt();

           

              System.out.println("請(qǐng)輸入發(fā)送消息內(nèi)容:");

              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);

          }

          服務(wù)端處理:

          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.根據(jù)“群組ID”獲取對(duì)應(yīng)的“組信息”

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

           

                  //2.給“發(fā)送人”響應(yīng),通知其發(fā)送的消息是否成功

                  if(group==null){

                      res.setCode(1);

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

                      channel.writeAndFlush(res);

                      return;

                  }else{

                      res.setCode(0);

                      res.setMsg("群發(fā)消息成功");

                      channel.writeAndFlush(res);

                  }

                  //3.根據(jù)“組”下面的“成員”,變量并且逐個(gè)推送消息

                  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、本篇小結(jié)

          本篇中涉及的功能點(diǎn)稍微有點(diǎn)多,主要是實(shí)現(xiàn)了群聊的幾個(gè)核心功能,分別是:創(chuàng)建群組、查看群組列表、加入群組、退出群組、查看成員列表、群發(fā)消息。

          這些功能經(jīng)過拆解,看起來就不是那么復(fù)雜了,希望大家都可以親自動(dòng)手實(shí)現(xiàn)一遍,加深理解,提高學(xué)習(xí)效果。

          實(shí)際上,真正的產(chǎn)品級(jí)IM中,群聊涉及的技術(shù)細(xì)節(jié)是非常多的,有興趣可以詳讀下面這幾篇:

          11、參考資料

          [1] 手把手教你用Netty實(shí)現(xiàn)心跳機(jī)制、斷線重連機(jī)制

          [2] 自已開發(fā)IM很難?手把手教你擼一個(gè)Andriod版IM

          [3] 基于Netty,從零開發(fā)一個(gè)IM服務(wù)端

          [4] 拿起鍵盤就是干,教你徒手開發(fā)一套分布式IM系統(tǒng)

          [5] 正確理解IM長(zhǎng)連接、心跳及重連機(jī)制,并動(dòng)手實(shí)現(xiàn)

          [6] 手把手教你用Go快速搭建高性能、可擴(kuò)展的IM系統(tǒng)

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

          [8] 萬字長(zhǎng)文,手把手教你用Netty打造IM聊天

          [9] 基于Netty實(shí)現(xiàn)一套分布式IM系統(tǒng)

          [10] 基于Netty,搭建高性能IM集群(含技術(shù)思路+源碼)

          [11] SpringBoot集成開源IM框架MobileIMSDK,實(shí)現(xiàn)即時(shí)通訊IM聊天功能

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



          作者:Jack Jiang (點(diǎn)擊作者姓名進(jìn)入Github)
          出處:http://www.52im.net/space-uid-1.html
          交流:歡迎加入即時(shí)通訊開發(fā)交流群 215891622
          討論:http://www.52im.net/
          Jack Jiang同時(shí)是【原創(chuàng)Java Swing外觀工程BeautyEye】【輕量級(jí)移動(dòng)端即時(shí)通訊框架MobileIMSDK】的作者,可前往下載交流。
          本博文 歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明出處(也可前往 我的52im.net 找到我)。


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          Jack Jiang的 Mail: jb2011@163.com, 聯(lián)系QQ: 413980957, 微信: hellojackjiang
          主站蜘蛛池模板: 长垣县| 麻江县| 万全县| 铜鼓县| 怀来县| 蓝田县| 新乡县| 北海市| 盐池县| 锡林浩特市| 理塘县| 吉隆县| 通渭县| 江孜县| 沐川县| 东莞市| 湖南省| 凌云县| 金乡县| 宁化县| 安泽县| 合阳县| 白水县| 莱阳市| 赤城县| 汝州市| 安泽县| 郧西县| 荃湾区| 淄博市| 乌鲁木齐县| 麻江县| 繁昌县| 封开县| 科技| 福建省| 牟定县| 安西县| 辽阳县| 蛟河市| 育儿|