├── .DS_Store ├── snap.gif ├── glide.yaml ├── main.go ├── .gitignore ├── utils └── utilities.go ├── README.md ├── glide.lock ├── LICENSE ├── client.go ├── message └── messages.go ├── hub.go └── client └── index.html /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chapin666/simple-drawing-backend/HEAD/.DS_Store -------------------------------------------------------------------------------- /snap.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chapin666/simple-drawing-backend/HEAD/snap.gif -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: simple-drawing-backend 2 | import: 3 | - package: github.com/gorilla/websocket 4 | - package: github.com/lucasb-eyer/go-colorful 5 | - package: github.com/tidwall/gjson 6 | - package: github.com/satori/go.uuid 7 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | hub := newHub() 10 | 11 | go hub.run() 12 | 13 | http.HandleFunc("/ws", hub.handleWebSocket) 14 | err := http.ListenAndServe(":3000", nil) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | vendor/ -------------------------------------------------------------------------------- /utils/utilities.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | colorful "github.com/lucasb-eyer/go-colorful" 8 | ) 9 | 10 | func init() { 11 | rand.Seed(time.Now().UnixNano()) 12 | } 13 | 14 | // GenerateColor function 15 | func GenerateColor() string { 16 | c := colorful.Hsv(rand.Float64()*360.0, 0.8, 0.8) 17 | return c.Hex() 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-drawing-backend 2 | 3 | 此项目使用 GO 语言编写了一个简单白板(涂鸦)服务端功能。 所使用的通信协议是 WebSocket,因此所有的用户都将实时看到彼此的绘制路径。其次,用户还可以为画笔设置颜色等。 4 | 5 | > [点此查看《使用golang构建实时白板》](https://my.oschina.net/997155658/blog/1606346) 6 | 7 | ### Install dependeice 8 | 9 | ``` 10 | $ glide install 11 | ``` 12 | 13 | ### Running 14 | 15 | Buld and run the server. 16 | 17 | ``` 18 | $ go build -o server && ./server 19 | ``` 20 | 21 | Open client/index.html in your browser. 22 | 23 | ### Snapshot 24 | 25 | ![效果截图](snap.gif) 26 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: 7c5c623cc422226a3d531d50f2bacf0c151473abeb9d31a91db6f9f131532f3d 2 | updated: 2017-12-26T23:13:39.706166+08:00 3 | imports: 4 | - name: github.com/gorilla/websocket 5 | version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b 6 | - name: github.com/lucasb-eyer/go-colorful 7 | version: 231272389856c976b7500c4fffcc52ddf06ff4eb 8 | - name: github.com/satori/go.uuid 9 | version: 879c5887cd475cd7864858769793b2ceb0d44feb 10 | - name: github.com/tidwall/gjson 11 | version: e62d62a3e1e9f324346170bbc04333341f803dfb 12 | - name: github.com/tidwall/match 13 | version: 1731857f09b1f38450e2c12409748407822dc6be 14 | testImports: [] 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 chapin 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 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "simple-drawing-backend/utils" 5 | 6 | "github.com/gorilla/websocket" 7 | uuid "github.com/satori/go.uuid" 8 | ) 9 | 10 | // Client struct. 11 | type Client struct { 12 | id string 13 | hub *Hub 14 | color string 15 | socket *websocket.Conn 16 | outbound chan []byte 17 | } 18 | 19 | // newClient return a new client 20 | func newClient(hub *Hub, socket *websocket.Conn) *Client { 21 | return &Client{ 22 | id: uuid.NewV4().String(), 23 | color: utils.GenerateColor(), 24 | hub: hub, 25 | socket: socket, 26 | outbound: make(chan []byte), 27 | } 28 | } 29 | 30 | // read method. 31 | func (client *Client) read() { 32 | defer func() { 33 | client.hub.unregister <- client 34 | }() 35 | 36 | for { 37 | _, data, err := client.socket.ReadMessage() 38 | if err != nil { 39 | break 40 | } 41 | client.hub.onMessage(data, client) 42 | } 43 | } 44 | 45 | // write method. 46 | func (client *Client) write() { 47 | for { 48 | select { 49 | case data, ok := <-client.outbound: 50 | if !ok { 51 | client.socket.WriteMessage(websocket.CloseMessage, []byte{}) 52 | return 53 | } 54 | client.socket.WriteMessage(websocket.TextMessage, data) 55 | } 56 | } 57 | } 58 | 59 | // run method 60 | func (client Client) run() { 61 | go client.read() 62 | go client.write() 63 | } 64 | 65 | // close method 66 | func (client Client) close() { 67 | client.socket.Close() 68 | close(client.outbound) 69 | } 70 | -------------------------------------------------------------------------------- /message/messages.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | const ( 4 | // KindConnected is sent when user connects 5 | KindConnected = iota + 1 6 | // KindUserJoined is sent when someone else joins 7 | KindUserJoined 8 | // KindUserLeft is sent when someone leaves 9 | KindUserLeft 10 | // KindStroke message specifies a drawn stroke by a user 11 | KindStroke 12 | // KindClear message is send when a user clears the screen 13 | KindClear 14 | ) 15 | 16 | // Point struct. 17 | type Point struct { 18 | X int `json:"x"` 19 | Y int `json:"y"` 20 | } 21 | 22 | // User struct. 23 | type User struct { 24 | ID string `json:"id"` 25 | Color string `json:"color"` 26 | } 27 | 28 | // Connected struct. 29 | type Connected struct { 30 | Kind int `json:"kind"` 31 | Color string `json:"color"` 32 | Users []User `json:"users"` 33 | } 34 | 35 | // NewConnected function 36 | func NewConnected(color string, users []User) *Connected { 37 | return &Connected{ 38 | Kind: KindConnected, 39 | Color: color, 40 | Users: users, 41 | } 42 | } 43 | 44 | // UserJoined struct. 45 | type UserJoined struct { 46 | Kind int `json:"kind"` 47 | User User `json:"user"` 48 | } 49 | 50 | // NewUserJoined function 51 | func NewUserJoined(userID string, color string) *UserJoined { 52 | return &UserJoined{ 53 | Kind: KindUserJoined, 54 | User: User{ID: userID, Color: color}, 55 | } 56 | } 57 | 58 | // UserLeft struct. 59 | type UserLeft struct { 60 | Kind int `json:"kind"` 61 | UserID string `json:"userId"` 62 | } 63 | 64 | // NewUserLeft function 65 | func NewUserLeft(userID string) *UserLeft { 66 | return &UserLeft{ 67 | Kind: KindUserLeft, 68 | UserID: userID, 69 | } 70 | } 71 | 72 | // Stroke struct 73 | type Stroke struct { 74 | Kind int `json:"kind"` 75 | UserID string `json:"userId"` 76 | Points []Point `json:"points"` 77 | Finish bool `json:"finish"` 78 | } 79 | 80 | // Clear struct 81 | type Clear struct { 82 | Kind int `json:"kind"` 83 | UserID string `json:"userId"` 84 | } 85 | -------------------------------------------------------------------------------- /hub.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | "simple-drawing-backend/message" 8 | 9 | "github.com/tidwall/gjson" 10 | 11 | "github.com/gorilla/websocket" 12 | ) 13 | 14 | var upgrader = websocket.Upgrader{ 15 | // Allow all origins 16 | CheckOrigin: func(r *http.Request) bool { return true }, 17 | } 18 | 19 | // Hub struct. 20 | type Hub struct { 21 | clients []*Client 22 | register chan *Client 23 | unregister chan *Client 24 | } 25 | 26 | // newHub function create new Hub struct. 27 | func newHub() *Hub { 28 | return &Hub{ 29 | clients: make([]*Client, 0), 30 | register: make(chan *Client), 31 | unregister: make(chan *Client), 32 | } 33 | } 34 | 35 | // run method of Hub 36 | func (hub *Hub) run() { 37 | for { 38 | select { 39 | case client := <-hub.register: 40 | hub.onConnect(client) 41 | case client := <-hub.unregister: 42 | hub.onDisconnect(client) 43 | } 44 | } 45 | } 46 | 47 | // handleWebSocket method process socket message. 48 | func (hub *Hub) handleWebSocket(w http.ResponseWriter, r *http.Request) { 49 | // check client is supported for websocket. 50 | socket, err := upgrader.Upgrade(w, r, nil) 51 | if err != nil { 52 | log.Println(err) 53 | http.Error(w, "could not upgrade", http.StatusInternalServerError) 54 | return 55 | } 56 | client := newClient(hub, socket) 57 | hub.clients = append(hub.clients, client) 58 | hub.register <- client 59 | client.run() 60 | } 61 | 62 | // send method. 63 | func (hub *Hub) send(message interface{}, client *Client) { 64 | data, _ := json.Marshal(message) 65 | client.outbound <- data 66 | } 67 | 68 | // broadcast method broadcasts a message to all clients, except one(sender). 69 | func (hub *Hub) broadcast(message interface{}, ignore *Client) { 70 | data, _ := json.Marshal(message) 71 | for _, c := range hub.clients { 72 | if c != ignore { 73 | c.outbound <- data 74 | } 75 | } 76 | } 77 | 78 | // onConnect method of Hub. 79 | func (hub *Hub) onConnect(client *Client) { 80 | log.Println("client connected: ", client.socket.RemoteAddr()) 81 | 82 | // Make list of all users 83 | users := []message.User{} 84 | for _, c := range hub.clients { 85 | users = append(users, message.User{ID: c.id, Color: c.color}) 86 | } 87 | 88 | // Send exists clients to client 89 | hub.send(message.NewConnected(client.color, users), client) 90 | // Notify user joined 91 | hub.broadcast(message.NewUserJoined(client.id, client.color), client) 92 | } 93 | 94 | // onDisconnect method of Hub. 95 | func (hub *Hub) onDisconnect(client *Client) { 96 | log.Println("client disconnected: ", client.socket.RemoteAddr()) 97 | client.close() 98 | 99 | // Find index of client 100 | i := -1 101 | for j, c := range hub.clients { 102 | if c.id == client.id { 103 | i = j 104 | break 105 | } 106 | } 107 | 108 | // Delete client from list 109 | copy(hub.clients[i:], hub.clients[i+1:]) 110 | hub.clients[len(hub.clients)-1] = nil 111 | hub.clients = hub.clients[:len(hub.clients)-1] 112 | 113 | // Notify user left 114 | hub.broadcast(message.NewUserLeft(client.id), nil) 115 | } 116 | 117 | // onMessage method of Hub. 118 | func (hub *Hub) onMessage(data []byte, client *Client) { 119 | kind := gjson.GetBytes(data, "kind").Int() 120 | if kind == message.KindStroke { 121 | var msg message.Stroke 122 | if json.Unmarshal(data, &msg) != nil { 123 | return 124 | } 125 | msg.UserID = client.id 126 | hub.broadcast(msg, client) 127 | } else if kind == message.KindClear { 128 | var msg message.Clear 129 | if json.Unmarshal(data, &msg) != nil { 130 | return 131 | } 132 | msg.UserID = client.id 133 | hub.broadcast(msg, client) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Drawing 8 | 13 | 14 | 15 | 18 | 19 |
20 | 21 |
22 | 157 | 158 | --------------------------------------------------------------------------------