tcpserial.cpp
Go to the documentation of this file.
1 //======================================================================
28 //======================================================================
29 
30 #include "sdhlibrary_settings.h"
31 
32 //----------------------------------------------------------------------
33 // System Includes - include with <>
34 //----------------------------------------------------------------------
35 
36 #include <errno.h>
37 #include <string.h>
38 #include <sys/types.h> // for setsockopt
39 #if SDH_USE_VCC
40 # include <winsock.h>
41 # pragma comment (lib, "Ws2_32.lib")
42 # include <windows.h>
43 # include <strsafe.h>
44 #else
45 # include <sys/socket.h> // for setsockopt, inet_aton
46 # include <netdb.h> // for gethostbyname
47 # include <arpa/inet.h> // for htons, inet_aton
48 # include <netinet/in.h> // for inet_aton
49 # include <netinet/tcp.h> // for TCP_NODELAY
50 # include <unistd.h>
51 #endif
52 #include <fcntl.h> // for fcntl
53 
54 #include <iostream>
55 #include <exception>
56 #include <string>
57 //#include <stdarg.h>
58 #include <assert.h>
59 
60 //----------------------------------------------------------------------
61 // Project Includes - include with ""
62 //----------------------------------------------------------------------
63 
64 #include "tcpserial.h"
65 #include "simpletime.h"
66 //#include "util.h"
67 
68 //----------------------------------------------------------------------
69 // Defines, enums, unions, structs,
70 //----------------------------------------------------------------------
71 
73 
83 #define SDH_TCP_DEBUG 1
84 
85 
86 #if SDH_TCP_DEBUG
87 
91 # define DBG( ... ) \
92  do { \
93  __VA_ARGS__; \
94  } while (0)
95 #else
96 # define DBG( ... )
97 #endif
98 
99 //----------------------------------------------------------------------
100 // Global variables
101 //----------------------------------------------------------------------
102 
103 //----------------------------------------------------------------------
104 // Function and class member implementation (function definitions)
105 //----------------------------------------------------------------------
106 
107 using namespace std;
108 
109 double const cTCPSerial::TIMEOUT_WAIT_FOR_EVER_S = -1.0;
110 double const cTCPSerial::TIMEOUT_RETURN_IMMEDITELY_S = 0.0;
113 //----------------------------------------------------------------------
114 
115 
116 cTCPSerial::cTCPSerial( char const* _tcp_adr, int _tcp_port, double _timeout )
117 {
118 #if SDH_USE_VCC
119  static WSADATA wsa;
120  static bool wsa_startup_called;
121 
122  if ( !wsa_startup_called && WSAStartup ( MAKEWORD ( 1, 1 ) , &wsa ) != 0 )
123  {
124  throw new cTCPSerialException( cMsg( "WSAStartup() failed: %s", GetLastErrorMessage() ) );
125  }
126  wsa_startup_called = true;
127 #endif
128 
129  tcp_adr = string( _tcp_adr );
130  tcp_port = _tcp_port;
131  fd = INVALID_SOCKET;
132  SetTimeout( _timeout );
133 }
134 //----------------------------------------------------------------------
135 
136 
137 void cTCPSerial::Open( void )
138 {
139  struct hostent *host;
140  struct sockaddr_in addr;
141  // test whether the given hostname is an IP Address in dotted notation:
142 #if SDH_USE_VCC
143  if ( (addr.sin_addr.s_addr = inet_addr ( tcp_adr.c_str() )) == -1 )
144 #else
145  if ( !inet_aton( tcp_adr.c_str(), &addr.sin_addr ) )
146 #endif
147  {
148  // no, its a hostname, so translate it to an IP address
149  host = gethostbyname( tcp_adr.c_str() );
150  if ( !host )
151  {
152  throw new cTCPSerialException( cMsg( "Invalid hostname \"%s\", gethostbyname() failed: %s", tcp_adr.c_str(), GetLastErrorMessage() ) );
153  }
154  addr.sin_addr = *(struct in_addr*) host->h_addr;
155  }
156  fd = socket( PF_INET, SOCK_STREAM, 0 );
157  if ( fd == INVALID_SOCKET )
158  throw new cTCPSerialException( cMsg( "Could not create TCP socket, socket() failed: %s", GetLastErrorMessage() ) );
159 
160  DBG( dbg << "Opening TCP connection to host: " << inet_ntoa( addr.sin_addr ) << ", port: " << tcp_port << "\n" );
161 
162  addr.sin_port = htons( tcp_port );
163  addr.sin_family = AF_INET;
164 
165  int rc = connect( fd, (struct sockaddr*) &addr, sizeof(addr) );
166  if ( rc == -1 )
167  throw new cTCPSerialException( cMsg( "Could not connect to \"%s:%d\", connect() failed: %s", tcp_adr.c_str(), tcp_port, GetLastErrorMessage() ) );
168 
169  //int zero = 0;
170 #if SDH_USE_VCC
171  int one = 1;
172  rc = setsockopt( fd, SOL_SOCKET, TCP_NODELAY, (const char*) &one, sizeof( one ) );
173 #elif defined( OSNAME_CYGWIN )
174  int one = 1;
175  rc = setsockopt( fd, SOL_SOCKET, TCP_NODELAY, &one, sizeof( one ) );
176 #else
177  // On linux setting the TCP_NODELAY option leads to an error permission denied
178  // So don't use it since it is an optimization only anyway.
179  //rc = setsockopt( fd, SOL_SOCKET, TCP_NODELAY, &one, sizeof( one ) );
180 #endif
181  if ( rc != 0 )
182  throw new cTCPSerialException( cMsg( "Could not set option TCP_NODELAY for connection to \"%s:%d\", setsockopt failed: %s", tcp_adr.c_str(), tcp_port, GetLastErrorMessage() ) );
183 
184  // set the timeout again after opening to set the O_NONBLOCK flag correctly
185  SetTimeout( GetTimeout() );
186 }
187 //----------------------------------------------------------------------
188 
189 
190 bool cTCPSerial::IsOpen( void )
191  throw()
192 {
193  return ( fd != INVALID_SOCKET );
194 }
195 //----------------------------------------------------------------------
196 
197 
198 void cTCPSerial::Close( void )
199 {
200  if ( !IsOpen() )
201  throw new cTCPSerialException( cMsg( "Could not close un-opened TCP socket" ) );
202 
203  DBG( dbg << "Closing TCP connection\n" );
204 
205 #if SDH_USE_VCC
206  closesocket( fd );
207 #else
208  close( fd );
209 #endif
210  fd = INVALID_SOCKET;
211 }
212 //----------------------------------------------------------------------
213 
214 int cTCPSerial::write( char const *ptr, int len )
215 {
216  assert( IsOpen() );
217 
218  if ( len == 0 )
219  len = int( strlen( ptr ) );
220 
221  DBG( dbg << "cTCPSerial::write(): sending " << len << " bytes (hex): " << cHexByteString( ptr, len ) << "\n" );
222 
223  //---------------------
224  int bytes_sent = send( fd, ptr, len, 0 );
225 
226  if ( bytes_sent < 0 && errno == EAGAIN && timeout_us != TIMEOUT_WAIT_FOR_EVER_US ) // TODO: does this work in native windows?
227  // expected timeout occurred
228  return 0;
229  if ( bytes_sent < 0 )
230  throw new cTCPSerialException( cMsg( "Error from send to TCP \"%s:%d\": %s", tcp_adr.c_str(), tcp_port, GetLastErrorMessage() ) );
231  if ( bytes_sent != len )
232  throw new cTCPSerialException( cMsg( "Could only send %d/%d bytes via TCP \"%s:%d\"", bytes_sent, len, tcp_adr.c_str(), tcp_port ) );
233  //---------------------
234 
235  return bytes_sent;
236 }
237 //----------------------------------------------------------------------
238 
239 
240 ssize_t cTCPSerial::Read( void *_data, ssize_t size, long _timeout_us, bool return_on_less_data )
241 {
242  assert( IsOpen() );
243 
244  char* data = (char*) _data;
245 
246  //---------------------
247  // adjust rx timeout if necessary
248  if ( _timeout_us != timeout_us )
249  {
250  SetTimeout( double(_timeout_us) / 1E6 );
251  }
252  //---------------------
253 
254  //---------------------
255  int bytes_received = 0;
256  int bytes_received_inc = 0;
257  do {
258  if ( _timeout_us > 0L )
259  {
260  // a timeout is set, so we have to use select() before recv() since timeouts do not work for recv() on cygwin (see also SetTimeout())
261 
262  //------------------------
263  // Prepare the file descriptor set readfds for select():
264  // - zero out
265  // - set connected socket
266  fd_set readfds;
267  #if SDH_USE_VCC
268  SOCKET max_socket;
269  #else
270  int max_socket;
271  #endif
272  FD_ZERO( &(readfds) );
273  FD_SET( fd, &(readfds) );
274  max_socket = fd+1;
275  //------------------------
276 
277  //------------------------
278  // Call select to see if new data is available on our sockets:
279  int rc;
280  // Watchout:
281  // The manpage states: "...On Linux, select() modifies timeout..."
282  // So create a copy before calling select():
283  struct timeval timeout_timeval_maybe_overwritten = timeout_timeval;
284  rc = select( (int) max_socket, &readfds, NULL, NULL, &timeout_timeval_maybe_overwritten );
285  if( rc < 0 )
286  throw new cTCPSerialException( cMsg( "Error from select() for TCP connection to \"%s:%d\": %s", tcp_adr.c_str(), tcp_port, GetLastErrorMessage() ) );
287  //------------------------
288 
289  //------------------------
290  // Check if new data is available in fd:
291  // If not then we have timeout, so break out .
292  if ( !FD_ISSET( fd, &readfds ) )
293  {
294  DBG( dbg << "cTCPSerial::Read(): read1 " << bytes_received << "/" << size << " bytes (hex): " << cHexByteString( data, bytes_received ) << "\n" );
295  return bytes_received;
296  }
297  //------------------------
298  }
299 
300  //------------------------
301  // do the actual receive:
302  bytes_received_inc = recv( fd, data+bytes_received, size-bytes_received, 0 );
303 
304  // and check for errors:
305  if ( bytes_received_inc < 0 && errno == EAGAIN && timeout_us == TIMEOUT_RETURN_IMMEDITELY_US ) // TODO: does this work in native windows?
306  {
307  // expected timeout occurred
308  DBG( dbg << "cTCPSerial::Read(): read2 " << bytes_received << "/" << size << " bytes (hex): " << cHexByteString( data, bytes_received ) << " (ignored)\n" );
309  return 0; // TODO: check if this is correct. Shouldn't we return bytes_received as "bytes received so far" here?
310  }
311  if ( bytes_received_inc < 0 )
312  throw new cTCPSerialException( cMsg( "Error from recv() for TCP connection to \"%s:%d\": %s", tcp_adr.c_str(), tcp_port, GetLastErrorMessage() ) );
313 
314  bytes_received += bytes_received_inc;
315  } while ( bytes_received < size && !return_on_less_data );
316 
317 
318  if ( bytes_received < size && !return_on_less_data )
319  {
320  DBG( dbg << "cTCPSerial::Read(): read3 ignoring " << bytes_received << "/" << size << " bytes (hex): " << cHexByteString( data, bytes_received ) << "\n" );
321  throw new cTCPSerialException( cMsg( "Could only receive %d/%ld bytes via TCP \"%s:%d\"", bytes_received, size, tcp_adr.c_str(), tcp_port ) );
322  }
323  //---------------------
324 
325  DBG( dbg << "cTCPSerial::Read(): read4 " << bytes_received << "/" << size << " bytes (hex): " << cHexByteString( data, bytes_received ) << "\n" );
326  return bytes_received;
327 }
328 //----------------------------------------------------------------------
329 
330 
331 void cTCPSerial::SetTimeout( double _timeout )
332 {
333  DBG( dbg << "cTCPSerial::SetTimeout(): " << _timeout << "\n" );
334 
335  if ( _timeout < 0.0 )
336  {
337  _timeout = TIMEOUT_WAIT_FOR_EVER_S;
338  timeout_us = TIMEOUT_WAIT_FOR_EVER_US;
339  timeout_timeval.tv_sec = 0;
340  timeout_timeval.tv_usec = 0;
341  }
342  else
343  {
344  timeout_timeval.tv_sec = (tTimevalSec) _timeout;
345  double v3 = (_timeout - ((double)timeout_timeval.tv_sec)) * 1.0E6;
346  timeout_timeval.tv_usec = (tTimevalUSec) ( (_timeout - ((double)timeout_timeval.tv_sec)) * 1.0E6 );
347  timeout_timeval.tv_usec = (tTimevalUSec) (v3);
348  double v = (_timeout*1.0E6);
349  timeout_us = (long)v;
350  }
351  cSerialBase::SetTimeout( _timeout );
352 
353  if ( IsOpen() )
354  {
355 #if SDH_USE_VCC
356  // see http://msdn.microsoft.com/en-us/library/ms738573%28VS.85%29.aspx
357  u_long mode = (_timeout == 0.0);
358  ioctlsocket( fd, FIONBIO, &mode );
359 #else
360  int flags = fcntl( fd, F_GETFL );
361  if ( _timeout == 0.0 )
362  fcntl( fd, F_SETFL, flags | O_NONBLOCK );
363  else
364  fcntl( fd, F_SETFL, flags & ~O_NONBLOCK );
365 #endif
366  /*
367  * remark: the SO_RCVTIMEO dose NOT work on cygwin, see also http://cygwin.ru/ml/cygwin/2003-01/msg00833.html
368  *
369  * so independent of what timeout you set with SO_RCVTIMEO a call to
370  * recv with no data to read will:
371  * - return immediately if O_NONBLOCK has been set (with errno = 11 EAGAIN)
372  * - return not at all if O_NONBLOCK has not been set (no matter what SO_RCVTIMEO has been set)
373  *
374  * => if timeout is > 0 then we have to use select() before recv() in Read()
375  *
376  int rc;
377  rc = setsockopt( fd, SOL_SOCKET, SO_RCVTIMEO, &timeout_timeval, sizeof( timeout_timeval ) );
378  if ( rc != 0 )
379  throw new cTCPSerialException( cMsg( "Could not set option SO_RCVTIMEO for TCP connection to \"%s:%d\", setsockopt failed: %s",
380  tcp_adr.c_str(), tcp_port, GetLastErrorMessage() ) );
381 
382  rc = setsockopt( fd, SOL_SOCKET, SO_SNDTIMEO, &timeout_timeval, sizeof( timeout_timeval ) );
383  if ( rc != 0 )
384  throw new cTCPSerialException( cMsg( "Could not set option SO_SNDTIMEO for TCP connection to \"%s:%d\", setsockopt failed: %s",
385  tcp_adr.c_str(), tcp_port, GetLastErrorMessage() ) );
386  */
387  }
388 }
389 //----------------------------------------------------------------------
390 
391 
392 //======================================================================
393 /*
394  Here are some settings for the emacs/xemacs editor (and can be safely ignored):
395  (e.g. to explicitely set C++ mode for *.h header files)
396 
397  Local Variables:
398  mode:C++
399  mode:ELSE
400  End:
401 */
402 //======================================================================
Interface of auxilliary utility functions for SDHLibrary-CPP.
static long const TIMEOUT_RETURN_IMMEDITELY_US
Definition: tcpserial.h:115
void Open(void)
Definition: tcpserial.cpp:137
#define NULL
Definition: getopt1.c:56
Interface of class #SDH::cTCPSerial, class to access TCP port cygwin/linux.
dummy class for (debug) stream output of bytes as list of hex values
Definition: dbg.h:329
void SetTimeout(double _timeout)
set the timeout for next readline() calls (negative value means: no timeout, wait for ever) ...
Definition: tcpserial.cpp:331
static double const TIMEOUT_WAIT_FOR_EVER_S
Definition: tcpserial.h:112
cTCPSerial(char const *_tcp_adr, int _tcp_port, double _timeout)
Definition: tcpserial.cpp:116
virtual void SetTimeout(double _timeout)
set the timeout for next readline() calls (negative value means: no timeout, wait for ever) ...
Definition: serialbase.h:151
suseconds_t tTimevalUSec
Definition: simpletime.h:78
bool IsOpen(void)
Return true if interface to CAN ESD is open.
Definition: tcpserial.cpp:190
static long const TIMEOUT_WAIT_FOR_EVER_US
Definition: tcpserial.h:114
Derived exception class for low-level CAN ESD related exceptions.
Definition: tcpserial.h:75
int write(char const *ptr, int len=0)
Write data to a previously opened port.
Definition: tcpserial.cpp:214
NAMESPACE_SDH_START typedef time_t tTimevalSec
Definition: simpletime.h:77
#define DBG(...)
Definition: tcpserial.cpp:91
#define USING_NAMESPACE_SDH
UInt16 size
Definition: dsa.h:269
void Close(void)
Close the previously opened CAN ESD interface port.
Definition: tcpserial.cpp:198
ssize_t Read(void *data, ssize_t size, long timeout_us, bool return_on_less_data)
Definition: tcpserial.cpp:240
This file contains settings to make the SDHLibrary compile on differen systems:
static double const TIMEOUT_RETURN_IMMEDITELY_S
Definition: tcpserial.h:113
Class for short, fixed maximum length text messages.
Definition: sdhexception.h:77


sdhlibrary_cpp
Author(s): Dirk Osswald
autogenerated on Mon Feb 28 2022 23:41:51