github.com/aldelo/common@v1.5.1/tcp/tcpclient.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  	"net"
    24  	"strings"
    25  	"time"
    26  )
    27  
    28  // TCPClient defines tcp client connection struct
    29  //
    30  // ServerIP = tcp server ip address
    31  // ServerPort = tcp server port
    32  // ReceiveHandler = func handler to be triggered when data is received from tcp server
    33  // ErrorHandler = func handler to be triggered when error is received from tcp server (while performing reader service)
    34  // ReadBufferSize = default: 1024, defines the read buffer byte size, if > 65535, defaults to 1024
    35  // ReaderYieldDuration = default: 25ms, defines the amount of time yielded during each reader service loop cycle, if > 1000ms, defaults to 25ms
    36  // ReadDeadLineDuration = default: 1000ms, defines the amount of time given to read action before timeout, valid range is 250ms - 5000ms
    37  // WriteDeadLineDuration = default: 0, duration value used to control write timeouts, this value is added to current time during write timeout set action
    38  type TCPClient struct {
    39  	// tcp server targets
    40  	ServerIP   string
    41  	ServerPort uint
    42  
    43  	ReceiveHandler func(data []byte)
    44  	ErrorHandler   func(err error, socketCloseFunc func())
    45  
    46  	ReadBufferSize      uint
    47  	ReaderYieldDuration time.Duration
    48  
    49  	ReadDeadLineDuration  time.Duration
    50  	WriteDeadLineDuration time.Duration
    51  
    52  	// tcp connection
    53  	_tcpConn       net.Conn
    54  	_readerEnd     chan bool
    55  	_readerStarted bool
    56  }
    57  
    58  // resolveTcpAddr takes tcp server ip and port defined within TCPClient struct,
    59  // and resolve to net.TCPAddr target
    60  func (c *TCPClient) resolveTcpAddr() (*net.TCPAddr, error) {
    61  	if util.LenTrim(c.ServerIP) == 0 {
    62  		return nil, fmt.Errorf("TCP Server IP is Required")
    63  	}
    64  
    65  	if c.ServerPort == 0 || c.ServerPort > 65535 {
    66  		return nil, fmt.Errorf("TCP Server Port Must Be 1 - 65535")
    67  	}
    68  
    69  	return net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", c.ServerIP, c.ServerPort))
    70  }
    71  
    72  // Dial will dial tcp client to the remote tcp server host ip and port defined within the TCPClient struct,
    73  // if Dial failed, an error is returned,
    74  // if Dial succeeded, nil is returned
    75  func (c *TCPClient) Dial() error {
    76  	if c.ReceiveHandler == nil {
    77  		return fmt.Errorf("TCP Client ReceiveHandler Must Be Defined")
    78  	}
    79  
    80  	if c.ErrorHandler == nil {
    81  		return fmt.Errorf("TCP Client ErrorHandler Must Be Defined")
    82  	}
    83  
    84  	if tcpAddr, err := c.resolveTcpAddr(); err != nil {
    85  		return err
    86  	} else {
    87  		if c._tcpConn, err = net.DialTCP("tcp", nil, tcpAddr); err != nil {
    88  			return err
    89  		} else {
    90  			return nil
    91  		}
    92  	}
    93  }
    94  
    95  // Close will close the tcp connection for the current TCPClient struct object
    96  func (c *TCPClient) Close() {
    97  	if c._tcpConn != nil {
    98  		_ = c._tcpConn.Close()
    99  		c._tcpConn = nil
   100  	}
   101  }
   102  
   103  // Write will write data into tcp connection stream, and deliver to the TCP Server host currently connected
   104  func (c *TCPClient) Write(data []byte) error {
   105  	if c._tcpConn == nil {
   106  		return fmt.Errorf("TCP Server Not Yet Connected")
   107  	}
   108  
   109  	if len(data) == 0 {
   110  		return fmt.Errorf("Data Required When Write to TCP Server")
   111  	}
   112  
   113  	if c.WriteDeadLineDuration > 0 {
   114  		_ = c._tcpConn.SetWriteDeadline(time.Now().Add(c.WriteDeadLineDuration))
   115  		defer c._tcpConn.SetWriteDeadline(time.Time{})
   116  	}
   117  
   118  	if _, err := c._tcpConn.Write(data); err != nil {
   119  		return fmt.Errorf("Write Data to TCP Server Failed: %s", err.Error())
   120  	} else {
   121  		return nil
   122  	}
   123  }
   124  
   125  // Read will read data into tcp client from TCP Server host,
   126  // Read will block until read deadlined timeout, then loop continues to read again
   127  func (c *TCPClient) Read() (data []byte, timeout bool, err error) {
   128  	if c._tcpConn == nil {
   129  		return nil, false, fmt.Errorf("TCP Server Not Yet Connected")
   130  	}
   131  
   132  	readDeadLine := 1000 * time.Millisecond
   133  	if c.ReadDeadLineDuration >= 250*time.Millisecond && c.ReadDeadLineDuration <= 5000*time.Millisecond {
   134  		readDeadLine = c.ReadDeadLineDuration
   135  	}
   136  	_ = c._tcpConn.SetReadDeadline(time.Now().Add(readDeadLine))
   137  	defer c._tcpConn.SetReadDeadline(time.Time{})
   138  
   139  	if c.ReadBufferSize == 0 || c.ReadBufferSize > 65535 {
   140  		c.ReadBufferSize = 1024
   141  	}
   142  
   143  	data = make([]byte, c.ReadBufferSize)
   144  
   145  	if _, err = c._tcpConn.Read(data); err != nil {
   146  		if strings.Contains(strings.ToLower(err.Error()), "timeout") {
   147  			return nil, true, fmt.Errorf("Read Data From TCP Server Timeout: %s", err)
   148  		} else {
   149  			return nil, false, fmt.Errorf("Read Data From TCP Server Failed: %s", err)
   150  		}
   151  	} else {
   152  		return bytes.Trim(data, "\x00"), false, nil
   153  	}
   154  }
   155  
   156  // StartReader will start the tcp client reader service,
   157  // it will continuously read data from tcp server
   158  func (c *TCPClient) StartReader() error {
   159  	if c._tcpConn == nil {
   160  		return fmt.Errorf("TCP Server Not Yet Connected")
   161  	}
   162  
   163  	if !c._readerStarted {
   164  		c._readerEnd = make(chan bool)
   165  		c._readerStarted = true
   166  	}
   167  
   168  	yield := 25 * time.Millisecond
   169  
   170  	if c.ReaderYieldDuration > 0 && c.ReaderYieldDuration <= 1000*time.Millisecond {
   171  		yield = c.ReaderYieldDuration
   172  	}
   173  
   174  	// start reader loop and continue until Reader End
   175  	go func() {
   176  		for {
   177  			select {
   178  			case <-c._readerEnd:
   179  				// command received to end tcp reader service
   180  				c._readerStarted = false
   181  				return
   182  
   183  			default:
   184  				// perform read action on connected tcp server
   185  				if data, timeout, err := c.Read(); err != nil && !timeout {
   186  					// read fail, not timeout, deliver error data to handler, stop reader
   187  					if c.ErrorHandler != nil {
   188  						// send error to error handler
   189  						if strings.Contains(strings.ToLower(err.Error()), "eof") {
   190  							c.ErrorHandler(fmt.Errorf("TCP Client Socket Closed By Remote Host"), c.Close)
   191  						} else {
   192  							c.ErrorHandler(err, nil)
   193  						}
   194  					}
   195  
   196  					// end reader service
   197  					c._readerStarted = false
   198  					return
   199  				} else if !timeout {
   200  					// read successful, deliver read data to handler
   201  					if c.ReceiveHandler != nil {
   202  						// send received data to receive handler
   203  						c.ReceiveHandler(data)
   204  					} else {
   205  						// if error handler is not defined, end reader service
   206  						if c.ErrorHandler != nil {
   207  							c.ErrorHandler(fmt.Errorf("TCP Client Receive Handler Undefined"), c.Close)
   208  						}
   209  
   210  						c._readerStarted = false
   211  						return
   212  					}
   213  				}
   214  
   215  				// note:
   216  				// if read timed out, the loop will continue
   217  			}
   218  
   219  			time.Sleep(yield)
   220  		}
   221  	}()
   222  
   223  	return nil
   224  }
   225  
   226  // StopReader will end the tcp client reader service
   227  func (c *TCPClient) StopReader() {
   228  	if c._readerStarted {
   229  		c._readerEnd <- true
   230  	}
   231  }