一. 引入gin、gorilla websocket包
go get github.com/gin-gonic/gin
go get github.com/gorilla/websocket
二. 服务端代码
package main
import (
"bytes"
"fmt"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"log"
"net/http"
"time"
)
const (
// Time allowed to write a message to the peer.
writeWait = 10 * time.Second
// Time allowed to read the next pong message from the peer.
pongWait = 60 * time.Second
// Send pings to peer with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10
// Maximum message size allowed from peer.
maxMessageSize = 512
)
var (
newline = []byte{'\n'}
space = []byte{' '}
broadcast chan []byte
register chan *websocket.Conn
conns map[*websocket.Conn]bool
unregister chan *websocket.Conn
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
func main() {
// 广播内容管道
broadcast = make(chan []byte)
// 注册连接管道
register = make(chan *websocket.Conn)
// 取消注册连接管道
unregister = make(chan *websocket.Conn)
// 连接池
conns = make(map[*websocket.Conn]bool)
router := gin.Default()
// home页面,有ws连接javascript代码
router.GET("/", serveHome)
// 服务端websocket连接
router.GET("/ws", serveWs)
// websocket广播
go run()
err := router.Run(":8080")
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
return
}
// 主页
func serveHome(ctx *gin.Context) {
w, r := ctx.Writer, ctx.Request
log.Println(r.URL)
if r.URL.Path != "/" {
http.Error(w, "Not found", http.StatusNotFound)
return
}
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
http.ServeFile(w, r, "home.html")
}
// websocket连接api
func serveWs(ctx *gin.Context) {
conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
if err != nil {
fmt.Printf("create conn fail. | err: %s", err)
return
}
register <- conn
go readPump(conn)
go writePump(conn)
}
// 监听读方法
func readPump(conn *websocket.Conn) {
defer func() {
unregister <- conn
conn.Close()
}()
conn.SetReadLimit(maxMessageSize)
conn.SetReadDeadline(time.Now().Add(pongWait))
conn.SetPongHandler(func(string) error { conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
for {
_, message, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("error: %v", err)
}
break
}
message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
broadcast <- message
}
}
// ping保持连接
func writePump(conn *websocket.Conn) {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
conn.Close()
}()
for {
select {
case <-ticker.C:
conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
}
}
}
// 广播方法
func run() {
for {
select {
case conn := <-register:
conns[conn] = true
case conn := <-unregister:
if _, ok := conns[conn]; ok {
delete(conns, conn)
}
case message, ok := <-broadcast:
for conn := range conns {
conn.SetWriteDeadline(time.Now().Add(writeWait))
if !ok {
// The hub closed the channel.
conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
w, err := conn.NextWriter(websocket.TextMessage)
if err != nil {
return
}
w.Write(message)
if err := w.Close(); err != nil {
return
}
}
}
}
}
三. home页代码
<!DOCTYPE html>
<html lang="en">
<head>
<title>Chat Example</title>
<script type="text/javascript">
window.onload = function () {
var conn;
var msg = document.getElementById("msg");
var log = document.getElementById("log");
function appendLog(item) {
var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1;
log.appendChild(item);
if (doScroll) {
log.scrollTop = log.scrollHeight - log.clientHeight;
}
}
document.getElementById("form").onsubmit = function () {
if (!conn) {
return false;
}
if (!msg.value) {
return false;
}
conn.send(msg.value);
msg.value = "";
return false;
};
if (window["WebSocket"]) {
conn = new WebSocket("ws://" + document.location.host + "/ws");
conn.onclose = function (evt) {
var item = document.createElement("div");
item.innerHTML = "<b>Connection closed.</b>";
appendLog(item);
};
conn.onmessage = function (evt) {
var messages = evt.data.split('\n');
for (var i = 0; i < messages.length; i++) {
var item = document.createElement("div");
item.innerText = messages[i];
appendLog(item);
}
};
} else {
var item = document.createElement("div");
item.innerHTML = "<b>Your browser does not support WebSockets.</b>";
appendLog(item);
}
};
</script>
<style type="text/css">
html {
overflow: hidden;
}
body {
overflow: hidden;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
background: gray;
}
#log {
background: white;
margin: 0;
padding: 0.5em 0.5em 0.5em 0.5em;
position: absolute;
top: 0.5em;
left: 0.5em;
right: 0.5em;
bottom: 3em;
overflow: auto;
}
#form {
padding: 0 0.5em 0 0.5em;
margin: 0;
position: absolute;
bottom: 1em;
left: 0px;
width: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="log"></div>
<form id="form">
<input type="submit" value="Send" />
<input type="text" id="msg" size="64" autofocus />
</form>
</body>
</html>
四. 运行多个页面可以看到消息同步类似聊天室
PS:
服务端代码为官方库example的简化版,可以直接看官方示例
websocket/examples/chat at master · gorilla/websocket · GitHub