github.com/aldelo/common@v1.5.1/tcp/tcpserver.go (about) 1 package tcp 2 3 /* 4 * Copyright 2020-2023 Aldelo, LP 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 import ( 20 "bytes" 21 "fmt" 22 util "github.com/aldelo/common" 23 "log" 24 "net" 25 "strings" 26 "sync" 27 "time" 28 ) 29 30 // TCPServer defines a concurrent tcp server for handling inbound client requests, and sending back responses 31 // 32 // Port = this tcp server port number to listen on 33 // ListenerErrorHandler = func to trigger when listener caused error event, this also signifies the end of tcp server listener serving mode 34 // ClientReceiveHandler = func to trigger when this tcp server receives data from client, the writeToClientFunc is provided for sending data back to the connected client 35 // ClientErrorHandler = func to trigger when this tcp server detected tcp client error during data receive event 36 // ReadBufferSize = default 1024, cannot be greater than 65535, defines the byte length of read buffer 37 // ListenerYieldDuration = default 0, no yield, valid range is 0 - 250ms, the amount of time to yield to cpu during listener accept loop cycle 38 // ReaderYieldDuration = default 25ms, the amount of time to yield to cpu process during each cycle of Read loop 39 // ReadDeadLineDuration = default 1000ms, the amount of time to wait for read action before timeout, valid range is 250ms - 5000ms 40 // WriteDeadLineDuration = the amount of time to wait for write action before timeout, if 0, then no timeout 41 type TCPServer struct { 42 Port uint 43 44 ListenerAcceptHandler func(clientIP string) 45 ListenerErrorHandler func(err error) 46 47 ClientReceiveHandler func(clientIP string, data []byte, writeToClientFunc func(writeData []byte, clientIP string) error) 48 ClientErrorHandler func(clientIP string, err error) 49 50 ReadBufferSize uint 51 ListenerYieldDuration time.Duration 52 ReaderYieldDuration time.Duration 53 54 ReadDeadlineDuration time.Duration 55 WriteDeadLineDuration time.Duration 56 57 _tcpListener net.Listener 58 _serving bool 59 _clients map[string]net.Conn 60 _clientEnd map[string]chan bool 61 _mux sync.Mutex 62 } 63 64 // Serve will startup TCP Server and begin accepting TCP Client connections, and initiates connection processing 65 func (s *TCPServer) Serve() (err error) { 66 if s._serving { 67 return fmt.Errorf("TCP Server Already Serving, Use Close() To Stop") 68 } 69 70 if s.Port == 0 || s.Port > 65535 { 71 return fmt.Errorf("TCP Server Listening Port Must Be 1 - 65535") 72 } 73 74 if s._tcpListener, err = net.Listen("tcp4", fmt.Sprintf(":%d", s.Port)); err != nil { 75 return err 76 } else { 77 s._mux.Lock() 78 79 s._serving = true 80 s._clients = make(map[string]net.Conn) 81 s._clientEnd = make(map[string]chan bool) 82 83 s._mux.Unlock() 84 85 // start continuous loop to accept incoming tcp client, 86 // and initiate client handler go-routine for each connection 87 go func() { 88 for { 89 // perform listener action on connected tcp client 90 if c, e := s._tcpListener.Accept(); e != nil { 91 // error encountered 92 if s.ListenerErrorHandler != nil { 93 if strings.Contains(strings.ToLower(e.Error()), "closed") { 94 s.ListenerErrorHandler(fmt.Errorf("TCP Server Closed")) 95 } else { 96 s.ListenerErrorHandler(e) 97 } 98 } 99 100 // clean up clients 101 s._mux.Lock() 102 103 for _, v := range s._clients { 104 if v != nil { 105 _ = v.Close() 106 } 107 } 108 109 // stop serving 110 s._clients = nil 111 s._serving = false 112 113 s._mux.Unlock() 114 115 return 116 } else { 117 // tcp client connected accepted 118 s._mux.Lock() 119 120 // register client connection to map 121 s._clients[c.RemoteAddr().String()] = c 122 123 s._mux.Unlock() 124 125 // handle client connection 126 go s.handleClientConnection(c) 127 128 // notify accept event 129 if s.ListenerAcceptHandler != nil { 130 s.ListenerAcceptHandler(c.RemoteAddr().String()) 131 } 132 } 133 134 if s.ListenerYieldDuration > 0 && s.ListenerYieldDuration <= 250*time.Millisecond { 135 time.Sleep(s.ListenerYieldDuration) 136 } 137 } 138 }() 139 140 log.Println("TCP Server Addr: ", util.GetLocalIP()+":"+util.UintToStr(s.Port)) 141 return nil 142 } 143 } 144 145 // Close will shut down TCP Server, and clean up resources allocated 146 func (s *TCPServer) Close() { 147 if s._tcpListener != nil { 148 _ = s._tcpListener.Close() 149 } 150 151 // clean up clients 152 s._mux.Lock() 153 154 for _, v := range s._clients { 155 if v != nil { 156 _ = v.Close() 157 } 158 } 159 160 // stop serving 161 s._clients = nil 162 s._serving = false 163 164 s._mux.Unlock() 165 } 166 167 // GetConnectedClients returns list of connected tcp clients 168 func (s *TCPServer) GetConnectedClients() (clientsList []string) { 169 s._mux.Lock() 170 defer s._mux.Unlock() 171 172 if s._clients == nil { 173 return []string{} 174 } 175 176 if len(s._clients) == 0 { 177 return []string{} 178 } 179 180 for k, _ := range s._clients { 181 clientsList = append(clientsList, k) 182 } 183 184 return clientsList 185 } 186 187 // DisconnectClient will close client connection and end the client reader service 188 func (s *TCPServer) DisconnectClient(clientIP string) { 189 if s._clientEnd != nil { 190 s._clientEnd[clientIP] = make(chan bool) 191 s._clientEnd[clientIP] <- true 192 } 193 } 194 195 // WriteToClient accepts byte slice and clientIP target, for writing data to the target client 196 func (s *TCPServer) WriteToClient(writeData []byte, clientIP string) error { 197 if writeData == nil { 198 return nil 199 } 200 201 if len(writeData) == 0 { 202 return nil 203 } 204 205 if util.LenTrim(clientIP) == 0 { 206 return nil 207 } 208 209 s._mux.Lock() 210 defer s._mux.Unlock() 211 212 if s._clients == nil { 213 return fmt.Errorf("Write To Client Failed: No TCP Client Connections Exist") 214 } 215 216 if len(s._clients) == 0 { 217 return fmt.Errorf("Write To Client Failed: TCP Client Connections Count is Zero") 218 } 219 220 // obtain client connection 221 if c, ok := s._clients[clientIP]; !ok { 222 return fmt.Errorf("Write To Client IP %s Failed: Client Not Found in TCP Client Connections", clientIP) 223 } else { 224 // write data to tcp client connection 225 if s.WriteDeadLineDuration > 0 { 226 _ = c.SetWriteDeadline(time.Now().Add(s.WriteDeadLineDuration)) 227 defer c.SetWriteDeadline(time.Time{}) 228 } 229 230 if _, e := c.Write(writeData); e != nil { 231 return fmt.Errorf("Write To Client IP %s Failed: %s", clientIP, e.Error()) 232 } else { 233 // write successful 234 return nil 235 } 236 } 237 } 238 239 // handleClientConnection is an internal method invoked by Serve, 240 // handleClientConnection stores tcp client connection into map, and begins the read loop to continuously acquire client data upon arrival 241 func (s *TCPServer) handleClientConnection(conn net.Conn) { 242 // clean up upon exit method 243 defer conn.Close() 244 245 readBufferSize := uint(1024) 246 if s.ReadBufferSize > 0 && s.ReadBufferSize < 65535 { 247 readBufferSize = s.ReadBufferSize 248 } 249 250 readYield := 25 * time.Millisecond 251 if s.ReaderYieldDuration > 0 && s.ReaderYieldDuration < 1000*time.Millisecond { 252 readYield = s.ReaderYieldDuration 253 } 254 255 readDeadline := 1000 * time.Millisecond 256 if s.ReadDeadlineDuration > 250*time.Millisecond && s.ReadDeadlineDuration <= 5000*time.Millisecond { 257 readDeadline = s.ReadDeadlineDuration 258 } 259 260 if s._clientEnd == nil { 261 s._clientEnd = make(map[string]chan bool) 262 } 263 264 for { 265 select { 266 case <-s._clientEnd[conn.RemoteAddr().String()]: 267 // command received to end tcp client connection handler 268 _ = conn.Close() 269 270 s._mux.Lock() 271 delete(s._clients, conn.RemoteAddr().String()) 272 s._mux.Unlock() 273 274 return 275 276 default: 277 // read data from client continuously 278 readBytes := make([]byte, readBufferSize) 279 280 _ = conn.SetReadDeadline(time.Now().Add(readDeadline)) 281 282 if _, e := conn.Read(readBytes); e != nil { 283 _ = conn.SetReadDeadline(time.Time{}) 284 285 errInfo := strings.ToLower(e.Error()) 286 timeout := strings.Contains(errInfo, "timeout") 287 eof := strings.Contains(errInfo, "eof") 288 289 if timeout { 290 // continue reader service 291 time.Sleep(readYield) 292 continue 293 } 294 295 if s.ClientErrorHandler != nil { 296 if !eof { 297 s.ClientErrorHandler(conn.RemoteAddr().String(), e) 298 } else { 299 s.ClientErrorHandler(conn.RemoteAddr().String(), fmt.Errorf("TCP Client Socket Closed By Remote Host")) 300 } 301 } 302 303 // remove connection from map 304 _ = conn.Close() 305 306 s._mux.Lock() 307 delete(s._clients, conn.RemoteAddr().String()) 308 s._mux.Unlock() 309 310 // end client handler 311 return 312 313 } else { 314 _ = conn.SetReadDeadline(time.Time{}) 315 316 // read ok 317 if s.ClientReceiveHandler != nil { 318 // send client data received to client handler for processing 319 s.ClientReceiveHandler(conn.RemoteAddr().String(), bytes.Trim(readBytes, "\x00"), s.WriteToClient) 320 } else { 321 // if client receive handler is not defined, end the client handler service 322 _ = conn.Close() 323 324 s._mux.Lock() 325 delete(s._clients, conn.RemoteAddr().String()) 326 s._mux.Unlock() 327 328 return 329 } 330 } 331 } 332 333 time.Sleep(readYield) 334 } 335 }