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 }