___________ WebSocket is introduced

In a typical Web application, the only way to achieve real-time page refresh is to use Ajax to poll regularly. After all, ajax only makes the page look real time, and there is actually some lag. Also, Ajax requires frequent HTTP requests to the server, which is inefficient. That’s where WebSocket came in.

WebSocket can be thought of as an upgraded HTTP connection with a lifetime until the server or client actively closes the connection. Websockets allow two-way communication between clients and servers over a TCP long connection, which greatly reduces the overhead of polling.

⒉ Data structure definition

  • First, you need to document the mapping between the user and the connection
  • Then you need a pipe to send the message
  • You also need a structure to define the message
  • Finally, a upgrader is needed to upgrade HTTP requests to WebSocket connections
import "github.com/gorilla/websocket"

type Message struct {
    User	string	`json:"user"`
    Info	string	`json:"message,omitempty"`
}

var Clients map[*websocket.Conn]string
var Broadcast chan Message
var upgrader websocket.Upgrader

func init(a) {
    upgrader = websocket.Upgrader{
		ReadBufferSize:    1024,
		WriteBufferSize:   1024,
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}

    Clients = make(map[*websocket.Conn]string)
    Broadcast = make(chan Message)
}
Copy the code

C. Establish the server

To establish the server, you first need to define the route. The definition of the route was mentioned in the previous article “Golang Developing REST-style apis” and will not be discussed here, except to document the establishment and response of the connection.

import (
    "fmt"
    "log"
    "net/http"
)

func Chat(w http.ResponseWriter, r *http.Request) {
    ws, err := upgrader.Upgrade(w, r, nil)
    iferr ! =nil {
        log.Fatal(err)
    }
    defer ws.Close()

    var msg structure.Message

    for {
        err = ws.ReadJSON(&msg)
        iferr ! =nil {
                log.Printf("error %v", err)
                delete(Clients, ws)
                break
        }
        if_, ok := Clients[ws]; ! ok { Clients[ws] = msg.User } fmt.Printf("%+v", Clients)
        fmt.Println("Server receives message", msg)
        if msg.Info == "" {
                continue
        }

        Broadcast <- msg
    }
}
Copy the code
  • In the process of receiving messages from the client, if an exception occurs, the connection is closed and the connection and the corresponding user are removed from the mapping table
  • When the client establishes a connection for the first time, the mapping between the connection and the user does not exist in the mapping table. Therefore, you need to add the mapping
  • Empty messages sent by clients are not distributed

The news was distributed to the host.

Messages sent by each user in the chat room need to be synchronized to other users. Therefore, this requires a separate Goroutine to listen to the message pipeline and distribute messages.

func DistributeMsg(a) {
    fmt.Println("distribute")
    for {
        msg := <- Broadcast
        fmt.Println("Ready to send message to client", msg)

        for ws, user := range Clients {
            fmt.Println(user)
            if user == msg.User {
                continue
            }
            err := ws.WriteJSON(msg)
            iferr ! =nil {
                fmt.Printf("error %v", err)
                _ = ws.Close()
                fmt.Printf("user %s disconnected", Clients[ws])
                delete(Clients, ws)
            }
        }
    }
}
Copy the code

During message distribution, messages of the same user are not distributed to the user. In addition, if an exception occurs in the process of sending a message to the client, the connection needs to be closed and the mapping relationship between the responding connection and the user needs to be removed from the mapping table.

5. Build a client

The client is implemented using Vue, with the help of the ElementUi component.

    import {Notification} from 'element-ui';
    let base_url = 'the ws: / / 127.0.0.1:8081 / ws';

    export default {
        name: 'ChatRoom'.data(){
            return {
                nickname: ' '.message: ' '.messages: ' '.socket: null,}},methods: {
            // Send the message
            send() {
                let info = this.nickname + ':' + this.message + '\n';
                let data = {
                    user: this.nickname,
                    message: this.message
                };
                this.socket.send(JSON.stringify(data));
                this.messages += info;
                this.message = ' ';
            },
            // Join the chat room
            join() {
                this.$prompt('Please enter a nickname'.'tip', {
                    confirmButtonText: 'sure'.inputPlaceholder: 'Please enter a nickname'.inputErrorMessage: 'Nicknames cannot be empty'.inputValidator: function ($event) {
                    return $event.length > 0
                    }
                }).then(({ value }) = > {
                    this.nickname = value;
                    // Initiates a WebSocket connection
                    this.createWebSocket();
                }).catch(() = > {
                    console.log('Cancel input')})},// Client closes the connection
            disconnect() {
                if (this.socket ! = =undefined && this.socket ! = =null) {
                    this.socket.close();
                }
                this.socket = null;
                this.nickname = ' ';
            },
            // Response to the browser refresh time
            leaving($event) {
                console.log($event);
                console.log(this.socket);
                if (this.socket ! = =undefined && this.socket ! = =null) {
                    this.socket.close(); }},// The client establishes a WebSocket connection
            createWebSocket() {
                if (this.socket === null) {
                    this.socket = new WebSocket(base_url)
                }

                this.socket.onopen = (event) = > {
                    console.log(event);
                    console.log('connected');
                    Notification.success('Connection established');
                    // When the connection is first established, send the nickname
                    let data = {user: this.nickname};
                    this.socket.send(JSON.stringify(data));
                };

                this.socket.onmessage = (event) = > {
                    console.log(event);
                    let data = JSON.parse(event.data)
                    let info = data.user + ':' + data.message + '\n';
                    Notification.info(info);
                    // Message content processing
                    this.messages += info
                };

                this.socket.onerror = (event) = > {
                    console.log(event);
                    console.log('error');
                    Notification.error('Server running exception');
                };

                this.socket.onclose = (event) = > {
                    console.log(event);
                    console.log('close');
                    Notification.info('Server closed connection');
                    this.nickname = ' ';
                    this.socket = null; }; }},created() {
            // Listen for browser refresh/close events
            window.addEventListener('beforeunload'.this.leaving); }}Copy the code

Note that browser refresh/shutdown will cause server exceptions, so you need to listen for browser refresh/shutdown events, and close the WebSocket connection when the browser refresh/shutdown.

Space is limited, so this is only part of the code. The full code is available in the Github repository.