本节思路
1、redis的go客户端安装
2、基于redis的set集合,实现房间的概念,一个房间对应一个set集合,集合内保存该房间内用户的唯一标识
我们给每个用户生成了唯一标识uuid(后期接入微信小程序,则可以使用微信用户openid代替),于是set集合大致如下:

房间1
用户A-uuid
用户B-uuid
......
房间2
用户C-uuid
用户D-uuid
......
......

3、用户的uuid,又对应着go服务端里面的一个map

ActiveClients = make(map[string]ClientConn)

该map以用户的uuid为key,在线用户的websocket链接为value
于是在发送消息时,取到redis里某房间内所有的uuid,就可以得到对应的websocket链接,实现房间内的广播
如果限制房间内只有2个用户,则实现了一对一私聊

安装redis的go客户端

go get -u github.com/go-redis/redis

服务器端go代码:

package main
import (
    "golang.org/x/net/websocket"
    "fmt"
    "log"
    "net/http"
    "github.com/go-redis/redis"
    "encoding/json" 
)

var (  
    JSON          = websocket.JSON              // codec for JSON  
    Message       = websocket.Message           // codec for string, []byte  
    ActiveClients = make(map[string]ClientConn) // map containing clients  
    User          = make(map[string]string)
)  

type ClientConn struct {
    websocket *websocket.Conn  
}

type UserMsg struct {
    Room string
    Cmd string
    User string
    Content string
    Uuid string
}

func echoHandler(ws *websocket.Conn) {
    var err error  
    var userMsg UserMsg
    
    for {  

        var data []byte
        if err = websocket.Message.Receive(ws, &data); err != nil {  
            fmt.Println("can't receive")  
            break  
        }

        err = json.Unmarshal(data, &userMsg)  
        fmt.Println(userMsg)

        go wsHandler(ws,userMsg)

    }  

}

func wsHandler(ws *websocket.Conn,userMsg UserMsg) {
    sockCli := ClientConn{ws}
    var err error


    redisClient := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })

    //登录
    if userMsg.Cmd == "login" {
        fmt.Println("login") 
        //用户列表新增当前用户
        ActiveClients[userMsg.Uuid] = sockCli
        //保存到redis房间set集合内
        redisClient.SAdd(userMsg.Room,userMsg.Uuid)
    //退出
    } else if userMsg.Cmd == "logout" {
        fmt.Println("logout") 

        delete(ActiveClients,userMsg.Uuid)

        redisClient.SRem(userMsg.Room,userMsg.Uuid)

    //发消息
    } else {

        //从redis取房间内的所有用户uuid
        roomSlice := redisClient.SMembers(userMsg.Room)
        //用户uuid保存到一个go切片online 
        online := roomSlice.Val()

        //循环给房间内用户发送消息
        if len(online) != 0 {
            for _, na := range online {  
                if na != "" {  
                    //ActiveClients[na].websocket就是用户对应的websocket链接
                    if err = websocket.Message.Send(ActiveClients[na].websocket, userMsg.User+"说:"+userMsg.Content); err != nil {  
                        log.Println("Could not send message to ", userMsg.User, err.Error())  
                    }  
                }  
            }
        }


    }
}

func main() {
    http.Handle("/echo", websocket.Handler(echoHandler))
    http.Handle("/", http.FileServer(http.Dir(".")))

    err := http.ListenAndServe(":8929", nil)

    if err != nil {
        panic("ListenAndServe: " + err.Error())
    }
}

为了生成唯一uuid,选择房间号和用户名,客户端代码由php实现(chat.php):

<?php
$room = $_GET["room"];
$username = $_GET["username"];
$uuid = md5(time().$username);
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>Sample of websocket with golang</title>
    <script src="//cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>

    <script>
      $(function() {
        var userAgent = navigator.userAgent;

        var ws = new WebSocket("ws://xxx.xx.xxx.xx:8929/echo");
        ws.onopen = function (evt) {
          //连接成功
          console.log("Connected to WebSocket server.");

          //发送登录信息
          msg = new Object();
          msg.Room = '<?php echo $room;?>';
          msg.Cmd = 'login';
          msg.User = '<?php echo $username;?>';
          msg.Uuid = '<?php echo $uuid;?>';
          ws.send(JSON.stringify(msg));
        };

        ws.onclose = function (evt) {
          
          console.log("Close WebSocket server.");

          //发送退出信息
          msg = new Object();
          msg.Room = '<?php echo $room;?>';
          msg.Cmd = 'logout';
          msg.User = '<?php echo $username;?>';
          msg.Uuid = '<?php echo $uuid;?>';
          ws.send(JSON.stringify(msg));
        };

        ws.onmessage = function(e) {
          $('<li>').text(event.data).appendTo($ul);
        };
        var $ul = $('#msg-list');
        $('#sendBtn').click(function(){

          //发送信息
          msg = new Object();
          msg.Room = '<?php echo $room;?>';
          msg.Cmd = 'send';
          msg.User = '<?php echo $username;?>';
          msg.Uuid = '<?php echo $uuid;?>';
          msg.Content = $('#name').val();
          ws.send(JSON.stringify(msg));

        });
      });
    </script>
</head>
<body>
<input id="name" type="text"/>
<input type="button" id="sendBtn" value="send"/>
<ul id="msg-list"></ul>
</body>
</html>

两个不同浏览器下,分别访问http://yourdomain/chat.php?room=1&username=keyunq1
http://yourdomain/chat.php?room=1&username=keyunq2
则可实现一对一私聊。
不完善之处:对于redis里房间的set集合,程序没有实现删除元素功能,如某用户的websocket链接关闭,set集合里未删除该用户的uuid,则发消息时,继续从set集合里取数据循环,则会发送数据到一个不存在的websocket,产生错误,此处待完善。