├── LICENSE
├── README.md
├── clients
└── client.go
├── message-chat
├── Different room.html
├── First room.html
├── README.md
├── Same room.html
├── config
│ └── config.go
├── docs
│ ├── pic.png
│ └── test.toml
├── main.go
├── middlewares
│ ├── config.go
│ └── redis.go
└── server
│ ├── client.go
│ └── hub.go
└── server
└── server.go
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 hcf
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # websocket-gin-demo
2 | gin websocket 服务端与客户端长连接 服务端进行消息推送
3 |
4 | `cd server` and `go run main.go`
5 |
6 | `cd client` and `go run main.go`
7 |
8 | you can send message to client..
9 |
10 |
11 | 添加多人多个聊天室demo在**message-chat**
12 | Add multiple people to multiple chat room demos in **message-chat**
13 | 
--------------------------------------------------------------------------------
/clients/client.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net/url"
6 | "time"
7 |
8 | "github.com/gorilla/websocket"
9 | )
10 |
11 | func main() {
12 | u := url.URL{Scheme: "ws", Host: "localhost:8009", Path: "/ws"}
13 | log.Printf("connecting to %s", u.String())
14 |
15 | c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
16 | if err != nil {
17 | log.Fatal("dial:", err)
18 | }
19 | defer c.Close()
20 |
21 | go func() {
22 | for {
23 | _, message, err := c.ReadMessage()
24 | if err != nil {
25 | log.Println("read:", err)
26 | return
27 | }
28 | log.Printf("recv: %s", message)
29 | }
30 | }()
31 | time.Sleep(100 * time.Second)
32 | }
33 |
--------------------------------------------------------------------------------
/message-chat/Different room.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Chat Example
5 |
50 |
83 |
84 |
85 |
86 |
90 |
91 |
--------------------------------------------------------------------------------
/message-chat/First room.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Chat Example
5 |
50 |
83 |
84 |
85 |
86 |
90 |
91 |
--------------------------------------------------------------------------------
/message-chat/README.md:
--------------------------------------------------------------------------------
1 | #GIN+Websocket Multiplayer, multiple chat rooms - demo(GIN+Websocket 多人,多个聊天室-demo)
2 |
3 |
4 | ##中文
5 | 改变程序内的import包,为自己的目录结构。
6 | 运行
7 | ```
8 | go run main.go
9 | ```
10 | 启动后台websocket服务
11 | 依次打开
12 | **First room.html**
13 | **Same room.html**
14 | **Different room.html**
15 |
16 | 模拟3个用户,进行聊天室聊天
17 | 如图
18 | 
19 |
20 | ##English
21 |
22 |
23 | Change the import package inside the program to its own directory structure.
24 | Run
25 | ```
26 | go run main.go
27 | ```
28 |
29 | Start the background websocket service
30 | Open sequentially
31 | **First room.html**
32 | **Same room.html**
33 | **Different room.html**
34 |
35 | Simulate 3 users and chat in a chat room
36 |
--------------------------------------------------------------------------------
/message-chat/Same room.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Chat Example
5 |
50 |
83 |
84 |
85 |
86 |
90 |
91 |
--------------------------------------------------------------------------------
/message-chat/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/BurntSushi/toml"
5 | )
6 |
7 | // Config 对应配置文件结构
8 | type Config struct {
9 | Listen string `toml:"listen"`
10 | RedisServers map[string]RedisServer `toml:"redisservers"`
11 | }
12 |
13 | // UnmarshalConfig 解析toml配置
14 | func UnmarshalConfig(tomlfile string) (*Config, error) {
15 | c := &Config{}
16 | if _, err := toml.DecodeFile(tomlfile, c); err != nil {
17 | return c, err
18 | }
19 | return c, nil
20 | }
21 |
22 | // RedisServerConf 获取数据库配置
23 | func (c Config) RedisServerConf(key string) (RedisServer, bool) {
24 | s, ok := c.RedisServers[key]
25 | return s, ok
26 | }
27 |
28 | // GetListenAddr 监听地址
29 | func (c Config) GetListenAddr() string {
30 | return c.Listen
31 | }
32 |
33 | // RedisServer 表示 redis 服务器配置
34 | type RedisServer struct {
35 | Addr string `toml:"addr"`
36 | Password string `toml:"password"`
37 | DB int `toml:"db"`
38 | }
39 |
--------------------------------------------------------------------------------
/message-chat/docs/pic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XanCafe/websocket-gin-demo/ff2426d42408e11e87d886c26dcf4f7a4faff92d/message-chat/docs/pic.png
--------------------------------------------------------------------------------
/message-chat/docs/test.toml:
--------------------------------------------------------------------------------
1 | listen = ":8009"
2 | # Redis数据库配置
3 | [redisservers]
4 | # token口令数据库
5 | [redisservers.test]
6 | addr = "127.0.0.1:6379"
7 | password = ""
8 | db = 0
--------------------------------------------------------------------------------
/message-chat/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "runtime"
6 | "test/websocket-gin-demo/message-chat/config"
7 | "test/websocket-gin-demo/message-chat/middlewares"
8 | "test/websocket-gin-demo/message-chat/server"
9 | "time"
10 |
11 | "github.com/gin-contrib/cors"
12 | "github.com/gin-gonic/gin"
13 | "github.com/go-xweb/log"
14 | )
15 |
16 | var (
17 | tomlFile = flag.String("config", "docs/test.toml", "config file")
18 | )
19 |
20 | // init 初始化配置
21 | func init() {
22 | runtime.GOMAXPROCS(runtime.NumCPU())
23 | gin.SetMode(gin.DebugMode)
24 | }
25 | func main() {
26 | flag.Parse()
27 | // 解析配置文件
28 | tomlConfig, err := config.UnmarshalConfig(*tomlFile)
29 | if err != nil {
30 | log.Printf("UnmarshalConfig: err:%v\n", err)
31 | return
32 | }
33 | // 绑定路由,及公共的tomlConfig
34 | //router := gin.Default()
35 | router := gin.New()
36 | router.Use(gin.Recovery())
37 | //设置跨域
38 | router.Use(cors.New(cors.Config{
39 | AllowAllOrigins: true,
40 | AllowMethods: []string{"GET", "PUT", "POST", "DELETE", "OPTIONS"},
41 | AllowHeaders: []string{"x-xq5-jwt", "Content-Type", "Origin", "Content-Length"},
42 | ExposeHeaders: []string{"x-xq5-jwt"},
43 | AllowCredentials: true,
44 | MaxAge: 12 * time.Hour,
45 | }))
46 | router.Use(middlewares.Config(tomlConfig))
47 | // router.Use(middlewares.Redis("test", tomlConfig))
48 | hub := server.NewHub()
49 | go hub.Run()
50 | router.GET("/ws", func(c *gin.Context) { server.ServeWs(hub, c) })
51 | log.Println("run websocket at ", tomlConfig.GetListenAddr())
52 | router.Run(tomlConfig.GetListenAddr())
53 | }
54 |
--------------------------------------------------------------------------------
/message-chat/middlewares/config.go:
--------------------------------------------------------------------------------
1 | package middlewares
2 |
3 | import (
4 | "test/websocket-gin-demo/message-chat/config"
5 |
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | // Config .
10 | func Config(tomlConfig *config.Config) gin.HandlerFunc {
11 | return func(c *gin.Context) {
12 | c.Set("config", tomlConfig)
13 | c.Next()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/message-chat/middlewares/redis.go:
--------------------------------------------------------------------------------
1 | package middlewares
2 |
3 | import (
4 | "fmt"
5 | "test/websocket-gin-demo/message-chat/config"
6 | "time"
7 |
8 | "github.com/garyburd/redigo/redis"
9 | "github.com/gin-gonic/gin"
10 | )
11 |
12 | // Redis .
13 | func Redis(cacheName string, tomlConfig *config.Config) gin.HandlerFunc {
14 | cacheConfig, ok := tomlConfig.RedisServerConf(cacheName)
15 | if !ok {
16 | panic(fmt.Sprintf("%v not set.", cacheName))
17 | }
18 |
19 | // 链接数据库
20 | pool := newPool(cacheConfig.Addr, cacheConfig.Password, cacheConfig.DB)
21 |
22 | return func(c *gin.Context) {
23 | c.Set(cacheName, pool)
24 | c.Next()
25 | }
26 | }
27 |
28 | // newPool New redis pool.
29 | func newPool(server, password string, db int) *redis.Pool {
30 | return &redis.Pool{
31 | MaxIdle: 3,
32 | IdleTimeout: 240 * time.Second,
33 | Dial: func() (redis.Conn, error) {
34 | c, err := redis.Dial("tcp", server,
35 | redis.DialPassword(password),
36 | redis.DialDatabase(db),
37 | redis.DialConnectTimeout(500*time.Millisecond),
38 | redis.DialReadTimeout(500*time.Millisecond),
39 | redis.DialWriteTimeout(500*time.Millisecond))
40 | if err != nil {
41 | return nil, err
42 | }
43 |
44 | return c, err
45 | },
46 |
47 | TestOnBorrow: func(c redis.Conn, t time.Time) error {
48 | if time.Since(t) < 5*time.Second {
49 | return nil
50 | }
51 | _, err := c.Do("PING")
52 | return err
53 | },
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/message-chat/server/client.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "log"
7 |
8 | "net/http"
9 | "time"
10 |
11 | "github.com/gin-gonic/gin"
12 |
13 | "github.com/gorilla/websocket"
14 | )
15 |
16 | const (
17 | // Time allowed to write a message to the peer.
18 | writeWait = 10 * time.Second
19 |
20 | // Time allowed to read the next pong message from the peer.
21 | pongWait = 60 * time.Second
22 |
23 | // Send pings to peer with this period. Must be less than pongWait.
24 | pingPeriod = (pongWait * 9) / 10
25 |
26 | // Maximum message size allowed from peer.
27 | maxMessageSize = 512
28 | )
29 |
30 | var (
31 | newline = []byte{'\n'}
32 | space = []byte{' '}
33 | )
34 |
35 | var upgrader = websocket.Upgrader{
36 | ReadBufferSize: 1024,
37 | WriteBufferSize: 1024,
38 | }
39 |
40 | // Client is a middleman between the websocket connection and the hub.
41 | type Client struct {
42 | hub *Hub
43 |
44 | // The websocket connection.
45 | conn *websocket.Conn
46 |
47 | // Buffered channel of outbound messages.
48 | send chan []byte
49 |
50 | // 用户名称
51 | username []byte
52 | // 房间号
53 | roomID []byte
54 | }
55 |
56 | // readPump pumps messages from the websocket connection to the hub.
57 | //
58 | // The application runs readPump in a per-connection goroutine. The application
59 | // ensures that there is at most one reader on a connection by executing all
60 | // reads from this goroutine.
61 | func (c *Client) readPump() {
62 | defer func() {
63 | c.hub.unregister <- c
64 | c.conn.Close()
65 | }()
66 | c.conn.SetReadLimit(maxMessageSize)
67 | c.conn.SetReadDeadline(time.Now().Add(pongWait))
68 | c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
69 | for {
70 | _, message, err := c.conn.ReadMessage()
71 | if err != nil {
72 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
73 | log.Printf("error: %v", err)
74 | }
75 | break
76 | }
77 | message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
78 | message = []byte(string(c.roomID) + "&" + string(c.username) + ":" + string(message))
79 | fmt.Println(string(message))
80 | c.hub.broadcast <- []byte(message)
81 | }
82 | }
83 |
84 | // writePump pumps messages from the hub to the websocket connection.
85 | //
86 | // A goroutine running writePump is started for each connection. The
87 | // application ensures that there is at most one writer to a connection by
88 | // executing all writes from this goroutine.
89 | func (c *Client) writePump() {
90 | ticker := time.NewTicker(pingPeriod)
91 | defer func() {
92 | ticker.Stop()
93 | c.conn.Close()
94 | }()
95 | for {
96 | select {
97 | case message, ok := <-c.send:
98 | c.conn.SetWriteDeadline(time.Now().Add(writeWait))
99 | if !ok {
100 | // The hub closed the channel.
101 | c.conn.WriteMessage(websocket.CloseMessage, []byte{})
102 | return
103 | }
104 |
105 | w, err := c.conn.NextWriter(websocket.TextMessage)
106 | if err != nil {
107 | return
108 | }
109 | // 使用“&”分割获取房间号
110 | // 聊天内容不得包含&字符
111 | // msg[0]为房间号 msg[1]为打印内容
112 | // msg := strings.Split(string(message), "&")
113 | // if msg[0] == string(c.hub.roomID[c]) {
114 | // w.Write([]byte(msg[1]))
115 | // }
116 | w.Write(message)
117 | // Add queued chat messages to the current websocket message.
118 | // n := len(c.send)
119 | // for i := 0; i < n; i++ {
120 | // if msg[0] == string(c.hub.roomID[c]) {
121 | // w.Write(newline)
122 | // w.Write(<-c.send)
123 | // }
124 | // }
125 | if err := w.Close(); err != nil {
126 | log.Printf("error: %v", err)
127 | return
128 | }
129 | case <-ticker.C:
130 | c.conn.SetWriteDeadline(time.Now().Add(writeWait))
131 | if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
132 | return
133 | }
134 | }
135 | }
136 | }
137 |
138 | // ChatRequest 聊天室请求
139 | type ChatRequest struct {
140 | RoomID string `json:"room_id" form:"room_id"`
141 | UserID int `json:"user_id" form:"user_id"`
142 | UserName string `json:"user_name" form:"user_name"`
143 | }
144 |
145 | // ServeWs handles websocket requests from the peer.
146 | func ServeWs(hub *Hub, c *gin.Context) {
147 | // 获取前端数据
148 | var req ChatRequest
149 | if err := c.ShouldBind(&req); err != nil {
150 | log.Printf("ServeWs err:%v\n", err)
151 | c.JSON(http.StatusOK, gin.H{"errno": "-1", "errmsg": "参数不匹配,请重试"})
152 | return
153 | }
154 | userName := req.UserName
155 | roomID := req.RoomID
156 | // 获取redis连接(暂未使用)
157 | // pool := c.MustGet("test").(*redis.Pool)
158 | // redisConn := pool.Get()
159 | // defer redisConn.Close()
160 | // 将网络请求变为websocket
161 | var upgrader = websocket.Upgrader{
162 | // 解决跨域问题
163 | CheckOrigin: func(r *http.Request) bool {
164 | return true
165 | },
166 | }
167 | conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
168 | if err != nil {
169 | log.Println(err)
170 | return
171 | }
172 | fmt.Println(userName)
173 | client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256), username: []byte(userName), roomID: []byte(roomID)}
174 | client.hub.register <- client
175 |
176 | // Allow collection of memory referenced by the caller by doing all work in
177 | // new goroutines.
178 | go client.writePump()
179 | go client.readPump()
180 | }
181 |
--------------------------------------------------------------------------------
/message-chat/server/hub.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import "strings"
4 |
5 | // Hub maintains the set of active clients and broadcasts messages to the
6 | // clients.
7 | type Hub struct {
8 | // Registered clients.
9 | clients map[*Client]bool
10 |
11 | // Inbound messages from the clients.
12 | broadcast chan []byte
13 |
14 | // Register requests from the clients.
15 | register chan *Client
16 |
17 | // Unregister requests from clients.
18 | unregister chan *Client
19 |
20 | // 房间号 key:client value:房间号
21 | roomID map[*Client]string
22 | }
23 |
24 | // NewHub .
25 | func NewHub() *Hub {
26 | return &Hub{
27 | broadcast: make(chan []byte),
28 | register: make(chan *Client),
29 | unregister: make(chan *Client),
30 | clients: make(map[*Client]bool),
31 | roomID: make(map[*Client]string),
32 | }
33 | }
34 |
35 | // Run .
36 | func (h *Hub) Run() {
37 | for {
38 | select {
39 | case client := <-h.register:
40 | h.clients[client] = true // 注册client端
41 | h.roomID[client] = string(client.roomID) // 给client端添加房间号
42 | case client := <-h.unregister:
43 | if _, ok := h.clients[client]; ok {
44 | delete(h.clients, client)
45 | delete(h.roomID, client)
46 | close(client.send)
47 | }
48 | case message := <-h.broadcast:
49 | for client := range h.clients {
50 | // 使用“&”对message进行message切割 获取房间号
51 | // 向信息所属的房间内的所有client 内添加send
52 | // msg[0]为房间号 msg[1]为打印内容
53 | msg := strings.Split(string(message), "&")
54 | if string(client.roomID) == msg[0] {
55 | select {
56 | case client.send <- []byte(msg[1]):
57 | default:
58 | close(client.send)
59 | delete(h.clients, client)
60 | delete(h.roomID, client)
61 | }
62 | }
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/server/server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 |
8 | "github.com/gin-gonic/gin"
9 | "github.com/gorilla/websocket"
10 | )
11 |
12 | var upgrader = websocket.Upgrader{} // use default options
13 | var messageChan = make(chan string)
14 |
15 | //此处为服务端接收 需要推送的信息
16 | func updateMsg() {
17 | for {
18 | var messageBuf string
19 | fmt.Scanln(&messageBuf)
20 | messageChan <- messageBuf
21 | }
22 | }
23 |
24 | //此处为websocket 执行的长连接 函数
25 | func echo(w http.ResponseWriter, r *http.Request) {
26 | var upgrader = websocket.Upgrader{
27 | // 解决跨域问题
28 | CheckOrigin: func(r *http.Request) bool {
29 | return true
30 | },
31 | }
32 | c, err := upgrader.Upgrade(w, r, nil)
33 | if err != nil {
34 | log.Print("upgrade:", err)
35 | return
36 | }
37 | defer c.Close()
38 | for {
39 | //信息推送 WriteMessage
40 | //updateMsg()
41 | c.WriteMessage(websocket.TextMessage, []byte(<-messageChan))
42 | }
43 | }
44 |
45 | func main() {
46 | go updateMsg()
47 | router := gin.Default()
48 | router.GET("/ws", func(c *gin.Context) {
49 | echo(c.Writer, c.Request)
50 | })
51 | router.Run(":8999")
52 | }
53 |
--------------------------------------------------------------------------------