This article is shared by the author "Chinese Cabbage", and there are many revisions and changes. Note: This series is an article for IM beginners, IM old fritters still look to Haihan, don't spray!
1 Introduction
Continued with two articles "IM System Design" and "Coding Practice (Single Chat Function)", this article mainly explains the realization of IM's group chat function through actual combat coding, and the content involves the realization principle of group chat technology, coding practice and other knowledge .
Learning and communication: - Introduction to mobile IM development article: "One entry for beginners: developing mobile IM from scratch" - Open source IM framework source code: https://github.com/JackJiang2011/MobileIMSDK (click here for alternate address)
(This article has been published simultaneously at: http://www.52im.net/thread-3981-1-1.html )
2. Write in front
It is recommended that before reading this article, you must read the first two articles in this series, "IM System Design" and "Coding Practice (Single Chat Function)", after focusing on understanding the theoretical design ideas of the IM system, then read the actual combat code to see the effect better. Finally, before starting this article, please be sure to understand the basic knowledge of Netty in advance, starting from the "Knowledge Preparation" chapter in the first "IM System Design" in this series.
3. Series of articles
This article is the third in a series of articles. The following is a series of catalogues: "Based on Netty, Developing IM from Scratch (1): IM System Design" "Based on Netty, Developing IM from Zero (2): Coding Practice (Single Chat Function) )" "Based on Netty, developing IM from scratch (3): coding practice (group chat function)" (* this article) "Based on Netty, developing IM from scratch (4): coding practice (system optimization)" (later post..)
4. Overview of this article
In the last article "Coding Practice (Single Chat Function)", we mainly implemented the single chat function of IM, and this section mainly implements the IM group chat function. The core functions of the group chat involved in this article are roughly as follows: 1) Login: when each client connects to the server, it needs to enter its own account information to bind with the connection channel; 2) Create a group: enter Group ID and group name to create a group. It needs to be verified according to the group ID to determine whether it already exists; 3) View group: View the list of groups that have been created so far; 4) Join a group: the main parameters are group ID and user ID, user ID only It needs to be obtained from the binding property of Channel. It is mainly to judge whether the group ID exists, and if so, it is necessary to judge whether the user ID is already in the group; 5) Exit the group: mainly to judge whether the group ID exists, and if so, delete the corresponding relationship; 6) View group members: query the corresponding member list according to the group ID; 7) group message: select a group to send a message, and all members of the group can receive the message. It mainly judges whether the group ID exists, and if so, obtains its corresponding member list.
5. The principle of group chat
In fact, the principle of group chat and single chat is the same as a whole, but the details have been upgraded. In the first article "IM System Design", the design part of "6. IM group chat idea design" has also been explained in detail. The general process of group chat is: find all member sets according to the group ID, and then traverse to find the connection channel corresponding to each member. The specific group chat structure idea is as follows:
As shown in the figure above, the technical principles of the group chat communication process are as follows: 1) The overall idea of group chat and single chat is the same: it is necessary to save the corresponding relationship between each user and channel, so that it is convenient to find the corresponding channel through the user ID later, and then Follow-up channel push messages; 2) The principle of group chat sending messages to group members: It is actually very simple. The server saves another mapping relationship, that is, the mapping relationship between chat rooms and members. When sending a message, first find all the corresponding members according to the chat room ID, then follow up the ID of each member to find the corresponding channel, and finally send the message by each channel; 3) Group members join a group chat When: Add a new record to the mapping table, and delete the corresponding mapping record if the member withdraws from the group.
6. Operation effect
Supplementary note: Because the main purpose of this series of articles is to guide IM beginners to write the logic and thinking ability of IM from scratch step by step in the case of Netty, so in order to simplify the coding implementation, the clients implemented by coding in this article are all It's console-based (hopefully not dismissed), because understanding the nature of the technology is clearly more important than the cool appearance. User login renderings:
Group operation renderings:
7. Entity definition in practice
7.1 The management of the server-side entity server-side mapping relationship is: 1) login information (user ID and channel); 2) group information (group ID and group membership). It is mainly maintained through two Maps, as follows: 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>();}//Group and member list relationship entities @Datapublic class Group implements Serializable { private String groupName; private List<GroupMember> members=new ArrayList<GroupMember>();}//Members and connection channels Relationship entity public class GroupMember implements Serializable { private Integer userid; private Channel channel;}7.2 Entity and instruction relationship We have prepared the corresponding entity, as well as the mapping relationship between entity and instruction, as follows: private static Map<Byte, Class< ? extends BaseBean>> map=new HashMap<Byte,Class<? extends BaseBean>>(); static{ //Login request and response entities map.put(1, LoginReqBean.class); map.put(2, LoginResBean .class); //Create group request and response entities map.put(3, GroupCreateReqBean.class); map.put(4, GroupCreateResBean.class); //View group request and response entities map.put( 5, GroupListReqBean.class); map.put(6, GroupListResBean.class); //The request and response entity to join the group map.put(7,GroupAddReqBean.class); map.put(8,GroupAddResBean.class); //The request and response entity to exit the group map.put(9,GroupQuitReqBean. class); map.put(10,GroupQuitResBean.class); //View member list request and response entities map.put(11,GroupMemberReqBean.class); map.put(12,GroupMemberResBean.class); //Send response Entity (send message, send response, receive message) map.put(13,GroupSendMsgReqBean.class); map.put(14,GroupSendMsgResBean.class); map.put(15,GroupRecMsgBean.class); The picture can be seen more clearly:
8. Handler defines actual combat
To implement the IM group chat function, we need two business handlers: 1) the client (ClientChatGroupHandler); 2) the server (ServerChatGroupHandler). 8.1 Client Handler Client Handler mainly performs different business operations by judging the entity type. Of course, SimpleChannelInboundHandler can also be used for Handler splitting. public class ClientChatGroupHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { //Login when the link is ready login(ctx.channel()); } //Mainly the response information of "accept the server" @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if(msg instanceof LoginResBean){ LoginResBean res=(LoginResBean) msg; System.out.println("Login response: "+res.getMsg()); if(res. getStatus()==0){ //Login successful//1. Bind the identity to the channel ctx.channel().attr(AttributeKey.valueOf("userid")).set(res.getUserid()); // 2. Display the operation type [see below] deal(ctx.channel()); }else{ //Login failed, continue to log in login(ctx.channel()); } }else if(msg instanceof GroupCreateResBean){ GroupCreateResBean res =(GroupCreateResBean)msg; System.out.println("Create a response group: "+res.getMsg()); }else if(msg instanceofGroupListResBean){ GroupListResBean res=(GroupListResBean)msg; System.out.println("View group list: "+res.getLists()); }elseif(msg instanceofGroupAddResBean){ GroupAddResBean res=(GroupAddResBean)msg; System.out .println("Join group response: "+res.getMsg()); }elseif(msg instanceof GroupQuitResBean){ GroupQuitResBean res=(GroupQuitResBean)msg; System.out.println("Exit group response: "+res .getMsg()); }else if(msg instanceof GroupMemberResBean){ GroupMemberResBean res=(GroupMemberResBean)msg; if(res.getCode()==1){ System.out.println("View member list: "+res. getMsg()); }else{ System.out.println("View member list: "+res.getLists()); } }else if(msg instanceof GroupSendMsgResBean){ GroupSendMsgResBean res=(GroupSendMsgResBean)msg; System.out. println("Group message response: "+res.getMsg()); }else if(msg instanceof GroupRecMsgBean){ GroupRecMsgBean res=(GroupRecMsgBean)msg; System.out.println("Received message fromuserid="+ res.getFromuserid()+ ",msg="+res.getMsg()); } }} loops to output control through child thread The method of output operation type, the following methods are currently empty methods, which will be explained in detail below. 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("Please select the type : 0 to create a group, 1 to view a group, 2 to join a group, 3 to leave a group, 4 to view group members, 5 to send a group message"); 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("The input type does not exist!"); } } } }).start(); }8.2 Server Handler Server Handler, mainly by judging Entity types to do different business operations, of course, you can also use SimpleChannelInboundHandler to perform Handler splitting. The following methods are currently empty methods, which will be explained in detail below. 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 login((LoginReqBean) msg, ctx.channel()); }else if(msg instanceof GroupCreateReqBean){ //Create group createGroup ((GroupCreateReqBean)msg,ctx.channel()); }else if(msg instanceof GroupListReqBean){ //View group list listGroup((GroupListReqBean)msg,ctx.channel()); }else if(msg instanceof GroupAddReqBean) { //Join the group addGroup((GroupAddReqBean)msg,ctx.channel()); }else if(msg instanceof GroupQuitReqBean){ //Exit the group quitGroup((GroupQuitReqBean)msg,ctx.channel()); }else if(msg instanceof GroupMemberReqBean) { //View member list listMember((GroupMemberReqBean)msg,ctx.channel()); }else if(msg instanceof GroupSendMsgReqBean){ //Message sending sendMsg((GroupSendMsgReqBean) msg,ctx.channel()); } }}
9. Specific function coding practice
9.1 Create group client request: private void createGroup(Scanner scanner,Channel channel){ System.out.println("Please enter the group ID"); Integer groupId=scanner.nextInt(); System.out.println(" Please enter the group name"); String groupName=scanner.next(); GroupCreateReqBean bean=new GroupCreateReqBean(); bean.setGroupId(groupId); bean.setGroupName(groupName); channel.writeAndFlush(bean); }Server processing : 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){ //Define a response entity GroupCreateResBean res=new GroupCreateResBean(); //Query whether groups already exist Group group=groups.get(bean.getGroupId()); //Determine whether it already exists if (group==null){ //Define group entity Group g=new Group(); //Define a collection to store members List<GroupMember > members=new ArrayList<GroupMember>(); //Attribute assignment g.setGroupName(bean.getGroupName()); g.setMembers(members); //Add to Map groups.put(bean.getGroupId(),g ); //response information res.setCode(0); res.setMsg("Group created successfully"); }else{ res.setCode(1); res.setMsg("The group already exists!"); } channel.writeAndFlush(res); }}9.2 View group client request: private void listGroup(Scanner scanner,Channel channel){ GroupListReqBean bean=new GroupListReqBean(); bean.setType("list"); channel.writeAndFlush(bean );} Server processing: 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())){ //Define a response entity GroupListResBean res=new GroupListRes Bean(); //Define a collection List<GroupInfo> lists=new ArrayList<GroupInfo>(); //Variable groups Map collection 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); } //Add the collection to the response entity res.setLists(lists); //Start writing to the client channel.writeAndFlush(res); } }}9.3 Join the group client request: private void addGroup(Scanner scanner,Channel channel ){ System.out.println("Please enter the group ID to join"); 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);}Server processing: public class ServerChatGroupHandler ex tends 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. Obtain the corresponding "group information" according to the "group ID" Group group=groups.get(bean.getGroupId()); //2. "Group" "Does not exist if(group==null){ res.setCode(1); res.setMsg("groupId="+bean.getGroupId()+", does not exist!"); channel.writeAndFlush(res); return; } //3. If the "group" exists, get the "member collection" under it List<GroupMember> members=group.getMembers(); boolean flag=false; //4. Traverse the collection to determine whether the "user" has exists for(GroupMember gm:members){ if(gm.getUserid()==bean.getUserId()){ flag=true; break; } } if(flag){ res.setCode(1); res.setMsg( "Already in the group, cannot join again!"); }else{ //1. User information GroupMember gm=new GroupMember(); gm.setUserid(bean.getUserId()); gm.setChannel(channel); //2. Add to the collection members.add(gm); //3. Give the "group" Reassign group.setMembers(members); res.setCode(0); res.setMsg("Join the group successfully"); } channel.writeAndFlush(res); }}9.4 Client request to quit the group: private void quitGroup( Scanner scanner,Channel channel){ System.out.println("Please enter the exit group 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);}Server processing: 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. Obtain the corresponding "group information" according to the "group ID" Group group=groups.get(bean.getGroupId()); if(group==null){ //2. The group does not exist res.setCode(1); res.setMsg("groupId="+bean.getGroupId()+", does not exist!"); channel.writeAndFlush(res); return; } // 3. If the group exists, get the "member collection" under it List<GroupMember> members=group.getMembers(); //4. Traverse the collection and find the serial number of the "current user" in the collection int index=-1; for( inti=0;i<members.size();i++){ if(members.get(i).getUserid()==bean.getUserId()){ index=i; break; } } //5. If the serial number Equal to -1, it means that the "current user" does not exist in the collection if(index==-1){ res.setCode(1); res.setMsg("userid="+bean.getUserId()+", there is no such Inside the group!"); channel.writeAndFlush(res); return; } //6. Delete the "current user" from the collection members.remove(index); //7. Give the "member list" of the "group" Reassign group.setMembers(members); res.setCode( 0); res.setMsg("Exit group successfully"); channel.writeAndFlush(res); }}9.5 View group member client request: private void listMembers(Scanner scanner,Channel channel){ System.out.println( "Please enter the group ID:"); int groupId=scanner.nextInt(); GroupMemberReqBean bean=new GroupMemberReqBean(); bean.setGroupId(groupId); channel.writeAndFlush(bean);}Server processing: 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. Get the corresponding "group information" according to the "group ID" Group group=groups.get(bean .getGroupId()); if(group==null){ //2. The queried group does not exist res.setCode(1); res.setMsg("groupId="+bean.getGroupId()+", does not exist !"); channel.writeAndFlush(res); }else{ //3. If the group exists, the underlying members of the variable 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("Query successful"); res.setLists(lists ); channel.writeAndFlush(res); } }}9.6 Group message client request: private void sendMsgToGroup(Scanner scanner,Channel channel){ System.out.println("Please enter the group ID: "); int groupId=scanner .nextInt(); System.out.println("Please enter the message content: "); 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);}Server processing: public class ServerChatGroupHandler extends Channel InboundHandlerAdapter { 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. Obtain the corresponding "group information" according to the "group ID" Group group=groups.get(bean.getTogroupid()); //2. Respond to the "sender" , notify it if the message sent is successful if(group==null){ res.setCode(1); res.setMsg("groupId="+bean.getTogroupid()+", does not exist!"); channel.writeAndFlush( res); return; }else{ res.setCode(0); res.setMsg("Successful mass message sending"); channel.writeAndFlush(res); } //3. According to the "member" under "group", the variable and Push messages one by one List<GroupMember> members=group.getMembers(); for(GroupMember gm:members){ GroupRecMsgBean rec=new GroupRecMsgBean(); rec.setFromuserid(bean.getFromuserid()); rec.setMsg(bean.getMsg( )); gm.getCha nnel().writeAndFlush(rec); } }}
10. Summary of this article
The function points involved in this article are a bit more, mainly to realize several core functions of group chat, namely: create group, view group list, join group, leave group, view member list, and send group messages. After dismantling these functions, it does not look so complicated. I hope everyone can implement it by themselves to deepen their understanding and improve the learning effect. In fact, in the real product-level IM, there are many technical details involved in group chat. If you are interested, you can read the following articles: IM group chat messages are so complicated, how to ensure that they are not lost or heavy? How to ensure the efficiency and real-time performance of large-scale group message push in mobile IM? Regarding the disorder of IM instant messaging group chat messages, discuss whether IM group chat messages should be stored in one copy (ie, diffusion reading) or in multiple copies (ie, diffusion writing)? A set of high-availability, easy-to-scale, high-concurrency IM group chat and single chat architecture solution design practice The IM architecture design of enterprise WeChat is revealed in the following technical practice: message model, ten thousand people, read receipt, message withdrawal, etc. Rongyun IM technology sharing: thinking and practice of ten thousand people chat message delivery scheme
11. References
[1] Teach you to use Netty to implement the heartbeat mechanism, disconnection and reconnection mechanism
[2] Is it difficult to develop IM by yourself? Teach you how to play an Android version of IM
[3] Based on Netty, develop an IM server from scratch
[4] Pick up the keyboard and do it, teach you to develop a distributed IM system with your bare hands
[5] Correctly understand the IM long connection, heartbeat and reconnection mechanism, and implement it
[6] Teach you to quickly build a high-performance and scalable IM system with Go
[7] Teach you to use WebSocket to create web-side IM chat
[8] 4D long text, teach you how to use Netty to create IM chat
[9] Implement a distributed IM system based on Netty
[10] Based on Netty, build a high-performance IM cluster (including technical ideas + source code)
[11] SpringBoot integrates the open source IM framework MobileIMSDK to realize the instant messaging IM chat function (this article has been published simultaneously at: http://www.52im.net/thread-3981-1-1.html )
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。