nds2-client - Developer 0.16.7
Loading...
Searching...
No Matches
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
28namespace 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
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
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 );
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
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
575TEST_CASE( "Can create a socket object", "[create_basic]" )
576{
578 REQUIRE( !s.good( ) );
579}
580
581TEST_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
591TEST_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
607TEST_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
620TEST_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
636TEST_CASE( "Test connect with an initialized socket", "[connect]" )
637{
639 REQUIRE_THROWS( s.connect( "localhost:5000" ) );
640 s.release( );
641}
642
643TEST_CASE( "Test connect with out a port specified", "[connect_no_port]" )
644{
646 REQUIRE_THROWS( s.connect( "localhost" ) );
647}
648
649TEST_CASE( "Test connect with out a host specified", "[connect_no_host]" )
650{
652 REQUIRE_THROWS( s.connect( ":5000" ) );
653}
654
655TEST_CASE( "Test connect with no address", "[connect_no_address]" )
656{
658 REQUIRE_THROWS( s1.connect( "" ) );
659 REQUIRE_THROWS( s2.connect( ":" ) );
660}
661
662TEST_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
696TEST_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
Definition socket.hh:109
unsigned short listen(int queue_depth=100)
Definition socket.hh:168
~FullSocket()
Definition socket.hh:124
FullSocket(socket_type s)
Definition socket.hh:120
socket_type release()
Definition socket.hh:138
socket_type s_
Definition socket.hh:246
void write_all(const char *start, const char *end)
Definition socket.hh:464
FullSocket()
Definition socket.hh:113
void set_option(int id, int val)
Definition socket.hh:346
detail::socket_type socket_type
Definition socket.hh:111
void connect(const std::string &target)
Definition socket.hh:402
FullSocket(FullSocket &&other)
Definition socket.hh:116
bool good() const
Definition socket.hh:160
FullSocket(const FullSocket &other)
void reset(socket_type fd)
Definition socket.hh:133
void swap(FullSocket &other)
Definition socket.hh:152
void bind(const std::string &address)
Definition socket.hh:376
socket_type get() const
Definition socket.hh:146
unsigned short listen(const std::string &address, int queue_depth=100)
Definition socket.hh:193
FullSocket & operator=(const FullSocket &other)
char * read_available(char *start, char *end)
Definition socket.hh:496
FullSocket accept()
Definition socket.hh:359
static std::vector< FullSocket * > select(Cont &sockets, long secs, long usec)
Definition socket.hh:209
Definition socket.hh:255
socket_type get() const
Definition socket.hh:283
bool good() const
Definition socket.hh:297
char * read_available(char *start, char *end)
Definition socket.hh:546
SocketHandle()
Definition socket.hh:259
void swap(SocketHandle &other)
Definition socket.hh:289
socket_type release()
Definition socket.hh:275
void reset(socket_type fd)
Definition socket.hh:269
void write_all(const char *start, const char *end)
Definition socket.hh:514
detail::socket_type socket_type
Definition socket.hh:257
SocketHandle(socket_type s)
Definition socket.hh:263
SocketHandle(const SocketHandle &other)=default
socket_type s_
Definition socket.hh:307
#define INVALID_SOCKET
Definition daqc_private.c:44
void * recv_ptr_type
Definition socket.hh:83
const void * send_ptr_type
Definition socket.hh:82
static const socket_type BAD_SOCKET
Definition socket.hh:86
const void * sockopt_ptr_type
Definition socket.hh:84
::socklen_t socklen_t
Definition socket.hh:81
void closesocket(socket_type s)
Definition socket.hh:95
int socket_type
Definition socket.hh:80
void bzero(void *addr, size_t len)
Definition socket.hh:89
Address parse_address(const std::string &addr)
Definition socket.hh:311
Definition span_reader.hh:14
Definition socket.hh:103
unsigned short port
Definition socket.hh:105
decltype(::sockaddr_in::sin_addr.s_addr) node
Definition socket.hh:104
TEST_CASE("daq_strlcpy copies strings safely when buffers are sufficiently large")
Definition test_bsd_string.cc:9