github.com/artisanhe/tools@v1.0.1-0.20210607022958-19a8fef2eb04/websocket-wrapper/ws_client.go (about) 1 package ww 2 3 import ( 4 "errors" 5 "fmt" 6 "math/rand" 7 "net/url" 8 "sync" 9 "time" 10 11 "github.com/gorilla/websocket" 12 ) 13 14 type WSClient struct { 15 // remote server's listen addr, like "localhost:8080" 16 remoteAddr string 17 // remote server's path, like "/echo" 18 remotePath string 19 // reconnect time 20 reconnectTime time.Duration 21 // on message func 22 onMessage func([]byte, error) 23 // on close func 24 onClose func() 25 // client send message chan 26 c chan *WSMessage 27 // conn 28 conn *websocket.Conn 29 // force quit 30 isForceQuit bool 31 // lock for concurrent safety 32 rwLock sync.RWMutex 33 // reconnectMsg,when reconnect or connect send it 34 reconnectMsg *WSMessage 35 } 36 37 func (ws *WSClient) connectRemote() error { 38 u := url.URL{Scheme: "ws", Host: ws.remoteAddr, Path: ws.remotePath} 39 c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) 40 if err != nil { 41 Error("dial err:%s,addr:%s", err.Error(), u.String()) 42 return err 43 } 44 ws.rwLock.Lock() 45 defer ws.rwLock.Unlock() 46 ws.conn = c 47 return nil 48 } 49 50 func (ws *WSClient) disconnectRemote() error { 51 ws.rwLock.Lock() 52 defer ws.rwLock.Unlock() 53 //TODO send close message first 54 if ws.c != nil { 55 close(ws.c) 56 ws.c = nil 57 } 58 if ws.conn != nil { 59 ws.conn.Close() 60 ws.conn = nil 61 } 62 return nil 63 } 64 65 func (ws *WSClient) Shutdown() error { 66 closeMsg := WSMessage{ 67 Type: WSMessageTypeClose, 68 } 69 ws.SendMessge(&closeMsg) 70 // need set forceQuit true 71 ws.isForceQuit = true 72 time.Sleep(time.Second) 73 return nil 74 } 75 76 func (ws *WSClient) Startup(remoteAddr, remotePath string, reconnectTime time.Duration, 77 onMessage func([]byte, error), onClose func(), reconnectMsg *WSMessage) error { 78 // check the input parameters 79 if len(remoteAddr) == 0 || len(remotePath) == 0 || nil == onMessage || nil == onClose { 80 errStr := fmt.Sprintf("invalid input paras,remoteAddr[%s],remotePath[%s],onMessage[%p],onClose[%p]", 81 remoteAddr, remotePath, onMessage, onClose) 82 Error(errStr) 83 return errors.New(errStr) 84 } 85 ws.remoteAddr = remoteAddr 86 ws.remotePath = remotePath 87 ws.reconnectTime = reconnectTime 88 ws.onMessage = onMessage 89 ws.onClose = onClose 90 { 91 ws.rwLock.Lock() 92 ws.reconnectMsg = reconnectMsg 93 ws.rwLock.Unlock() 94 } 95 // if the first connect is failed,caller need handle the error 96 if err := ws.connectRemote(); err != nil { 97 Error("WSClient,connect remote server error:%s", err.Error()) 98 return err 99 } 100 // the below anonymous go routine for handle client's recv,send,reconnect and quit 101 go ws.run() 102 // sleep 10 ms ,make sure the client's inner chan has already created 103 time.Sleep(10 * time.Microsecond) 104 105 return nil 106 } 107 108 func (ws *WSClient) SendMessge(msg *WSMessage) (err error) { 109 if msg == nil { 110 return nil 111 } 112 ws.rwLock.RLock() 113 defer ws.rwLock.RUnlock() 114 if ws.c == nil { 115 Error("WSClient SendMessge chan is nil") 116 return errors.New("SendMessge,the chan is nil") 117 } 118 ws.c <- msg 119 defer func() { 120 if e := recover(); e != nil { 121 Error("SendMessge recover error:%+v", e) 122 err = errors.New("SendMessge erros,write to chan fail") 123 } 124 }() 125 return nil 126 } 127 128 func (ws *WSClient) run() { 129 for { 130 { 131 ws.rwLock.Lock() 132 ws.c = make(chan *WSMessage, 10) 133 ws.rwLock.Unlock() 134 } 135 // the below anonymous go routine for handle the the client's recv 136 reconnectChan := make(chan struct{}) 137 go func() { 138 for { 139 mt, message, err := ws.conn.ReadMessage() 140 if err != nil { 141 Error("WSClient,ReadMessage error:%s", err.Error()) 142 ws.onMessage(nil, err) 143 close(reconnectChan) 144 break 145 } 146 switch mt { 147 case websocket.TextMessage: 148 ws.onMessage(message, nil) 149 case websocket.CloseMessage: 150 ws.onClose() 151 close(reconnectChan) 152 break 153 default: 154 errMsg := fmt.Sprintf("websocket client unsupport message type:%d", mt) 155 panic(errors.New(errMsg)) 156 } 157 } 158 }() 159 { 160 ws.rwLock.RLock() 161 // if the ws.reconnectMsg is not nil,send it 162 if nil != ws.reconnectMsg { 163 // for thread safe,send the copy 164 copyMsg := *ws.reconnectMsg 165 ws.SendMessge(©Msg) 166 } 167 ws.rwLock.RUnlock() 168 } 169 // the below loop for handle the client's send 170 WriteDone: 171 for { 172 keepAliveTime := 30 * time.Second 173 keepAliveTimer := time.NewTimer(keepAliveTime) 174 select { 175 case msg, ok := <-ws.c: 176 if !ok { 177 break WriteDone 178 } 179 switch { 180 case msg.Type == WSMessageTypeBusiness: 181 err := ws.conn.WriteMessage(websocket.TextMessage, msg.Data) 182 if err != nil { 183 remoteAddr := ws.conn.RemoteAddr().String() 184 Error("WSClient,WriteMessage error:%s,remote addr:%s,msg type:%d", 185 err.Error(), remoteAddr, msg.Type) 186 break WriteDone 187 } 188 keepAliveTimer.Reset(keepAliveTime) 189 case msg.Type == WSMessageTypeClose: 190 err := ws.conn.WriteMessage(websocket.CloseMessage, 191 websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) 192 if err != nil { 193 remoteAddr := ws.conn.RemoteAddr().String() 194 Error("WSClient,WriteMessage error:%s,remote addr:%s,msg type:%d", 195 err.Error(), remoteAddr, msg.Type) 196 } 197 ws.onClose() 198 goto QUIT 199 } 200 case <-keepAliveTimer.C: 201 err := ws.conn.WriteMessage(websocket.BinaryMessage, nil) 202 if err != nil { 203 remoteAddr := ws.conn.RemoteAddr().String() 204 Error("WSClient,keepAliveTimer WriteMessage error:%s,remote addr:%s", 205 err.Error(), remoteAddr) 206 break WriteDone 207 } 208 209 case <-reconnectChan: 210 break WriteDone 211 } 212 } 213 Reconnect: 214 Warning("process reconnecting ...") 215 { 216 ws.rwLock.RLock() 217 isForceQuit := ws.isForceQuit 218 ws.rwLock.RUnlock() 219 if isForceQuit { 220 Warning("WSClient,force quit") 221 goto QUIT 222 } 223 } 224 r := rand.New(rand.NewSource(time.Now().UnixNano())) 225 extraMs := r.Uint32() % 3000 //random add extra reconnect time (0~3s) 226 delayTime := ws.reconnectTime + time.Duration(extraMs)*time.Millisecond 227 time.Sleep(delayTime) 228 ws.disconnectRemote() 229 if err := ws.connectRemote(); err != nil { 230 Error("WSClient reconnect error:%s", err.Error()) 231 goto Reconnect 232 } 233 Info("reconnect success") 234 } 235 QUIT: 236 // goto this label,means do not reconnect remote server again. 237 Info("WSClient,quit") 238 } 239 240 // RefreshReconnectMsg can refresh the client's reconnect message 241 func (ws *WSClient) RefreshReconnectMsg(reconnectMsg *WSMessage) { 242 ws.rwLock.Lock() 243 ws.reconnectMsg = reconnectMsg 244 ws.rwLock.Unlock() 245 }