github.com/GuanceCloud/cliutils@v1.1.21/network/ws/ws.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the MIT License.
     3  // This product includes software developed at Guance Cloud (https://www.guance.com/).
     4  // Copyright 2021-present Guance, Inc.
     5  
     6  // Package ws wraps websocket implements among UNIX-like(Linux & macOS) platform
     7  package ws
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  	"net"
    13  	"net/http"
    14  	"sync"
    15  	"syscall"
    16  	"time"
    17  
    18  	"github.com/GuanceCloud/cliutils"
    19  	"github.com/GuanceCloud/cliutils/logger"
    20  	"github.com/gobwas/ws"
    21  	"github.com/gobwas/ws/wsutil"
    22  )
    23  
    24  var (
    25  	l             = logger.DefaultSLogger("ws")
    26  	CommonChanCap = 128
    27  )
    28  
    29  type Server struct {
    30  	Path string
    31  	Bind string
    32  
    33  	MsgHandler func(*Server, net.Conn, []byte, ws.OpCode) error // server msg handler
    34  	AddCli     func(w http.ResponseWriter, r *http.Request)
    35  
    36  	uptime time.Time
    37  
    38  	exit *cliutils.Sem
    39  	wg   *sync.WaitGroup
    40  
    41  	epoller *epoll
    42  }
    43  
    44  func NewServer(bind, path string) (s *Server, err error) {
    45  	s = &Server{
    46  		Path: path,
    47  		Bind: bind,
    48  
    49  		uptime: time.Now(),
    50  
    51  		exit: cliutils.NewSem(),
    52  		wg:   &sync.WaitGroup{},
    53  	}
    54  
    55  	s.epoller, err = MkEpoll()
    56  	if err != nil {
    57  		l.Error("MkEpoll() error: %s", err.Error())
    58  		return
    59  	}
    60  
    61  	return
    62  }
    63  
    64  func (s *Server) AddConnection(conn net.Conn) error {
    65  	if err := s.epoller.Add(conn); err != nil {
    66  		l.Errorf("epoll.Add() error: %s", err.Error())
    67  
    68  		if err := conn.Close(); err != nil {
    69  			l.Warnf("Close: %s, ignored", err)
    70  		}
    71  		return err
    72  	}
    73  
    74  	return nil
    75  }
    76  
    77  func SendMsgToClient(msg []byte, conn net.Conn) error {
    78  	return wsutil.WriteServerText(conn, msg)
    79  }
    80  
    81  func (s *Server) Stop() {
    82  	s.exit.Close()
    83  
    84  	l.Debug("wait...")
    85  	s.wg.Wait()
    86  
    87  	l.Debug("wait done")
    88  }
    89  
    90  func (s *Server) Start() {
    91  	l = logger.SLogger("ws")
    92  
    93  	// remove resources limitations
    94  	var rLimit syscall.Rlimit
    95  	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
    96  		panic(err)
    97  	}
    98  
    99  	l.Debugf("rLimit cur: %d, max: %d", rLimit.Cur, rLimit.Max)
   100  
   101  	rLimit.Cur = rLimit.Max
   102  	if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
   103  		fmt.Printf("warn: Setrlimit %+#v failed: %s\n", rLimit, err.Error())
   104  	}
   105  
   106  	s.wg.Add(1)
   107  	go func() {
   108  		defer s.wg.Done()
   109  		s.startEpoll()
   110  	}()
   111  
   112  	srv := &http.Server{
   113  		Addr: s.Bind,
   114  	}
   115  
   116  	if s.AddCli == nil {
   117  		l.Fatal("AddCli not set")
   118  	}
   119  
   120  	http.HandleFunc(s.Path, s.AddCli)
   121  
   122  	s.wg.Add(1)
   123  	go func() {
   124  		defer s.wg.Done()
   125  		if err := srv.ListenAndServe(); err != nil {
   126  			l.Info(err)
   127  		}
   128  	}()
   129  
   130  	<-s.exit.Wait()
   131  	if err := srv.Shutdown(context.TODO()); err != nil {
   132  		l.Errorf("srv.Shutdown: %s", err.Error())
   133  	}
   134  
   135  	l.Info("websocket server stopped.")
   136  }
   137  
   138  func (s *Server) startEpoll() {
   139  	for {
   140  		select {
   141  		case <-s.exit.Wait():
   142  			l.Debug("epoll exit.")
   143  			if err := s.epoller.Close(); err != nil {
   144  				l.Warnf("Close: %s, ignored", err)
   145  			}
   146  			return
   147  
   148  		default:
   149  
   150  			connections, err := s.epoller.Wait(100)
   151  			if err != nil {
   152  				// You should check the epoll_wait return value,
   153  				// then if it's -1 compare errno to EINTR and,
   154  				// if so, retry the system call.
   155  				// This is usually done with continue in a loop.
   156  				continue
   157  			}
   158  
   159  			for _, conn := range connections {
   160  				if conn == nil {
   161  					break
   162  				}
   163  
   164  				if data, opcode, err := wsutil.ReadClientData(conn); err != nil {
   165  					l.Debugf("ReadClientData: %s", err.Error())
   166  
   167  					if err := s.epoller.Remove(conn); err != nil {
   168  						l.Errorf("Failed to remove %v", err)
   169  					}
   170  
   171  					l.Debugf("close cli %s", conn.RemoteAddr().String())
   172  					if err := conn.Close(); err != nil {
   173  						l.Warnf("Close: %s, ignored", err)
   174  					}
   175  				} else if s.MsgHandler != nil {
   176  					if err := s.MsgHandler(s, conn, data, opcode); err != nil {
   177  						l.Error("s.handler() error: %s", err.Error())
   178  					}
   179  				}
   180  			}
   181  		}
   182  	}
   183  }