Build a Simple Websocket Chat Server in Go with Gin
Introduction
In the previous article, we used Golang's Gin framework, combined with the popular Websocket library gorilla/websocket
, to build a simple real-time backend service. This Websocket service just accepts and responds to simple messages. If we need to implement the backend of the chat room, we still need to do some work.
Next, we continue to use Golang + Gin to build a simple chat room background service. Our overall plan is that the Websocket service receives the full amount of data from the front end, and then forwards it in real time to achieve multi-terminal communication of chat messages. At the same time, the server needs to be a client manager to be responsible for the registration and deregistration of the client to achieve a simple chat function backend service.
Solution
For the basic front-end tutorial, please move to Build a Simple Websocket Server in Go with Gin.
Back-end
- Create a new main.go
Note that in the same directory, there can only be one file containing the main
method (Any filename is allowed)
package main
import (
"encoding/json"
"fmt"
"net"
"net/http"
"github.com/gorilla/websocket"
uuid "github.com/satori/go.uuid"
)
//Client management
type ClientManager struct {
//The client map stores and manages all long connection clients, online is TRUE, and those who are not there are FALSE
clients map[*Client]bool
//Web side MESSAGE we use Broadcast to receive, and finally distribute it to all clients
broadcast chan []byte
//Newly created long connection client
register chan *Client
//Newly canceled long connection client
unregister chan *Client
}
//Client
type Client struct {
//User ID
id string
//Connected socket
socket *websocket.Conn
//Message
send chan []byte
}
//Will formatting Message into JSON
type Message struct {
//Message Struct
Sender string `json:"sender,omitempty"` //发送者
Recipient string `json:"recipient,omitempty"` //接收者
Content string `json:"content,omitempty"` //内容
ServerIP string `json:"serverIp,omitempty"` //实际不需要 验证k8s
SenderIP string `json:"senderIp,omitempty"` //实际不需要 验证k8s
}
//Create a client manager
var manager = ClientManager{
broadcast: make(chan []byte),
register: make(chan *Client),
unregister: make(chan *Client),
clients: make(map[*Client]bool),
}
func (manager *ClientManager) start() {
for {
select {
//If there is a new connection access, pass the connection to conn through the channel
case conn := <-manager.register:
//Set the client connection to true
manager.clients[conn] = true
//Format the message of returning to the successful connection JSON
jsonMessage, _ := json.Marshal(&Message{Content: "/A new socket has connected. ", ServerIP: LocalIp(), SenderIP: conn.socket.RemoteAddr().String()})
//Call the client's send method and send messages
manager.send(jsonMessage, conn)
//If the connection is disconnected
case conn := <-manager.unregister:
//Determine the state of the connection, if it is true, turn off Send and delete the value of connecting client
if _, ok := manager.clients[conn]; ok {
close(conn.send)
delete(manager.clients, conn)
jsonMessage, _ := json.Marshal(&Message{Content: "/A socket has disconnected. ", ServerIP: LocalIp(), SenderIP: conn.socket.RemoteAddr().String()})
manager.send(jsonMessage, conn)
}
//broadcast
case message := <-manager.broadcast:
//Traversing the client that has been connected, send the message to them
for conn := range manager.clients {
select {
case conn.send <- message:
default:
close(conn.send)
delete(manager.clients, conn)
}
}
}
}
}
//Define the send method of client management
func (manager *ClientManager) send(message []byte, ignore *Client) {
for conn := range manager.clients {
//Send messages not to the shielded connection
if conn != ignore {
conn.send <- message
}
}
}
//Define the read method of the client structure
func (c *Client) read() {
defer func() {
manager.unregister <- c
_ = c.socket.Close()
}()
for {
//Read message
_, message, err := c.socket.ReadMessage()
//If there is an error message, cancel this connection and then close it
if err != nil {
manager.unregister <- c
_ = c.socket.Close()
break
}
//If there is no error message, put the information in Broadcast
jsonMessage, _ := json.Marshal(&Message{Sender: c.id, Content: string(message), ServerIP: LocalIp(), SenderIP: c.socket.RemoteAddr().String()})
manager.broadcast <- jsonMessage
}
}
func (c *Client) write() {
defer func() {
_ = c.socket.Close()
}()
for {
select {
//Read the message from send
case message, ok := <-c.send:
//If there is no message
if !ok {
_ = c.socket.WriteMessage(websocket.CloseMessage, []byte{})
return
}
//Write it if there is news and send it to the web side
_ = c.socket.WriteMessage(websocket.TextMessage, message)
}
}
}
func main() {
fmt.Println("Starting application...")
//Open a goroutine execution start program
go manager.start()
//Register the default route to /ws, and use the wsHandler method
http.HandleFunc("/ws", wsHandler)
http.HandleFunc("/health", healthHandler)
//Surveying the local 8011 port
fmt.Println("chat server start.....")
//Note that this must be 0.0.0.0 to deploy in the server to use
_ = http.ListenAndServe("0.0.0.0:8448", nil)
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024 * 1024 * 1024,
WriteBufferSize: 1024 * 1024 * 1024,
//Solving cross-domain problems
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func wsHandler(res http.ResponseWriter, req *http.Request) {
//Upgrade the HTTP protocol to the websocket protocol
conn, err := upgrader.Upgrade(res, req, nil)
if err != nil {
http.NotFound(res, req)
return
}
//Every connection will open a new client, client.id generates through UUID to ensure that each time it is different
client := &Client{id: uuid.Must(uuid.NewV4(), nil).String(), socket: conn, send: make(chan []byte)}
//Register a new link
manager.register <- client
//Start the message to collect the news from the web side
go client.read()
//Start the corporation to return the message to the web side
go client.write()
}
func healthHandler(res http.ResponseWriter, _ *http.Request) {
_, _ = res.Write([]byte("ok"))
}
func LocalIp() string {
address, _ := net.InterfaceAddrs()
var ip = "localhost"
for _, address := range address {
if ipAddress, ok := address.(*net.IPNet); ok && !ipAddress.IP.IsLoopback() {
if ipAddress.IP.To4() != nil {
ip = ipAddress.IP.String()
}
}
}
return ip
}
- Install dependencies
go mod tidy
Fron-tend
- New HTML
Create a new index.html
in the current project or in any directory
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Univer Server</title>
</head>
<body>
<script>
var ws = new WebSocket("ws://localhost:8448/ws");
//Triggered when the connection is opened
ws.onopen = function(evt) {
console.log("Connection open...");
ws.send("Hello WebSockets!");
};
//Triggered when a message is received
ws.onmessage = function(evt) {
console.log("Received Message: " + evt.data);
};
//Triggered when the connection is closed
ws.onclose = function(evt) {
console.log("Connection closed.");
};
</script>
</body>
</html>
- start
Use VSCode Live Server to start this HTML file
http://127.0.0.1:5500/index.html
Open a few more pages, and you can see the synchronization of multiple client messages in the browser console.
Comments