The author of this article, Xiao Fu, the original title "Using DDD+Netty to develop a distributed IM (Instant Messaging) system". In order to improve the reading experience, there are a lot of revisions and changes, thanks to the original author.
0, series of articles
"Follow the source code to learn IM (1): teach you how to use Netty to implement the heartbeat mechanism and the disconnected reconnection mechanism"
"Learning IM from Source Code (2): Is it difficult to develop IM by yourself? Teach you how to pick up an Andriod version of IM"
"Learning IM from Source Code (3): Develop an IM server from scratch based on Netty"
"Learn IM from the source code (4): Just pick up the keyboard and do it, teach you to develop a distributed IM system with your bare hands"
"Learning IM from the source code (5): Correctly understand the IM long connection, heartbeat and reconnection mechanism, and implement it by hand"
"Learning IM from the source code (6): teach you to use Go to quickly build a high-performance and scalable IM system"
"Learning IM from the source code (7): teach you how to use WebSocket to create IM chat on the web"
"Learning IM with the source code (8): 4D long text, teach you how to create IM chat with Netty"
"Learning IM from Source Code (9): Implementing a Distributed IM System Based on Netty" (* This article)
1. Introduction
In the study of computer programming, whether you can learn the knowledge by hand is about hands-on practice. In the articles I write, I basically tell the content of the article with the results of practical code verification as the core.
Since I was young, I like to do it. Take an instant messaging project as an example. It has been implemented 5 or 6 times based on different technical solutions, just to practice the technology. The screenshots are as follows.
As shown in the picture above:
1) When some people just finished learning Socket and Swing, they wanted to try these technologies if they could write a QQ;
2) There are also projects that need to be completed because of internship training, but after having some foundation, you can write all the functions in a week;
3) Although these items still look ugly interface, and the code logic may not be so perfect. But in every realization of the learning phase, it can bring a lot of technical growth for itself.
So, this time I take the opportunity of this article to leave the opportunity of IM practice to you, and I hope you can use it.
In the following content, I will introduce you how to develop all aspects of an IM, including system architecture, communication protocol, single chat group chat, emoticon sending, UI event-driven, etc., as well as a full set of practical source code for you to learn.
Note: The source code can be downloaded in the attachment of the "4. Source code of this article" section of this article.
study Exchange:
- 5 groups for instant messaging/push technology development and communication: 215477170 [recommended]
- Mobile IM development introductory article: "One entry is enough for novices: Develop mobile IM from scratch"
- Open source IM framework source code: https://github.com/JackJiang2011/MobileIMSDK
(This article has been simultaneously published at: http://www.52im.net/thread-3789-1-1.html)
2. Knowledge preparation
- Important note: This article is not an instant messaging theory article. The content of the article is all organized by actual code. If you know too little about instant messaging (IM) technology theory, it is recommended to read it in detail: "A beginner's introduction is enough: Develop mobile IM from scratch".
Some people may not know what Netty is, here is a brief introduction:
Netty is a Java open source framework. Netty provides asynchronous, event-driven network application frameworks and tools to quickly develop high-performance, high-reliability network server and client programs.
In other words, Netty is a NIO-based client and server-side programming framework. Using Netty can ensure that you can quickly and easily develop a network application, such as a client and server-side application that implements a certain protocol.
Netty simplifies and streamlines the programming and development process of network applications, for example, TCP and UDP Socket service development.
Here are a few introductory articles about Netty, which are worth reading:
"Novice Getting Started: The most thorough analysis of Netty's high-performance principles and framework architecture so far"
"Writing for Beginners: Learning Methods and Advanced Strategies of Netty, a Java High-Performance NIO Framework"
"Introduction to the most popular Netty framework in history: basic introduction, environment construction, hands-on combat"
If you don’t even know what Java’s NIO is, the following articles are recommended to read first:
"Less long-winded! One minute to take you to understand the difference between Java's NIO and classic IO "
"Introduction to the strongest Java NIO in history: if you are worried about getting started to giving up, please read this! 》
"Java BIO and NIO are difficult to understand? Use code practice to show you, if I don’t understand, I will switch to another career! 》
Online reading address of Netty source code and API:
1) Netty-4.1.x complete source code (online reading version) (* recommended)
2) Netty-4.0.x complete source code (online reading version)
3) Netty-4.1.x API documentation (online version) (*recommended)
4) Netty-4.0.x API documentation (online version)
3. Operation effect
Before starting to learn, let's show you the operation effect of the source code of this article (the source code can be downloaded in the attachment of the section "4. Source code of this article").
Chat page:
add friend:
message notification:
4. Source code of this article
Download the complete code attachment of this article:
(Please download from the synchronous release link: http://www.52im.net/thread-3789-1-1.html)
The directory structure of the source code is as shown in the figure below:
This set of IM code is divided into three groups of modules: UI, client, and server.
The reason for this split is to isolate the UI display from the business logic and use events and interfaces to drive it to make the code level cleaner and easier to extend and maintain.
The functions of each module are explained as follows:
5. System design
In this set of IM, the server adopts the DDD domain-driven design model to build. The Netty function is handed over to SpringBoot for start and stop control, and at the same time, a console can be set up on the server side to operate the communication system very conveniently, and perform user and communication management. In the construction of the client, the UI separation method is used to build to ensure that the business code is separated from the UI display, and the control is very easy to expand.
In addition, the functional realization includes: perfectly imitating the core functions of WeChat desktop client, login, search and add friends, user communication, group communication, and emoticon sending. If there are functions that actually need to be used, they can be expanded in accordance with this system framework.
explain:
1) UI development: Use JavaFx and Maven to build a UI desktop project, and explain step by step various UI display and operation events such as login box, chat box, dialog box, and friend bar;
2) Architecture design: Use the four-layer model structure of DDD field-driven design in combination with Netty to construct a reasonable layered framework (design of corresponding library table functions);
3) Function realization: including; log in, add friends, conversation notification, message sending, disconnected reconnection and other functions.
6. UI development
6.1 Functional division
The chat form, compared to the login form, has more content and is relatively more complicated.
The following figure is a sketch of the function definition of the chat form:
As shown in FIG:
1) First is the definition of our entire chat main window, which is a blank panel, and removes the default border buttons (minimize, exit, etc.);
2) After that is our left sidebar, which we call the bar Bar, the realization of the functional area;
3) Finally, add a form event to change the filling information in the content panel when the button is clicked.
6.2 Chat interface
The content area after the dialog box is selected is displayed, that is, information is sent and displayed between users.
On the whole, this is a linked process. Click on the dialog user on the left, and the corresponding content will be filled on the right. Then the ListView of the filled dialogue list on the right side needs to be associated with each dialogue user. When the chat user is clicked, it is a process of repeatedly switching and filling. Results as shown below.
See the picture above, let me explain:
1) Click on each dialog body on the left, and the filling content of the chat box on the right will change accordingly (and the corresponding dialog name will also change);
2) The left side of the dialog box displays the information sent by friends, and the right side displays the information sent by individuals (at the same time, the content of the message will increase in height and width as the content increases);
3) At the bottom is the text input box. In the following implementation, our text input box is designed in a public way. Of course, you can also design it for individual personal use.
6.3 Friends list
Everyone often uses WeChat on the PC side. You can know that there are several paragraphs of content in the Friends column, including: new friends, official accounts, groups, and bottom friends (the function is divided as shown in the figure below).
See the picture above, let me explain:
1) The content of the top search box remains unchanged, the same as the previous one. The method we currently use is fxml design. For example, this part is a common function, which can be extracted and put into the code to be designed as a component element class;
2) After our analysis, based on the use of JavaFx component development, this part is a nested ListView, that is, the bottom panel is a ListView, and friends and groups are each a ListView. After processing, we will It is very convenient to fill in data;
3) In addition, this structure is mainly conducive to the operation of our program, if you add a friend, then we need to refresh the friend information to the friend column, and when the data is filled, in order to be more convenient and efficient, so we designed Nested ListView (if you don't understand it, you can get the answer from the subsequent code).
6.4 Event definition
In the desktop UI development, in order to isolate the UI from the business logic, we need to provide an interface for the display effect of the operation interface and an abstract class for interface operation events after we package the UI.
Then you can understand it according to the following figure:
The above interfaces are all the behavior interfaces provided by our current UI to the outside. A link description of these interfaces is: open windows, search for friends, add friends, open dialog boxes, and send messages.
7. Communication design
7.1 System Architecture
I mentioned earlier that the more suitable architecture is the best architecture that meets your current needs.
So how to design the required architecture?
The reason for this design is based on the following prerequisites in this system:
1) The system must have a web page on the server side to manage communication users and control and monitor the server side;
2) The object class of the database should not be externally contaminated, and should be isolated (for example: your database class is exposed to external use as a display class, then now you need to add a field, and this field is not an attribute of your database. .Then the database class has been polluted at this time).
3) Because Netty communication is currently implemented in the Java language, both the server and the client need to use the protocol definition and analysis in the communication process. Then we need to extract the Jar package from this layer (it is good for reuse, otherwise the client and server copy the same code for maintenance, it would be too disgusting);
4) Interfaces, business processing, underlying services, and communication interactions must be clearly distinguished and implemented to avoid confusion and difficulty in maintenance.
Combining the premise of our four points above, what model structure does your mind reflect? And is there a plan for the corresponding technology stack selection?
Next, I will introduce two architectural design models, one is the MVC that you are very familiar with, and the other is the DDD domain-driven design that you may have heard of.
7.2 Communication protocol
From the perspective of the artwork, we need to add a "frame identifier" to the transmission package to determine which object the current business object is when transmitting objects, which can make our business more clear and avoid using a large number of ifs. Sentence judgment.
Agreement framework:
agreement
└── src
├── main
│ ├── java
│ │ └── org.itstack.naive.chat
│ │ ├── codec
│ │ │ ├── ObjDecoder.java
│ │ │ └── ObjEncoder.java
│ │ ├── protocol
│ │ │ ├── demo
│ │ │ ├── Command.java
│ │ │ └── Packet.java
│ │ └── util
│ │ └── SerializationUtil.java
│ ├── resources
│ │ └── application.yml
│ └── webapp
│ └── chat
│ └── res
│ └── index.html
└── test
└── java
└── org.itstack.demo.test
└── ApiTest.java
Protocol package:
public abstract class Packet {
private final static Map<Byte, Class<? extendsPacket>> packetType = new ConcurrentHashMap<>();
static{
packetType.put(Command.LoginRequest, LoginRequest.class);
packetType.put(Command.LoginResponse, LoginResponse.class);
packetType.put(Command.MsgRequest, MsgRequest.class);
packetType.put(Command.MsgResponse, MsgResponse.class);
packetType.put(Command.TalkNoticeRequest, TalkNoticeRequest.class);
packetType.put(Command.TalkNoticeResponse, TalkNoticeResponse.class);
packetType.put(Command.SearchFriendRequest, SearchFriendRequest.class);
packetType.put(Command.SearchFriendResponse, SearchFriendResponse.class);
packetType.put(Command.AddFriendRequest, AddFriendRequest.class);
packetType.put(Command.AddFriendResponse, AddFriendResponse.class);
packetType.put(Command.DelTalkRequest, DelTalkRequest.class);
packetType.put(Command.MsgGroupRequest, MsgGroupRequest.class);
packetType.put(Command.MsgGroupResponse, MsgGroupResponse.class);
packetType.put(Command.ReconnectRequest, ReconnectRequest.class);
}
public static Class<? extends Packet> get(Byte command) {
return packetType.get(command);
}
/**
* 获取协议指令
*
* @return 返回指令值
*/
public abstract Byte getCommand();
}
7.3 Add friends
As you can see from the above flowchart, there are two parts here: searching for friends and adding friends.
When the friend is added, the friend will appear in our friend column.
And here we use unilateral consent to add friends, that is, when you add a friend, the other party also has your friend information.
If you need to add friends and agree to it in your business, you can add a piece of status information when you initiate the addition of friends, requesting to add friends. After the other party agrees, the two users can become friends and communicate.
Sample code for adding friends:
public class AddFriendHandler extends MyBizHandler<AddFriendRequest> {
public AddFriendHandler(UserService userService) {
super(userService);
}
@Override
public void channelRead(Channel channel, AddFriendRequest msg) {
// 1. 添加好友到数据库中[A->B B->A]
List<UserFriend> userFriendList = newArrayList<>();
userFriendList.add(newUserFriend(msg.getUserId(), msg.getFriendId()));
userFriendList.add(newUserFriend(msg.getFriendId(), msg.getUserId()));
userService.addUserFriend(userFriendList);
// 2. 推送好友添加完成 A
UserInfo userInfo = userService.queryUserInfo(msg.getFriendId());
channel.writeAndFlush(newAddFriendResponse(userInfo.getUserId(), userInfo.getUserNickName(), userInfo.getUserHead()));
// 3. 推送好友添加完成 B
Channel friendChannel = SocketChannelUtil.getChannel(msg.getFriendId());
if(null== friendChannel) return;
UserInfo friendInfo = userService.queryUserInfo(msg.getUserId());
friendChannel.writeAndFlush(newAddFriendResponse(friendInfo.getUserId(), friendInfo.getUserNickName(), friendInfo.getUserHead()));
}
}
7.4 Message response
From the overall process, we can see that when the user initiates a friend or group communication, an event behavior will be triggered, and then the client will send a dialogue request with the friend to the server.
After the server receives the conversation request: if it is a friend conversation, then it needs to save the communication information with the friend in the dialog box. At the same time, inform friends that I am going to communicate with you. You are in your dialog list, add me.
If it’s a group communication: it’s not necessary to do this notification, because it’s impossible to notify all group users who are not online yet (people haven’t logged in yet), so this part only needs to be created after the user goes online and receives the information. The dialog box is in the list. You can understand it carefully, and you can also think about other ways to achieve it.
Message response sample code:
public class MsgHandler extends MyBizHandler<MsgRequest> {
public MsgHandler(UserService userService) {
super(userService);
}
@Override
public void channelRead(Channel channel, MsgRequest msg) {
logger.info("消息信息处理:{}", JSON.toJSONString(msg));
// 异步写库
userService.asyncAppendChatRecord(newChatRecordInfo(msg.getUserId(), msg.getFriendId(), msg.getMsgText(), msg.getMsgType(), msg.getMsgDate()));
// 添加对话框[如果对方没有你的对话框则添加]
userService.addTalkBoxInfo(msg.getFriendId(), msg.getUserId(), Constants.TalkType.Friend.getCode());
// 获取好友通信管道
Channel friendChannel = SocketChannelUtil.getChannel(msg.getFriendId());
if(null== friendChannel) {
logger.info("用户id:{}未登录!", msg.getFriendId());
return;
}
// 发送消息
friendChannel.writeAndFlush(newMsgResponse(msg.getUserId(), msg.getMsgText(), msg.getMsgType(), msg.getMsgDate()));
}
}
7.5 Reconnect after disconnection
From the above process, we can see that when the network connection is disconnected, it will send a reconnection request like the server. Then the process of initiating the link is different from the initial link of the system. Reconnection after disconnection requires the user's ID information to be sent to the server, so that the server can update the binding relationship between the user and the communication channel.
At the same time, it is necessary to update the reconnection information in the group, and add the user's reconnection to the group mapping. At this time, the communication function between the user and friends and groups can be restored.
Message response sample code:
// Channel status regular inspection; after 3 seconds, it will be executed every 5 seconds
scheduledExecutorService.scheduleAtFixedRate(() -> {while(!nettyClient.isActive()) {System.out.println("Communication pipeline inspection: communication pipeline status" + nettyClient.isActive());
try{System.out.println("通信管道巡检:断线重连 [Begin]");
Channel freshChannel = executorService.submit(nettyClient).get();
if(null== CacheUtil.userId) continue;
freshChannel.writeAndFlush(newReconnectRequest(CacheUtil.userId));
} catch(InterruptedException | ExecutionException e) {System.out.println("通信管道巡检:断线重连 [Error]");}
}
}, 3, 5, TimeUnit.SECONDS);
Related articles to learn:
"Why does the mobile terminal IM based on the TCP protocol still need a heartbeat keep-alive mechanism? 》
"An article to understand the network heartbeat packet mechanism in instant messaging applications: functions, principles, realization ideas, etc."
"Rongyun Technology Sharing: Practice of Network Link Keep Alive Technology of Rongyun Android IM Products"
"Correctly understand the heartbeat and reconnection mechanism of the IM long connection, and implement it by hand (there is a complete IM source code)"
"A Discussion on the Design and Implementation of an IM Intelligent Heartbeat Algorithm on Android (with sample code)"
"Teach you how to use Netty to realize the heartbeat mechanism and disconnection reconnection mechanism of network communication programs"
"Web-side instant messaging practice dry goods: How to make your WebSocket disconnect and reconnect faster? 》
7.6 Cluster communication
As shown in the figure above, I implemented IM cluster communication like this:
1) Cross-service cases use redis publish and subscribe to deliver messages. If you are a large service, you can use zookeeper;
2) When user A sends a message to user B, he needs to pass B's channeId to the server to find out whether the channeId belongs to its own service;
3) A single machine can also start multiple Netty services, and the program will automatically search for available ports.
8. Summary of this article
This IM system involves a lot of technology stacks: Netty4.x, SpringBoot, Mybatis, Mysql, JavaFx, layui and other technology stacks, and the entire system frame structure adopts DDD four-layer architecture + Socket module to build, all The UI is designed in a separate event-driven way from the back-end. In this process, as long as you can persist in learning, you will definitely gain a lot of content. Bragging enough!
The learning process of any new technology stack will include such a route: running HelloWorld, proficient use of API, project practice, and finally deep source code mining. So when hearing such a demand, Java programmers will definitely think of a series of technical knowledge points to fill in the various modules in our project (for example: interface with JavaFx, Swing, etc., communication with Socket or know the Netty framework, server-side control Use MVC model plus SpringBoot, etc.). But how to build our system rationally for these various technology stacks is indeed the most important part of the learning, practice, and growth process.
Well, IM development actually involves a lot of knowledge dimensions. Due to space limitations, I won’t go into more lengthy here. Readers must learn from the source code synchronously, so that the effect will be better (the source code is in the "4. Source code of this article" section of this article Can be downloaded at the attachment).
9. Reference materials
[1] Getting Started: The most thorough analysis of Netty's high-performance principles and framework architecture so far
[2] For beginners: learning methods and advanced strategies of Netty, a Java high-performance NIO framework
[3] Introduction to the strongest Java NIO in history: If you are worried about getting started to giving up, please read this!
[4] Java BIO and NIO are difficult to understand? Use code practice to show you, if I don’t understand, I will switch to another career!
[5] The most popular Netty framework in history: basic introduction, environment construction, hands-on combat
[6] Integrate theory with practice: a set of typical IM communication protocol design details
[7] Talking about the architecture design of IM system
[8] Briefly describe the pits of mobile IM development: architecture design, communication protocol and client
[9] A set of mobile IM architecture design practice sharing for massive online users (including detailed pictures and texts)
[10] A set of original distributed instant messaging (IM) system theoretical architecture scheme
[11] A set of high-availability, easy-scalable, high-concurrency IM group chat and single chat architecture design practices
[12] A set of IM architecture technical dry goods for hundreds of millions of users (Part 1): overall architecture, service split, etc.
[13] A set of IM architecture technical dry goods for hundreds of millions of users (Part 2): reliability, orderliness, weak network optimization, etc.
[14] From novice to expert: How to design a distributed IM system with billions of messages
[15] Based on practice: a summary of the technical points of a small-scale IM system with a million messages
This article has been simultaneously published on the official account of "Instant Messaging Technology Circle".
The synchronous publishing link is: http://www.52im.net/thread-3789-1-1.html
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。