Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to quickly build a highly available IM system

2025-02-27 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly introduces "how to quickly build a highly available IM system". In daily operation, I believe many people have doubts about how to quickly build a highly available IM system. The editor consulted all kinds of materials and sorted out simple and easy-to-use operation methods. I hope it will be helpful for you to answer the doubts about "how to quickly build a highly available IM system". Next, please follow the editor to study!

In-depth understanding of WebSocket protocol

The goal of Web Sockets is to provide full-duplex, two-way communication over a single persistent connection. After Javascript creates the WebSocket, a HTTP request is sent to the browser to initiate the connection.

After getting a response from the server, the established connection will upgrade the HTTP from HTTP protocol to WebSocket protocol.

Because WebSocket uses a custom protocol, the URL mode is also slightly different. Unencrypted connections are no longer http://, but ws://; encrypted connections are not https://, but wss://.

You must carry this pattern with you when using WebSocket URL, as other patterns are likely to be supported in the future.

The advantage of using a custom protocol instead of the HTTP protocol is the ability to send a very small amount of data between the client and the server without worrying about byte-level overhead like HTTP. Because the packets passed are very small, WebSocket is very suitable for mobile applications.

The above is only a general description of Web Sockets, and the following pages will explore the details of the implementation of Web Sockets.

The next few sections of this article will not cover a large number of code snippets, but will analyze the relevant API and technical principles. I believe you will suddenly feel enlightened to read this description after reading the following article.

① WebSocket reuses HTTP's handshake channel

"handshake channel" is a communication channel established by client and server through "TCP three-way handshake" in HTTP protocol.

Each interaction between the client and the server using the HTTP protocol requires the establishment of such a "channel" and then communication through this channel.

The familiar Ajax interaction completes data transfer over such a channel, except that the Ajax interaction is a short connection, and after a Request → Response, the "channel" connection is disconnected.

The following is a schematic diagram of the process of establishing a "handshake channel" in the HTTP protocol:

As we mentioned above, after Javascript creates the WebSocket, a HTTP request is sent to the browser to initiate the connection, and then the server responds, which is the process of "shaking hands".

During this handshake, the client and server do two main things:

A connection "handshake channel" is established for communication: this is the same as the HTTP protocol, except that the HTTP protocol releases this handshake channel after the data exchange is completed. This is the so-called "short connection". Its life cycle is the time of a data exchange, usually in milliseconds.

Upgrade the HTTP protocol to the WebSocket protocol and reuse the handshake channel of the HTTP protocol to establish a persistent connection.

At this point, some people may ask: why does the HTTP protocol not reuse its own "handshake channel" instead of re-establishing the "handshake channel" through the TCP three-way handshake every time the data exchange occurs?

The answer is this: although the "long connection" eliminates the troublesome step of establishing a "handshake channel" every time during the interaction between the client and the server.

But maintaining such a "long connection" needs to consume server resources, and in most cases, this kind of resource consumption is unnecessary. It can be said that the formulation of HTTP standards has been carefully considered.

When we talk about WebSocket protocol data frames later, you may understand that there is too much to do to maintain a "long connection" between the server and the client.

After talking about the handshake channel, let's take a look at how HTTP protocol is upgraded to WebSocket protocol.

Upgrade ② HTTP protocol to WebSocket protocol

Upgrading the protocol requires communication between the client and the server. How does the server know to upgrade from HTTP to WebSocket? It must have picked up some kind of signal from the client.

The following is the message that I intercepted from Google browser, "the client initiates the protocol upgrade request". By analyzing this message, we can get more details about the protocol upgrade in WebSocket.

First, the client initiates a protocol upgrade request. The standard HTTP message format is adopted and only the GET method is supported.

Here is the meaning of the first part of the priority request:

Connection:Upgrade: indicates the protocol to be upgraded.

Upgrade: websocket: indicates that you want to upgrade to WebSocket protocol.

Sec-WebSocket-Version: 13: indicates the version of WebSocket.

Sec-WebSocket-Key:UdTUf90CC561cQXn4n5XRg==: is compatible with the response header Sec-WebSocket-Accept: GZk41FJZSYY0CmsrZPGpUGRQzkY= in Response Header to provide basic protection, such as malicious or unintentional connections.

Connection is the signal we mentioned earlier, the client sends the signal to the server, and the server receives the signal before upgrading the HTTP protocol.

So how does the server confirm that the request sent by the client is legitimate? Each time the client initiates a protocol upgrade request, a unique code is generated: Sec-WebSocket-Key.

After the server gets the code, it verifies it through an algorithm, then responds to the client through Sec-WebSocket-Accept, and the client verifies the Sec-WebSocket-Accept to complete the verification.

The algorithm is simple:

Concatenate Sec-WebSocket-Key with the globally unique (GUID, [RFC4122]) identity: 258EAFA5-E914-47DA-95CA-C5AB0DC85B11.

The summary is calculated through SHA1 and converted to a base64 string.

The string 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 is also called "magic string". As to why it is used as a string used in WebSocket handshake calculation, we don't need to care about this, we just need to know that it is stipulated by the RFC standard.

The official parsing simply says that this value is unlikely to be used by network terminals that do not understand the WebSocket protocol.

Let's describe this algorithm in the best language in the world:

Public function dohandshake ($sock, $data, $key) {if (preg_match ("/ Sec-WebSocket-Key: (. *)\ r\ n /", $data, $match)) {$response = base64_encode (sha1 ($match [1]. '258EAFA5-E914-47DAMAFE95 Switching Protocol C5AB0DC85B11, true); $upgrade = "HTTP/1.1 101CAMI\ r\ n". "Upgrade: websocket\ r\ n". "Connection: Upgrade\ r\ n". "Sec-WebSocket-Accept:" $response. "\ r\ n\ r\ n"; socket_write ($sock, $upgrade, strlen ($upgrade)); $this- > isHand [$key] = true;}}

The header information of the server response client is in the same format as the HTTP protocol. The HTTP1.1 protocol is separated by a newline character (\ r\ n). We can parse the value of Sec-WebSocket-Accept through regular matching, which is the same as using the curl tool to simulate the get request.

It seems that the result is not very intuitive. We use the command line CLI to calculate whether the Sec-WebSocket-Accept returned by the server is correct based on the Sec-WebSocket-Key and handshake algorithm in the figure above:

As you can see from the figure, the base64 string calculated by the algorithm is the same as Sec-WebSocket-Accept.

What if the server returns an incorrect Sec-WebSocket-Accept string during the handshake?

Of course, the client will report an error and the connection will fail. You can give it a try, such as changing the globally unique identifier 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 to 258EAFA5-E914-47DA-95CA-C5AB0DC85B12.

Frame and data slicing Transmission of ③ WebSocket

The following picture is a test I did: copy the first chapter of the novel gone with the Wind into text data, send it to the server through the client, and then the server responds to the same information to complete a communication.

You can see that it only takes 150ms time to complete the communication between client and server with nearly 15000 bytes of data.

We can also see the text data sent by the client and the response from the server displayed in the Frame bar in the browser console. You must be surprised by the powerful data transmission capability of WebSocket communication.

Is the data really as shown in Frame that the client sends a large piece of text data directly to the server, and after the server receives the data, it returns a large piece of text data to the client?

Of course, this is impossible. We all know that the HTTP protocol is implemented based on TCP, and the data sent by HTTP is also forwarded in subpackets, that is, big data is divided into small pieces according to the message form and sent to the server. After receiving the message sent by the client, the server splices and assembles the small pieces of data.

With regard to the subcontracting strategy of HTTP, you can check the relevant materials for research. WebSocket protocol is also forwarded through shredded packaged data, but the strategy is different from that of HTTP.

Frame (frame) is the basic unit of data sent by WebSocket, and the following is its message format:

The message content specifies the format of data marking, operation code, mask, data, data length and so on. It doesn't matter if I don't understand it. I'll explain below that you only need to understand the role of the important signs in the message.

First of all, we understand that the client-side and server-side WebSocket message delivery is like this:

Client: the message is cut into multiple frames and sent to the server.

Server: receives the message frame and reassembles the associated frame into a complete message.

When the server receives the frame message sent by the client, it assembles these frames. How does it know when the data is assembled?

This is the information stored in FIN (one bit) in the upper-left corner of the message. 1 indicates that this is the last fragment of the message. If 0, it is not the last shard of the message.

In WebSocket communication, the data slicing sent by the client is orderly, which is different from HTTP.

After the message is subpackaged by HTTP, it is sent to the server concurrently and disorderly, and the position of the packet information in the data is stored in the HTTP message, while WebSocket only needs a FIN bit to ensure that the data is sent to the server completely.

What is the function of the next three bits of RSV1,RSV2,RSV3? These three flags are left to be extended through negotiation between client developers and server developers, and the default is 0.

Expanding how to use it must be negotiated at the stage of handshake. in fact, the handshake itself is also a negotiation between the client and the server.

④ WebSocket connection retention and heartbeat detection

WebSocket is a persistent connection. In order to maintain real-time two-way communication between the client and the server, it is necessary to ensure that the TCP channel between the client and the server is not disconnected.

However, for connections with no data exchanges for a long time, if they are still maintained, server resources may be wasted.

Do not rule out some scenarios, although there is no data exchange between the client and the server for a long time, you still need to maintain a connection. For example, you haven't chatted with a QQ friend for months, and suddenly one day he sends a QQ message telling you that he is going to get married, and you can still receive it at the first time.

That's because clients and servers keep using heartbeats to check connections. Heartbeat connection detection between client and server is like playing ping-pong:

Sender → receiver: ping

Receiver → sender: pong

When there is no ping, pong, then there must be a problem with the connection.

Having said so, then I use Go language to achieve a heartbeat detection, WebSocket communication implementation details is a tedious thing, directly using the open source class library is a better choice, I use: gorilla/websocket.

This class library has encapsulated the implementation details of WebSocket (handshake, data decoding) very well. I'm going to post the code directly:

Package main import ("net/http"time"github.com/gorilla/websocket") var (/ / complete handshake upgrade = websocket.Upgrader {/ / allow cross-domain (generally speaking, websocket is deployed independently) CheckOrigin:func (r * http.Request) bool {return true},}) func wsHandler (w http.ResponseWriter R * http.Request) {var (conn * websocket.Conn err error data [] byte) / / the server responds to the client's http request (upgraded to websocket protocol) After the reply, the tcp three-way handshake is maintained when the protocol is upgraded to websocket,http to establish a connection. If conn, err = upgrade.Upgrade (w, r, nil); err! = nil {return} / / starts a heartbeat message go func () {var (err error) for {if err = conn.WriteMessage (websocket.TextMessage, [] byte ("heartbeat")) to the client every 1s Err! = nil {return} time.Sleep (1 * time.Second)}} () / / after obtaining the long link of websocket, you can manipulate the data passed by the client. For {/ / the data read through the long link of websocket can be text text data. It can also be binary Binary if _, data, err = conn.ReadMessage () Err! = nil {goto ERR} if err = conn.WriteMessage (websocket.TextMessage, data); err! = nil {goto ERR}} ERR: / / close the socket connection conn.Close ()} func main () {http.HandleFunc ("/ ws", wsHandler) http.ListenAndServe ("0.0.0.0V7777", nil)}

With the help of the fact that it is easy to build a protocol in the Go language, I specially opened a protocol to send a message to the client every second.

When you open the client browser, you can see that the heartbeat data in Frame keeps beating all the time. When the long link is broken, the heartbeat is gone, just like a person without a heartbeat:

Now that you have an understanding of WebSocket protocol, let's quickly build a high-performance and scalable IM system.

Quickly build a high-performance and scalable IM system

① system architecture and code file directory structure

The following figure is a relatively complete IM system architecture: including C-side, access layer (access through protocol access), S-side processing logic and distribution of messages, storage layer used to persist data.

We use Webapp on the C side of this section, which quickly implements the function by rendering Vue templates in Go language, and the access layer uses WebSocket protocol, which has been introduced in depth earlier.

The S-side is the focus of our implementation, in which the functions of authentication, login, relationship management, single chat and group chat have been realized. Readers can expand other functions on the basis of these functions. For example: video voice chat, sending red packets, moments and other business modules.

What we do in the storage layer is relatively simple, just use MySQL to simply persist the user relationship, and then store the picture resources in the chat into the local file.

Although the implementation of our IM system is relatively simple, readers can improve, improve and expand on this basis, and can still make highly available enterprise products.

Our system service is built in Go language, the code structure is relatively simple, but the performance is excellent (which is unmatched by Java and other languages), and it supports tens of thousands of online chats on a single machine.

The following is the directory structure of the code file:

App │ ├── args │ │ ├── contact.go │ │ └── pagearg.go │ ├── controller / / Controller layer Api entry │ │ ├── chat.go │ │ ├── contract.go │ │ ├── upload.go │ │ └── user.go │ ├── main.go / / Program entry │ ├── model / / data definition and storage │ │ ├── community.go contract .go │ │ ├── init.go │ │ └── user.go │ ├── service / / logical implementation of │ │ ├── contract.go │ │ └── user.go │ ├── util / / helper function │ │ ├── md5.go parse.go │ ├── resp.go │ │ └── string.go │ └── view / / template Resources │ │ ├──... Asset / / js, css file resource / / upload resources, and the uploaded images will be put here

Starting with the entry function main.go, we define the Controller layer, which is the entry of the client API. Service is used to handle the main user logic, message distribution and user management are implemented here.

The Model layer defines some data tables, mainly user registration and user friendship, groups and other information, which are stored in MySQL.

Under the Util package are some helper functions, such as encryption, request response, and so on. Template resource information is stored under View, all of which are stored in the App folder, and asset is used to store css, js files and emoticons used in chat.

Resource stores files such as pictures or videos in user chat. Generally speaking, our code directory structure is relatively simple and clear.

Now that we know the IM system architecture we are going to build, let's take a look at the functions that the architecture focuses on.

② 10-line code universal template rendering

Go language provides powerful HTML rendering capabilities. It is very simple to build Web applications. The following is the code for template rendering. It is so simple that it can be directly implemented in the main.go function:

Func registerView () {tpl, err: = template.ParseGlob (". / app/view/**/*") if err! = nil {log.Fatal (err.Error ())} for _, v: = range tpl.Templates () {tplName: = v.Name () http.HandleFunc (tplName, func (writer http.ResponseWriter, request * http.Request) {tpl.ExecuteTemplate (writer, tplName) Nil)})}} Func main () {. Http.Handle ("/ asset/", http.FileServer (http.Dir (".")) Http.Handle ("/ resource/", http.FileServer (http.Dir (".")) RegisterView () log.Fatal (http.ListenAndServe (": 8081", nil))}

Go to implement a static resource server is also very simple, just call http.FileServer, so that HTML files can easily access dependent js, css, and icon files.

Using ParseGlob and ExecuteTemplate under the http/template package, you can easily parse Web pages, which are completely independent of Nginx.

Now we have completed the construction of the C-side interface for login, registration and chat:

③ registration, login and authentication

As we mentioned earlier, for registration, login, and friendship management, we need a user table to store user information.

When we use github.com/go-xorm/xorm to manipulate MySQL, first look at the design of the MySQL table:

App/model/user.go:

Package model import "time" const (SexWomen = "W" SexMan = "M" SexUnknown = "U") type User struct {Id int64 `xorm: "competitive autoincr bigint (64)" form: "id" json: "id" `Mobile string `xorm: "varchar (20)" form: "mobile" json: "mobile" `Passwd string `xorm: "varchar (40)" form: "passwd" json: "- "`/ / user password md5 (passwd + salt) Avatar string `xorm:" varchar (150) "form:" avatar "json:" avatar "`Sex string `xorm:" varchar (2) "form:" sex "json:" sex "`Nickname string `xorm:" varchar (20) "form:" nickname "json:" nickname "`Salt string `xorm:" varchar (10) "form:" salt "json:"- "`Online int `xorm:" int (10) "form:" online "json:" online "`/ whether online Token string `xorm:" varchar (40) "form:" token "json:" token "` / / user authentication Memo string `xorm:" varchar (140) "form:" memo "json:" memo "`Createat time.Time `xorm:" datetime "form:" createat "json:" createat "`/ / creation time Use} when counting user increments

Our user table stores some important information such as user name, password, profile picture, user gender, mobile phone number, etc. More importantly, we also store Token indicating that after the user logs in, the HTTP protocol is upgraded to WebSocket protocol for authentication. We mentioned this detail earlier, and there will be a code demonstration below.

Let's take a look at some of the things that model initialization needs to do:

App/model/init.go:

Package model import ("errors"fmt" _ "github.com/go-sql-driver/mysql"github.com/go-xorm/xorm"log") var DbEngine * xorm.Engine func init () {driverName: = "mysql" dsnName: = "root:root@ (127.0.0.1 dsnName 3306) / chat?charset=utf8" err: = errors.New (") DbEngine, err = xorm.NewEngine (driverName DsnName) if err! = nil & & err.Error ()! = "{log.Fatal (err)} DbEngine.ShowSQL (true) / / set the number of database connections DbEngine.SetMaxOpenConns (10) / / automatically create database DbEngine.Sync (new (User), new (Community), new (Contact)) fmt.Println (" init database ok! ")}

We create a DbEngine global MySQL connection object and set a connection pool with a size of 10.

The init function in the Model package will be executed first when the program is loaded, and students who are familiar with the Go language should know this.

We have also set some additional parameters for debugging programs, such as setting SQL for print runs, automatic synchronization of data tables, etc., which can be turned off in a production environment.

Our Model initialization work is done, very crude, in the actual project, such as the database user name, password, number of connections and other configuration information, it is recommended to set it to the configuration file, and then read it, unlike the hard-coded program in this article.

Registration is a common API program, which is too easy for the Go language to complete. Let's take a look at the code:

# # / / app/controller/user.go # #. / / user registers func UserRegister (writer http.ResponseWriter, request * http.Request) {var user model.User util.Bind (request, & user) user, err: = UserService.UserRegister (user.Mobile, user.Passwd, user.Nickname, user.Avatar, user.Sex) if err! = nil {util.RespFail (writer, err.Error ())} else {util.RespOk (writer, user, ")}}. # # / / app/service/user.go # #. Type UserService struct {} / / user registration func (s * UserService) UserRegister (mobile, plainPwd, nickname, avatar, sex string) (user model.User, err error) {registerUser: = model.User {} _, err = model.DbEngine.Where ("mobile=?", mobile). Get (& registerUser) if err! = nil {return registerUser, err} / / if the user has already registered Return error message if registerUser.Id > 0 {return registerUser, errors.New ("the mobile number is registered")} registerUser.Mobile = mobile registerUser.Avatar = avatar registerUser.Nickname = nickname registerUser.Sex = sex registerUser.Salt = fmt.Sprintf ("d", rand.Int31n (10000)) registerUser.Passwd = util.MakePasswd (plainPwd) RegisterUser.Salt) registerUser.Createat = time.Now () / / insert user information _, err = model.DbEngine.InsertOne (& registerUser) return registerUser, err}. # # / / main.go # #. Func main () {http.HandleFunc ("/ user/register", controller.UserRegister)}

First of all, we use util.Bind (request, & user) to bind user parameters to the user object, using the Bind function in the util package. Readers can study the specific implementation details by themselves, mainly imitating the parameter binding of the Gin framework, which can be used immediately, which is very convenient.

Then we search for whether the database already exists according to the user's mobile phone number, and insert it into the database if it does not exist, and return the registration success information. The logic is very simple.

The login logic is simpler:

# # / / app/controller/user.go # #... / user logs in to func UserLogin (writer http.ResponseWriter Request * http.Request) {request.ParseForm () mobile: = request.PostForm.Get ("mobile") plainpwd: = request.PostForm.Get ("passwd") / / check parameter if len (mobile) = = 0 | | len (plainpwd) = = 0 {util.RespFail (writer, "incorrect username or password")} loginUser, err: = UserService.Login (mobile) Plainpwd) if err! = nil {util.RespFail (writer, err.Error ())} else {util.RespOk (writer, loginUser, "")}}. # # / / app/service/user.go # #. Func (s * UserService) Login (mobile, plainpwd string) (user model.User, err error) {/ / Database operation loginUser: = model.User {} model.DbEngine.Where ("mobile =?", mobile). Get (& loginUser) if loginUser.Id = = 0 {return loginUser, errors.New ("user does not exist") / / determine whether the password is correct if! util.ValidatePasswd (plainpwd, loginUser.Salt) LoginUser.Passwd) {return loginUser, errors.New ("incorrect password")} / / refresh the token value of the user login token: = util.GenRandomStr (32) loginUser.Token = token model.DbEngine.ID (loginUser.Id) .Cols ("token") .Update (& loginUser) / / returns the new user information return loginUser Nil}... # # / / main.go # #. Func main () {http.HandleFunc ("/ user/login", controller.UserLogin)}

The login logic is implemented, and then we go to the user home page, where a list of users is listed, and you can click to enter the chat page.

Users can also click on the Tab bar below to view their group, from which they can enter the group chat page.

The specific work also requires readers to develop their own user lists, add friends, create groups, add groups and other functions, these are some ordinary API development work, our code procedures are also implemented, readers can take to modify and use, here is no longer demonstrated.

Let's focus on user authentication. User authentication means that when a user clicks a chat to enter the chat interface, the client will send a GET request to the server.

Request to establish a WebSocket persistent connection. After receiving the request to establish a connection, the server verifies the client request to confirm whether the persistent connection is established, and then adds the handle of the persistent connection to the Map (because the server not only serves one client, there may be tens of thousands of persistent connections).

Let's take a look at the specific code implementation:

# # / / app/controller/chat.go # #. / / this core is to form the mapping relationship between userid and Node type Node struct {Conn * websocket.Conn / / parallel to serial, DataQueue chan [] byte GroupSets set.Interface}. / / userid and Node mapping table var clientMap map [int64] * Node = make (map [int64] * Node, 0) / / read-write lock var rwlocker sync.RWMutex / / func Chat (writer http.ResponseWriter, request * http.Request) {query: = request.URL.Query () id: = query.Get ("id") token: = query.Get ("token") userId, _: = strconv.ParseInt (id, 10) 64) / / verify whether token is valid islegal: = checkToken (userId, token) conn, err: = (& websocket.Upgrader {CheckOrigin: func (r * http.Request) bool {return islegal},}) .upgrade (writer, request) Nil) if err! = nil {log.Println (err.Error ()) return} / / get the websocket link conn node: = & Node {Conn: conn, DataQueue: make (chan [] byte, 50), GroupSets: set.New (set.ThreadSafe),} / / get all user groups Id comIds: = concatService.SearchComunityIds (userId) for _ V: = range comIds {node.GroupSets.Add (v)} rwlocker.Lock () clientMap [userId] = node rwlocker.Unlock () / / enable go sendproc (node) / / open the cooperative process to complete the receiving logic go recvproc (node) sendMsg (userId, [] byte ("welcome!"))}. / / verify whether token is valid func checkToken (userId int64, token string) bool {user: = UserService.Find (userId) return user.Token = = token}. # # / / main.go # #. Func main () {http.HandleFunc ("/ chat", controller.Chat)}.

When you enter the chat room, the client initiates a GET request for / chat, and the server first creates a Node structure to store the WebSocket persistent connection handle established with the client.

Each handle has a pipe DataQueue, which is used to send and receive messages. GroupSets is the group information corresponding to the client, which we will mention later.

Type Node struct {Conn * websocket.Conn / / parallel serial transfer, DataQueue chan [] byte GroupSets set.Interface}

The server creates a Map that associates the client user ID with its Node:

/ / userid and Node mapping table var clientMap map [int64] * Node = make (map [int64] * Node, 0)

Next is the main user logic. After receiving the parameters of the client, the server first verifies whether Token is legal, so as to determine whether to upgrade the HTTP protocol to the WebSocket protocol and establish a persistent connection. This step is called authentication.

/ / verify whether token is valid islegal: = checkToken (userId, token) conn, err: = (& websocket.Upgrader {CheckOrigin: func (r * http.Request) bool {return islegal},}) .upgrade (writer, request, nil)

After successful authentication, the server initializes a Node, searches the group ID to which the client user belongs, and populates it in the group's GroupSets attribute.

Then add the Node node to the ClientMap to maintain, we must lock the operation of ClientMap, because the Go language in the case of concurrency, the operation of Map does not guarantee atomic security:

/ / get the websocket link conn node: = & Node {Conn: conn, DataQueue: make (chan [] byte, 50), GroupSets: set.New (set.ThreadSafe),} / / get all user groups Id comIds: = concatService.SearchComunityIds (userId) for _ V: = range comIds {node.GroupSets.Add (v)} rwlocker.Lock () clientMap [userId] = node rwlocker.Unlock ()

After a long link is established between the server and the client, two protocols are opened to deal with the sending and receiving of client messages. For the Go language, the cost of maintaining the protocol is very low.

So our stand-alone program can easily support thousands of users to chat, which is still in the absence of optimization.

. / / enable go sendproc (node) / / enable the receiving logic go recvproc (node) sendMsg (userId, [] byte ("welcome!")).

At this point, our authentication work has been completed, the connection between the client and the server has been established, and then let's implement the specific chat function.

④ implements single chat and group chat.

In the process of realizing the chat, the design of the message body is very important, the design of the message body is reasonable, the function is very convenient to expand, and it is relatively simple to maintain and optimize later.

Let's take a look at the design of our message body:

# # / / app/controller/chat.go # # type Message struct {Id int64 `json: "id,omitempty" form: "id" `/ / message ID Userid int64 `json: "userid Omitempty "form:" userid "`/ who sent Cmd int `json:" cmd,omitempty "form:" cmd "` / group chat or private chat Dstid int64 `json:" dstid,omitempty "form:" dstid "`/ / a pair of end users ID/ group ID Media int `json:" media,omitempty "form:" media "` / / what style the message displays Content string `json:" content Omitempty "form:" content "`/ / the content of the message Pic string `json:" pic,omitempty "form:" pic "` / Preview image Url string `json:" url,omitempty "form:" url "`/ / URL Memo string `json of the service:" memo,omitempty "form:" memo "` / / brief description Amount int `json:" amount,omitempty "form:" amount "`/ / other numbers-related}

Each message has a unique ID, and we can persist the message in the future, but this work is not done in our system, and readers can do it on their own according to their needs.

Then there is userid, the user who initiated the message, corresponding to dstid, to whom the message is to be sent. Another very important parameter is cmd, which indicates whether it is a group chat or a private chat.

The code processing logic of group chat is different from that of private chat, for which we define some cmd constants:

/ / define the command line format const (CmdSingleMsg = 10 CmdRoomMsg = 11 CmdHeart = 0)

Media is a media type. We all know that Wechat supports voice, video and various other file transfers. After we set this parameter, readers can expand these functions on their own.

Content is the text of a message and is the most commonly used form of chat. Pic and URL are set for pictures and other linked resources.

Memo is a brief introduction, and Amount is information related to numbers. For example, red packet business may use this field.

This is the design of the message body. Based on this message body, let's take a look at how the server sends and receives messages and implements individual chat and group chat.

Starting from the previous section, we open two processes for each client long link to send and receive messages, and the logic of chat is implemented in these two processes.

# # / / app/controller/chat.go # #. / / send logic func sendproc (node * Node) {for {select {case data: =

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report