nds2-client - ClientDeveloper  0.16.8
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
socket.hh
Go to the documentation of this file.
1 //
2 // Created by jonathan.hanks on 4/12/17.
3 //
4 // This is somewhat based on the GDS socket code, with many adaptations
5 //
6 
7 #ifndef NDS_SOCKET_HH
8 #define NDS_SOCKET_HH
9 
10 #if _WIN32
11 //#define WIN32_LEAN_AND_MEAN
12 //#include <Windows.h>
13 #include <Winsock2.h>
14 #include <Ws2tcpip.h>
15 #else
16 #include <netinet/in.h>
17 #include <unistd.h>
18 #include <netdb.h>
19 #include <sys/types.h>
20 #include <sys/socket.h>
21 #endif
22 
23 #include <cstring>
24 #include <sstream>
25 #include <memory>
26 #include <vector>
27 
28 namespace nds_impl
29 {
30  namespace Socket
31  {
32  namespace detail
33  {
34 #if _WIN32
35  typedef SOCKET socket_type;
36  typedef int socklen_t;
37  typedef const char* send_ptr_type;
38  typedef char* recv_ptr_type;
39  typedef const char* sockopt_ptr_type;
40 
41  static const socket_type BAD_SOCKET = INVALID_SOCKET;
42 
43  inline void
44  bzero( void* addr, size_t len )
45  {
46  ::memset( addr, 0, len );
47  }
48 
49  inline void
51  {
52  ::closesocket( s );
53  }
54 
55  class wsa_init
56  {
57  public:
58  wsa_init( )
59  {
60  WORD wVersionRequested;
61  WSAData wsaData;
62 
63  wVersionRequested = MAKEWORD( 2, 0 );
64  if ( WSAStartup( wVersionRequested, &wsaData ) != 0 )
65  {
66  throw std::runtime_error(
67  "Unable to initialize winsock library" );
68  }
69  }
70 
71  ~wsa_init( )
72  {
73  WSACleanup( );
74  }
75  };
76 
77  static wsa_init _init_obj;
78 
79 #else
80  typedef int socket_type;
82  typedef const void* send_ptr_type;
83  typedef void* recv_ptr_type;
84  typedef const void* sockopt_ptr_type;
85 
86  static const socket_type BAD_SOCKET = -1;
87 
88  inline void
89  bzero( void* addr, size_t len )
90  {
91  ::bzero( addr, len );
92  }
93 
94  inline void
96  {
97  ::close( s );
98  }
99 #endif
100  }
101 
102  struct Address
103  {
104  decltype(::sockaddr_in::sin_addr.s_addr ) node;
105  unsigned short port;
106  };
107 
109  {
110  public:
112 
113  FullSocket( ) : s_( detail::BAD_SOCKET )
114  {
115  }
116  FullSocket( FullSocket&& other ) : s_{ other.release( ) }
117  {
118  }
119 
120  explicit FullSocket( socket_type s ) : s_( s )
121  {
122  }
123 
125  {
126  if ( s_ != detail::BAD_SOCKET )
127  {
129  }
131  }
132  void
134  {
135  s_ = fd;
136  }
139  {
140  socket_type tmp = s_;
142  return tmp;
143  }
144 
146  get( ) const
147  {
148  return s_;
149  }
150 
151  void
152  swap( FullSocket& other )
153  {
154  socket_type tmp = other.s_;
155  other.s_ = s_;
156  s_ = tmp;
157  }
158 
159  bool
160  good( ) const
161  {
162  return s_ != detail::BAD_SOCKET;
163  }
164 
165  void bind( const std::string& address );
166 
167  unsigned short
168  listen( int queue_depth = 100 )
169  {
170  if ( !good( ) )
171  throw std::runtime_error(
172  "Need to bind a port before listening" );
173  auto rc = ::listen( get( ), queue_depth );
174  if ( rc < 0 )
175  {
176  auto err = errno;
177  if ( err == EADDRINUSE )
178  throw std::runtime_error(
179  "Listen reported address in use" );
180  throw std::runtime_error( "Error on listen" );
181  }
182  ::sockaddr_in sock;
183  detail::socklen_t len = sizeof( sock );
184  if ( getsockname( s_, (struct ::sockaddr*)&sock, &len ) != 0 )
185  {
186  throw std::runtime_error(
187  "Unable to retrieve socket info after listen call" );
188  }
189 
190  return sock.sin_port;
191  }
192  unsigned short
193  listen( const std::string& address, int queue_depth = 100 )
194  {
195  bind( address );
196  return ntohs( listen( queue_depth ) );
197  }
198 
199  FullSocket accept( );
200 
201  void connect( const std::string& target );
202 
203  void write_all( const char* start, const char* end );
204 
205  char* read_available( char* start, char* end );
206 
207  template < typename Cont >
208  static std::vector< FullSocket* >
209  select( Cont& sockets, long secs, long usec )
210  {
211  struct ::timeval tv;
212  tv.tv_sec = secs;
213  tv.tv_usec = usec;
214 
215  int nfds = 0;
216  fd_set rd_set;
217  fd_set wr_set;
218  FD_ZERO( &rd_set );
219  FD_ZERO( &wr_set );
220 
221  for ( auto& s : sockets )
222  {
223  FD_SET( s->get( ), &rd_set );
224  FD_SET( s->get( ), &wr_set );
225  nfds = ( s->get( ) > nfds ? s->get( ) : nfds );
226  }
227  ++nfds;
228  int count = ::select( nfds,
229  &rd_set,
230  &wr_set,
231  nullptr,
232  ( secs >= 0 ? &tv : nullptr ) );
233  std::vector< FullSocket* > results;
234  for ( auto& s : sockets )
235  {
236  if ( FD_ISSET( s->get( ), &rd_set ) ||
237  FD_ISSET( s->get( ), &wr_set ) )
238  {
239  results.push_back( s );
240  }
241  }
242  return results;
243  }
244 
245  private:
247 
248  void set_option( int id, int val );
249 
250  FullSocket( const FullSocket& other );
251  FullSocket& operator=( const FullSocket& other );
252  };
253 
255  {
256  public:
258 
259  SocketHandle( ) : s_( detail::BAD_SOCKET )
260  {
261  }
262 
264  {
265  }
266  SocketHandle( const SocketHandle& other ) = default;
267 
268  void
270  {
271  s_ = fd;
272  }
273 
276  {
277  socket_type tmp = s_;
279  return tmp;
280  }
281 
283  get( ) const
284  {
285  return s_;
286  }
287 
288  void
289  swap( SocketHandle& other )
290  {
291  socket_type tmp = other.s_;
292  other.s_ = s_;
293  s_ = tmp;
294  }
295 
296  bool
297  good( ) const
298  {
299  return s_ != detail::BAD_SOCKET;
300  }
301 
302  void write_all( const char* start, const char* end );
303 
304  char* read_available( char* start, char* end );
305 
306  private:
308  };
309 
310  inline Address
311  parse_address( const std::string& addr )
312  {
313  Address result{ 0, 0 };
314 
315  //-------------------------------- Pick off the port number
316  std::string::size_type inx = addr.find( ":" );
317  std::string host = addr;
318  if ( inx != std::string::npos )
319  {
320  host = addr.substr( 0, inx );
321  std::istringstream is( addr.substr( inx + 1 ) );
322  is >> result.port;
323  }
324 
325  //-------------------------------- Get the address.
326  if ( host.size( ) == 0 )
327  {
328  result.node = INADDR_ANY;
329  }
330  else
331  {
332  struct hostent* hentp = ::gethostbyname( host.c_str( ) );
333  if ( !hentp )
334  {
335  throw std::runtime_error( "Error in gethostbyname" );
336  // perror("parse_addr: error in gethostbyname");
337  // return serr_failure;
338  }
339  std::memcpy(
340  &result.node, *hentp->h_addr_list, sizeof( result.node ) );
341  }
342  return result;
343  }
344 
345  inline void
346  FullSocket::set_option( int id, int val )
347  {
348  int rc = ::setsockopt(
349  s_,
350  SOL_SOCKET,
351  id,
352  reinterpret_cast< detail::sockopt_ptr_type >( &val ),
353  sizeof( int ) );
354  if ( rc < 0 )
355  throw std::runtime_error( "Error setting socket options" );
356  }
357 
358  inline FullSocket
360  {
361  auto client = FullSocket( );
362 
363  ::sockaddr_in sock;
364 
365  detail::bzero( &sock, sizeof( sock ) );
366  sock.sin_family = AF_INET;
367  sock.sin_addr.s_addr = 0;
368  sock.sin_port = 0;
369  detail::socklen_t len = 16;
370 
371  client.reset(::accept( s_, (struct ::sockaddr*)&sock, &len ) );
372  return client;
373  }
374 
375  inline void
376  FullSocket::bind( const std::string& address )
377  {
378  if ( good( ) )
379  throw std::runtime_error(
380  "You cannot bind to a connected socket" );
381 
382  ::sockaddr_in sock;
383  auto addr_info = parse_address( address );
384  sock.sin_addr.s_addr = addr_info.node;
385  sock.sin_port = addr_info.port;
386 
387  if ( ( s_ = ::socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 )
388  {
389  throw std::runtime_error( "Unable to create socket" );
390  }
391  sock.sin_family = AF_INET;
392  detail::socklen_t len = 16;
393 
394  set_option( SO_REUSEADDR, 1 );
395 
396  if (::bind( s_, (struct ::sockaddr*)&sock, len ) < 0 )
397  throw std::runtime_error(
398  "Unable to bind to requested address" );
399  }
400 
401  inline void
402  FullSocket::connect( const std::string& target )
403  {
404  class address_cleanup
405  {
406  public:
407  void
408  operator( )( struct ::addrinfo* p )
409  {
410  if ( p )
411  ::freeaddrinfo( p );
412  }
413  };
414 
415  if ( good( ) )
416  throw std::runtime_error(
417  "Cannot connect a socket that is already initialized" );
418 
419  std::string::size_type sep = target.find( ':' );
420  if ( sep == std::string::npos )
421  {
422  throw std::runtime_error(
423  "Socket connect target has no port specifier" );
424  }
425  std::string hostname = target.substr( 0, sep );
426  std::string port = target.substr( sep + 1 );
427 
428  struct ::addrinfo hints;
429  struct ::addrinfo *result = nullptr, *rp = nullptr;
430  std::memset( &hints, 0, sizeof( hints ) );
431  hints.ai_family = AF_UNSPEC;
432  hints.ai_socktype = SOCK_STREAM;
433  hints.ai_protocol = IPPROTO_TCP;
434 
435  if ( int rc = ::getaddrinfo( hostname.c_str( ),
436  port.c_str( ),
437  &hints,
438  &result ) != 0 )
439  {
440  throw std::runtime_error( "Unable to lookup address "
441  "information during connect "
442  "operation" );
443  }
444  std::unique_ptr< struct ::addrinfo, address_cleanup > result_(
445  result );
446  for ( rp = result; rp != nullptr; rp = rp->ai_next )
447  {
448  FullSocket tmp(::socket(
449  rp->ai_family, rp->ai_socktype, rp->ai_protocol ) );
450  if ( !tmp.good( ) )
451  {
452  continue;
453  }
454  if (::connect( tmp.get( ), rp->ai_addr, rp->ai_addrlen ) == 0 )
455  {
456  swap( tmp );
457  return;
458  }
459  }
460  throw std::runtime_error( "Unable to connecto to target address" );
461  }
462 
463  inline void
464  FullSocket::write_all( const char* start, const char* end )
465  {
466  if ( !good( ) )
467  throw std::runtime_error(
468  "Cannot send data on a socket that is not opened" );
469  auto cur = start;
470  while ( cur < end )
471  {
472  auto count =
473  ::send( s_,
474  static_cast< detail::send_ptr_type >( cur ),
475  end - cur,
476  0 );
477  if ( count <= 0 )
478  {
479  auto err = errno;
480  if ( err == EINTR || err == EAGAIN )
481  continue;
482  throw std::runtime_error( "Unable to send on socket" );
483  }
484  else if ( count == 0 )
485  {
486  throw std::runtime_error( "Connection closed" );
487  }
488  else
489  {
490  cur += count;
491  }
492  }
493  }
494 
495  inline char*
496  FullSocket::read_available( char* start, char* end )
497  {
498  if ( !good( ) )
499  throw std::runtime_error(
500  "Cannot read data on a socket that is not opened" );
501 
502  auto count = ::recv( s_,
503  static_cast< detail::recv_ptr_type >( start ),
504  end - start,
505  0 );
506  if ( count <= 0 )
507  {
508  throw std::runtime_error( "Unable to read data from socket" );
509  }
510  return start + count;
511  }
512 
513  inline void
514  SocketHandle::write_all( const char* start, const char* end )
515  {
516  if ( !good( ) )
517  throw std::runtime_error(
518  "Cannot send data on a socket that is not opened" );
519  auto cur = start;
520  while ( cur < end )
521  {
522  auto count =
523  ::send( s_,
524  static_cast< detail::send_ptr_type >( cur ),
525  end - cur,
526  0 );
527  if ( count <= 0 )
528  {
529  auto err = errno;
530  if ( err == EINTR || err == EAGAIN )
531  continue;
532  throw std::runtime_error( "Unable to send on socket" );
533  }
534  else if ( count == 0 )
535  {
536  throw std::runtime_error( "Connection closed" );
537  }
538  else
539  {
540  cur += count;
541  }
542  }
543  }
544 
545  inline char*
546  SocketHandle::read_available( char* start, char* end )
547  {
548  if ( !good( ) )
549  throw std::runtime_error(
550  "Cannot read data on a socket that is not opened" );
551 
552  auto count = ::recv( s_,
553  static_cast< detail::recv_ptr_type >( start ),
554  end - start,
555  0 );
556  if ( count <= 0 )
557  {
558  throw std::runtime_error( "Unable to read data from socket" );
559  }
560  return start + count;
561  }
562  }
563 }
564 
566 // Tests only after this point.
568 
569 #ifdef _NDS_IMPL_ENABLE_CATCH_TESTS_
570 
571 #include <iostream>
572 #include <thread>
573 #include "catch.hpp"
574 
575 TEST_CASE( "Can create a socket object", "[create_basic]" )
576 {
578  REQUIRE( !s.good( ) );
579 }
580 
581 TEST_CASE( "Can create a socket from a fd number", "[create_from_fd]" )
582 {
584  REQUIRE( s.good( ) );
585  REQUIRE( s.get( ) == 4 );
586  s.release( );
587  REQUIRE( !s.good( ) );
588  REQUIRE( s.get( ) == -1 );
589 }
590 
591 TEST_CASE( "Can parse and lookup a name", "[ext_network]" )
592 {
594  nds_impl::Socket::parse_address( "localhost:10000" );
595  REQUIRE( addr.port == 10000 );
596  REQUIRE( addr.node == htonl( 0x7f000001 ) );
597 
598  addr = nds_impl::Socket::parse_address( "localhost" );
599  REQUIRE( addr.port == 0 );
600  REQUIRE( addr.node == htonl( 0x7f000001 ) );
601 
602  addr = nds_impl::Socket::parse_address( ":5050" );
603  REQUIRE( addr.port == 5050 );
604  REQUIRE( addr.node == 0 );
605 }
606 
607 TEST_CASE( "Test release", "[release]" )
608 {
610 
612  REQUIRE( s.get( ) == nds_impl::Socket::detail::BAD_SOCKET );
613 
614  s.reset( 4 );
615  REQUIRE( s.get( ) == 4 );
616  REQUIRE( s.release( ) == 4 );
617  REQUIRE( s.get( ) == nds_impl::Socket::detail::BAD_SOCKET );
618 }
619 
620 TEST_CASE( "Test get", "[get]" )
621 {
624 
625  REQUIRE( s1.get( ) == 5 );
626  REQUIRE( s2.get( ) == -1 );
627 
628  s2.reset( s1.release( ) );
629  REQUIRE( s1.get( ) == -1 );
630  REQUIRE( s2.get( ) == 5 );
631 
632  s2.release( );
633  REQUIRE( s2.get( ) == -1 );
634 }
635 
636 TEST_CASE( "Test connect with an initialized socket", "[connect]" )
637 {
639  REQUIRE_THROWS( s.connect( "localhost:5000" ) );
640  s.release( );
641 }
642 
643 TEST_CASE( "Test connect with out a port specified", "[connect_no_port]" )
644 {
646  REQUIRE_THROWS( s.connect( "localhost" ) );
647 }
648 
649 TEST_CASE( "Test connect with out a host specified", "[connect_no_host]" )
650 {
652  REQUIRE_THROWS( s.connect( ":5000" ) );
653 }
654 
655 TEST_CASE( "Test connect with no address", "[connect_no_address]" )
656 {
658  REQUIRE_THROWS( s1.connect( "" ) );
659  REQUIRE_THROWS( s2.connect( ":" ) );
660 }
661 
662 TEST_CASE( "Test full connection" )
663 {
665 
666  unsigned short port = server.listen( "127.0.0.1" );
667  REQUIRE( port != 0 );
668  std::string target_address = "127.0.0.1:";
669  target_address += std::to_string( port );
670 
671  std::thread server_thread( [&server]( ) {
672  std::vector< nds_impl::Socket::FullSocket* > sockets;
673  sockets.push_back( &server );
674  auto results = nds_impl::Socket::FullSocket::select( sockets, 5, 0 );
675  if ( results.empty( ) )
676  {
677  throw std::runtime_error( "Client did not connect in time" );
678  }
679  auto client = server.accept( );
680  std::string data{ "hi there" };
681  client.write_all( data.data( ), data.data( ) + data.size( ) );
682  } );
684  client.connect( target_address );
685  std::vector< char > buf( 8 );
686  auto end = client.read_available( buf.data( ), buf.data( ) + buf.size( ) );
687  REQUIRE( end != buf.data( ) );
688 
689  // std::string tmp( &buf[ 0 ], end );
690  // std::cout << "Read in " << tmp.size( ) << " bytes\n";
691  // std::cout << tmp << std::endl;
692 
693  server_thread.join( );
694 }
695 
696 TEST_CASE( "Test socket wrapper" )
697 {
699 
700  unsigned short port = server.listen( "127.0.0.1" );
701  REQUIRE( port != 0 );
702  std::string target_address = "127.0.0.1:";
703  target_address += std::to_string( port );
704 
705  std::thread server_thread( [&server]( ) {
706  std::vector< nds_impl::Socket::FullSocket* > sockets;
707  sockets.push_back( &server );
708  auto results = nds_impl::Socket::FullSocket::select( sockets, 5, 0 );
709  if ( results.empty( ) )
710  {
711  throw std::runtime_error( "Client did not connect in time" );
712  }
713  auto client = server.accept( );
714  std::string data{ "hi there" };
715  client.write_all( data.data( ), data.data( ) + data.size( ) );
716  } );
718  client_.connect( target_address );
719 
720  nds_impl::Socket::SocketHandle client{ client_.get( ) };
721  std::vector< char > buf( 8 );
722  auto end = client.read_available( buf.data( ), buf.data( ) + buf.size( ) );
723  REQUIRE( end != buf.data( ) );
724 
725  // std::string tmp( &buf[ 0 ], end );
726  // std::cout << "Read in " << tmp.size( ) << " bytes\n";
727  // std::cout << tmp << std::endl;
728 
729  server_thread.join( );
730 }
731 
732 #endif // _NDS_IMPL_ENABLE_CATCH_TESTS_
733 
734 #endif // NDS_SOCKET_HH
socket_type s_
Definition: socket.hh:307
Definition: socket.hh:108
static const socket_type BAD_SOCKET
Definition: socket.hh:86
static std::vector< FullSocket * > select(Cont &sockets, long secs, long usec)
Definition: socket.hh:209
Definition: socket.hh:102
Definition: socket.hh:254
void connect(const std::string &target)
Definition: socket.hh:402
bool good() const
Definition: socket.hh:297
TEST_CASE("daq_strlcpy copies strings safely when buffers are sufficiently large")
Definition: test_bsd_string.cc:9
void bind(const std::string &address)
Definition: socket.hh:376
void closesocket(socket_type s)
Definition: socket.hh:95
void bzero(void *addr, size_t len)
Definition: socket.hh:89
unsigned short port
Definition: socket.hh:105
void swap(FullSocket &other)
Definition: socket.hh:152
decltype(::sockaddr_in::sin_addr.s_addr) node
Definition: socket.hh:104
char * read_available(char *start, char *end)
Definition: socket.hh:496
bool good() const
Definition: socket.hh:160
void write_all(const char *start, const char *end)
Definition: socket.hh:514
detail::socket_type socket_type
Definition: socket.hh:257
const void * send_ptr_type
Definition: socket.hh:82
socket_type get() const
Definition: socket.hh:283
FullSocket()
Definition: socket.hh:113
socket_type release()
Definition: socket.hh:275
FullSocket & operator=(const FullSocket &other)
::socklen_t socklen_t
Definition: socket.hh:81
~FullSocket()
Definition: socket.hh:124
socket_type release()
Definition: socket.hh:138
Address parse_address(const std::string &addr)
Definition: socket.hh:311
void reset(socket_type fd)
Definition: socket.hh:133
detail::socket_type socket_type
Definition: socket.hh:111
void swap(SocketHandle &other)
Definition: socket.hh:289
unsigned short listen(const std::string &address, int queue_depth=100)
Definition: socket.hh:193
const void * sockopt_ptr_type
Definition: socket.hh:84
socket_type s_
Definition: socket.hh:246
SocketHandle(socket_type s)
Definition: socket.hh:263
void * recv_ptr_type
Definition: socket.hh:83
void write_all(const char *start, const char *end)
Definition: socket.hh:464
FullSocket(socket_type s)
Definition: socket.hh:120
SocketHandle()
Definition: socket.hh:259
char * read_available(char *start, char *end)
Definition: socket.hh:546
#define INVALID_SOCKET
Definition: daqc_private.c:44
void reset(socket_type fd)
Definition: socket.hh:269
unsigned short listen(int queue_depth=100)
Definition: socket.hh:168
void set_option(int id, int val)
Definition: socket.hh:346
int socket_type
Definition: socket.hh:80
FullSocket(FullSocket &&other)
Definition: socket.hh:116
socket_type get() const
Definition: socket.hh:146
FullSocket accept()
Definition: socket.hh:359