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  }