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 }