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(&copyMsg)
   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  }