websocket 聊天室数据结构分析
- 首先要做一个聊天室我们需要把所有的连接信息都保存下来
- 所以就需要有一个客户端
client
的 manager
,manager
里应该保存所有的client
信息 - 所以在我们的程序里定义了
ClientManager
这个结构体 - 用
clients
这个 map
结构来保存所有的连接信息 - 遍历
clients
通过使用 broadcast
这个 channel
把 web
端传送来的消息分发给所有的客户端client
- 其次每个成功建立长连接的
client
开一个 read
协程和 wrtie
协程 read
协程不断读取 web
端输入的 meaasge
,并把 message
传递给 boradcast
,让 manager
遍历 clients
把 message
通过 broadcast
channel
,传递给各个客户端 client
的 send
channel
write
协程不断的将 send
channel
里的消息发送给 web
端
流程图
具体代码
package main
import (
"encoding/json"
"fmt"
"net/http"
"github.com/gorilla/websocket"
"github.com/satori/go.uuid"
)
type ClientManager struct {
clients map[*Client]bool
broadcast chan []byte
register chan *Client
unregister chan *Client
}
type Client struct {
id string
socket *websocket.Conn
send chan []byte
}
type Message struct {
Sender string `json:"sender,omitempty"`
Recipient string `json:"recipient,omitempty"`
Content string `json:"content,omitempty"`
}
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 {
case conn := <-manager.register:
manager.clients[conn] = true
jsonMessage, _ := json.Marshal(&Message{Content: "/A new socket has connected."})
manager.send(jsonMessage, conn)
case conn := <-manager.unregister:
if _, ok := manager.clients[conn]; ok {
close(conn.send)
delete(manager.clients, conn)
jsonMessage, _ := json.Marshal(&Message{Content: "/A socket has disconnected."})
manager.send(jsonMessage, conn)
}
case message := <-manager.broadcast:
for conn := range manager.clients {
select {
case conn.send <- message:
default:
close(conn.send)
delete(manager.clients, conn)
}
}
}
}
}
func (manager *ClientManager) send(message []byte, ignore *Client) {
for conn := range manager.clients {
if conn != ignore {
conn.send <- message
}
}
}
func (c *Client) read() {
defer func() {
manager.unregister <- c
c.socket.Close()
}()
for {
_, message, err := c.socket.ReadMessage()
if err != nil {
manager.unregister <- c
c.socket.Close()
break
}
jsonMessage, _ := json.Marshal(&Message{Sender: c.id, Content: string(message)})
manager.broadcast <- jsonMessage
}
}
func (c *Client) write() {
defer func() {
c.socket.Close()
}()
for {
select {
case message, ok := <-c.send:
if !ok {
c.socket.WriteMessage(websocket.CloseMessage, []byte{})
return
}
c.socket.WriteMessage(websocket.TextMessage, message)
}
}
}
func main() {
fmt.Println("Starting application...")
go manager.start()
http.HandleFunc("/ws", wsHandler)
http.ListenAndServe(":8011", nil)
}
func wsHandler(res http.ResponseWriter, req *http.Request) {
conn, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(res, req, nil)
if err != nil {
http.NotFound(res, req)
return
}
client := &Client{id: uuid.Must(uuid.NewV4()).String(), socket: conn, send: make(chan []byte)}
manager.register <- client
go client.read()
go client.write()
}
package main
import (
"flag"
"fmt"
"github.com/gorilla/websocket"
"net/url"
)
var addr = flag.String("addr", "localhost:8011", "http service address")
func main() {
u := url.URL{Scheme: "ws", Host: *addr, Path: "/ws"}
var dialer *websocket.Dialer
conn, _, err := dialer.Dial(u.String(), nil)
if err != nil {
fmt.Println(err)
return
}
for {
_, message, err := conn.ReadMessage()
if err != nil {
fmt.Println("read:", err)
return
}
fmt.Printf("received: %s\n", message)
}
}
<html>
<head>
<title>Golang Chat</title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
$(function() {
var conn;
var msg = $("#msg");
var log = $("#log");
function appendLog(msg) {
var d = log[0]
var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight;
msg.appendTo(log)
if (doScroll) {
d.scrollTop = d.scrollHeight - d.clientHeight;
}
}
$("#form").submit(function() {
if (!conn) {
return false;
}
if (!msg.val()) {
return false;
}
conn.send(msg.val());
msg.val("");
return false
});
if (window["WebSocket"]) {
conn = new WebSocket("ws://localhost:12345/ws");
conn.onclose = function(evt) {
appendLog($("<div><b>Connection Closed.</b></div>"))
}
conn.onmessage = function(evt) {
appendLog($("<div/>").text(evt.data))
}
} else {
appendLog($("<div><b>WebSockets Not Support.</b></div>"))
}
});
</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="发送" />
<input type="text" id="msg" size="64"/>
</form>
</body>
</html>
- 本文章是对 一蓑烟雨1989 文章的分析 ,如想了解原文章可以点击超连接查看原文