+ All Categories
Home > Documents > Multithreaded sockets c++11

Multithreaded sockets c++11

Date post: 08-Aug-2015
Category:
Upload: russell-childs
View: 61 times
Download: 0 times
Share this document with a friend
Popular Tags:
23
/** * @mainpage * @anchor mainpage * @brief * @details * @copyright Russell John Childs, PhD, 2015 * @author Russell John Childs, PhD * @date 2015-06-21 * * This file contains classes: Stream, Message, Socket, Verify. * * \e Stream \e: Wraps a char* buffer so that "<<" and ">>" can be used. * * \e Message \e: Serialises port, ip, message string to and from wrapped char buf[] * using "<<" and ">>" operators. This makes application less code intensive * when transferring data between application and socket * * \e Socket \e: A generic, multithreaded, multi-socket class that is used * to create a SOCK_STREAM, SOCK_DGRAM or multicast server or client * * \e Verify \e: A class for recording passes/fails of expected==actual. * * Compiled and tested under Linux Mint, using g++ 4.8. * * g++ options: -O0 -g3 -Wall -O0 -fopenmp -mavx -m64 -g -Wall -c * -fmessage-length=0 -fno-omit-frame-pointer --fast-math * -std=c++11 -I/opt/intel/vtune_amplifier_xe_2013/include/ * * Linker options: -Wl,--no-as-needed -fopenmp * -L/opt/intel/vtune_amplifier_xe_2013/lib64/ * * Cmd line options: -lpthread -latomic -littnotify -ldl * * Documentation: Doxygen comments for interfaces, normal for impl. * * @file sockets.cpp * @see * @ref mainpage */ #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <unordered_map> #include <unordered_set> #include <vector> #include <queue> #include <string> #include <sstream> #include<functional> #include <future> #include <mutex> #include <iostream> #include <iomanip> #include <algorithm> #include <cxxabi.h> #include <string.h> /** * \addtogroup Sockets * @{ */ namespace Sockets
Transcript

/** * @mainpage * @anchor mainpage * @brief * @details * @copyright Russell John Childs, PhD, 2015 * @author Russell John Childs, PhD * @date 2015-06-21 * * This file contains classes: Stream, Message, Socket, Verify. * * \e Stream \e: Wraps a char* buffer so that "<<" and ">>" can be used. * * \e Message \e: Serialises port, ip, message string to and from wrapped char buf[] * using "<<" and ">>" operators. This makes application less code intensive * when transferring data between application and socket * * \e Socket \e: A generic, multithreaded, multi-socket class that is used * to create a SOCK_STREAM, SOCK_DGRAM or multicast server or client * * \e Verify \e: A class for recording passes/fails of expected==actual. * * Compiled and tested under Linux Mint, using g++ 4.8. * * g++ options: -O0 -g3 -Wall -O0 -fopenmp -mavx -m64 -g -Wall -c * -fmessage-length=0 -fno-omit-frame-pointer --fast-math * -std=c++11 -I/opt/intel/vtune_amplifier_xe_2013/include/ * * Linker options: -Wl,--no-as-needed -fopenmp * -L/opt/intel/vtune_amplifier_xe_2013/lib64/ * * Cmd line options: -lpthread -latomic -littnotify -ldl * * Documentation: Doxygen comments for interfaces, normal for impl. * * @file sockets.cpp * @see * @ref mainpage */ #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <unordered_map> #include <unordered_set> #include <vector> #include <queue> #include <string> #include <sstream> #include<functional> #include <future> #include <mutex> #include <iostream> #include <iomanip> #include <algorithm> #include <cxxabi.h> #include <string.h> /** * \addtogroup Sockets * @{ */ namespace Sockets

{ enum ApplicationType { server = 0, /**< Creates a server */ client = 1 /**< Creates a client */ }; /** * This class wraps a buffer, char buf[Size], so that it may be used with * iostream "<<" and ">>" */ struct Stream : public std::streambuf { /** * Sets range of put and get pointers in buf[] * \tparam Size {int} - deduced size of wrapped buffer char buf[Size] */ template<int Size> Stream( char (&buffer)[Size]) { setg(buffer, buffer, buffer+Size); setp(buffer, buffer+Size); } ~Stream( void ) { } /** * Override of virtual function. * @see std::streambuf::underflow */ int_type underflow( void ) { return gptr() == egptr() ? traits_type::eof() : traits_type::to_int_type(*gptr()); } /** * Override of virtual function. * @see std::streambuf::overflow */ int_type overflow( void ) { return pptr() == epptr() ? traits_type::eof() : traits_type::to_int_type(*epptr()); } }; /** * Serialises data between buffer[] and socket, via operators "<<" and ">>".\n * Usage example:\n * std::pair<const char*, int> buf;\n * std::string buf_1; * * message >> reference_wrapper<int>(descriptor) >> buf >> * reference_wrapper<sockaddr_storage*>(addr); //order independent, vars optl * \n * message_1 >> reference_wrapper<int>(descriptor) >> buf_1 >> * reference_wrapper<sockaddr_storage*>(addr); //order independent, vars opt * * message_2 << buf << reference_wrapper<int>(descriptor) << * reference_wrapper<sockaddr_storage*>(addr); //order independent, vars opt * \n * message_3 << reference_wrapper<sockaddr_storage*>(addr) <<

* reference_wrapper<int>(descriptor) << buf_1; //order independent, vars opt */ struct Message { /** * \tparam Size {int} - size of wrapped buffer (NB: deduced) * @param buffer {char (&)[Size]} - Reference to wrapped char buffer[] * @param descriptor {int} - Socket descriptor * @param address {sockaddr_storage*} - Client or Server address of socket */ template<int Size> Message( char (&buffer)[Size], int descriptor=-1, sockaddr_storage *address=0 ) : m_buffer(buffer), m_size(Size), m_stream(buffer), m_descriptor(descriptor), m_address(address), m_in(&m_stream), m_out(&m_stream) { } ~Message( void ) { } /** * Extractor that extracts values from wrapped buffer to "msg" argument. * \tparam T - type of variable to be extracted to (deduced) * @param msg {T} - variable to extract to * * Usage: * * my_message >> my_int >> my_float >> my_string; */ template< typename T> Message& operator>>(T& msg) { m_in >> msg; m_in.get(); return *this; } /** * Extractor that copies wrapped buffer to "msg" argument. * @param msg {std::string&} - variable to extract to. * * Notes: Preserves, rather than skips, whitespace. */ Message& operator>>(std::string& msg) { std::getline(m_in, msg); m_in.get(); return *this; } /** * Extractor that extracts socket descriptor. * @param descriptor {std::reference_wrapper<int>} - * variable to extract to * * Notes: std::reference_wrapper is used to disambiguate * ">> int_from_client" and ">> reference_wrapper<int>(descriptor)" * */

friend Message& operator>>(Message& in, std::reference_wrapper<int> descriptor) { descriptor.get() = in.m_descriptor; return in; } /** * Extractor that extracts socket's client/server address. * @param address {std::reference_wrapper<sockaddr_storage*>} - * variable to extract to. * * The argument is a pointer, so that it can be used with UNIX sockets. */ friend Message& operator>>(Message& in, std::reference_wrapper<sockaddr_storage*> address) { address.get() = in.m_address; return in; } /** * Extractor that extracts wrapper buffer to char* msg. * @param msg {const char*} - variable to extract to */ friend Message& operator>>(Message& in, const char*& msg) { msg = in.m_buffer; return in; } /** * Extractor that extracts wrapper buffer & size. * @param msg {std::pair<const char*, int>&} - variable to extract to */ friend Message& operator>>(Message& in, std::pair<const char*, int>& msg) { msg.first = in.m_buffer; msg.second = in.m_size; return in; } /** * Inserter that inserts values into wrapped buffer to "msg" argument. * \tparam T type of variable to insert from * @param msg {T} - variable to insert from * * Usage: * * my_message << my_int << my_float << my_string; */ template< typename T> Message& operator<<(const T& msg) { m_out << msg << std::endl; return *this; } /** * Inserter that copies wrapper buffer & size. * @param {std::pair<const char*, int>&} msg - variable to insert from */ friend

Message& operator<<(Message& in, const std::pair<const char*, int>& msg) { in.m_buffer = msg.first; in.m_size = msg.second; return in; } private: const char* m_buffer; int m_size; Stream m_stream; int m_descriptor; sockaddr_storage *m_address; std::istream m_in; std::ostream m_out; }; /** * Helper that stores destination port, IP, request string to initiate * handshake and socket backlog */ struct PortIpRequestBacklog { /** * @param port {const std::string&} * @param ip {const std::string&} * @param request {const std::string&} * @param backlog {int} */ PortIpRequestBacklog( const std::string& port, const std::string& ip="", const std::string& request="", int backlog=10) : m_port(port), m_ip(ip), m_request(request), m_backlog(backlog) { } std::string m_port; std::string m_ip; std::string m_request; int m_backlog; }; /** * Used with SocketInfo and Socket::operator[] to extract peer/host address */ enum PeerOrHost { peer = 0, /**< Signifies remote machine for socket */ host = 1, /**< Signifies local machine for socket */ undefined = 2 /**< Signifies that getpeername/getsockname not called */ }; /** * Allows Socket<>::operator[] to be overloaded by return type. * * \tparam T {PeerOrHost} - whether info is for peer, host or undefined * * Usage example: * SocketInfo<host> info = my_socket[sock_descriptor]; \n * sockaddr_storage *host = info; \n * or \n * sockaddr_storage *host=(SocketInfo<peer>)my_socket[sock_descriptor]; */

template< PeerOrHost T = undefined> struct SocketInfo { SocketInfo( void ) : m_descriptor(0) { memset(&m_addr, 0, sizeof(sockaddr_storage)); } /** * Initialises descriptor and address * @param descriptor {int} - socket descriptor * * Notes: Zeroing sockaddr_storage seems cause getpeer(sock)name to work * incorrectly. */ SocketInfo( int descriptor) : m_descriptor( descriptor ) { //memset(&m_addr, 0, sizeof(sockaddr_storage)); } ~SocketInfo( void ) { } /** * Returns the remote machine (peer) address container. This mechanism * requires less code than overloading via a covariant return type. */ operator SocketInfo<peer>&( void ) { socklen_t len = sizeof(sockaddr_storage); getpeername(m_descriptor,(sockaddr*)&m_addr, &len); return *(SocketInfo<peer>*)this; } /** * Returns the local machine (host) address container. This mechanism * requires less code than overloading via a covariant return type. */ operator SocketInfo<host>&( void ) { socklen_t len = sizeof(sockaddr_storage); getsockname(m_descriptor,(sockaddr*)&m_addr, &len); return *(SocketInfo<host>*)this; } /** * Extracts address from address container structure. * */ operator sockaddr_storage*( void ) { return &m_addr; } //private: const int m_descriptor; sockaddr_storage m_addr; }; /** * This encapsulates the creation of a list of one or more sockets, of the * same type: stream, datagram, multicast datagram, or connected datagram. * The class may used to create a client or a server.

* * Usage example: * //Create a stream server \n * Socket<SOCK_STREAM, server> server_socket; \n * server_socket.create(vector<PortIpRequestBacklog>(port)); * server_socket.run(callback_func_for_server_recv); //Call within thread * * //Create a connected dgram client \n * Socket<SOCK_DGRAM, client> client_socket(true); //false/true is un/connected * \n //create(...), run(callback_func_for_client_recv) in thread * * //Create a multicast dgram server ('/' marks start of multicast ip) \n * Socket<SOCK_DGRAM, server> server_socket_1; \n * server_socket.create(vector<PortIpRequestBacklog>(port, "/225.0.0.37")); \n * //create(...), run(...) in thread * * //Create a multicast dgram client ('/' marks start of multicast ip) \n * Socket<SOCK_DGRAM, client> client_socket_1; \n * server_socket.create(vector<PortIpRequestBacklog>(dest_port,"/225.0.0.37")); * \n //create(...), run(...) in thread * * \tparam SockType {int} SOCK_STREAM | DGRAM - type of socket * \tparam AppType {ApplicationType} server | client - App is client or server * \tparam Family {int} - internet family (see addrinfo) * \tparam BufferSize {int} - size of buffer used to receive/send TCP/UDP msg * * @see man getaddrinfo * * Notes: * * In this implementation, a server or client sends and receives on the * same socket. This is to permit multithreading without locks. In a future * implementation they will be able to send a single msg to multiple * sockets. * * The current implementation allows a client to multicast to * multiple servers on the same socket from which it receives a msg, but * all servers must have the same port number. * * The user may effect sending of a single msg to multiple sockets, but * must provide their own thread-safety locks and record which socket * descriptor has been allocated to each server address. */ template< int SockType=SOCK_STREAM, ApplicationType AppType=client, int Family=AF_UNSPEC, int BufferSize = 256> class Socket { public: /** * @param connected {bool} - false/true is un/connected. Used with DGRAM. * @param flags {int} - addrinfo flags * @param prot {int} - internet protocol (see addrinfo) * @see man getaddrinfo */ Socket( bool connected=false, int flags=0, int prot=0) : m_quit(true), m_error(""), m_hints{flags, Family, SockType, prot}, m_max(0), m_connected(connected) { FD_ZERO(&m_descriptors); } ~Socket( void ) {

} /** * Checks if the app is running or has exited due to error. If no error has * occurred it returns true. If quit() has been called on the app, then * is_valid() will return true, unless include_quit is set, when it will * return false. * * @param include_quit {bool} - If set, returns false if quit() was called */ bool is_valid( bool include_quit=true ) { return !(include_quit && m_quit == true) && (m_error == ""); } /** * Creates the socket * * @param in {const PortIpRequestBacklog&} - server/dest port, IP, string to * be sent from client to server to start handshake, backlog val for socket * * Notes: For a multicast the IP should begin with '/'. Eg: "/225.0.0.37". */ void create(const PortIpRequestBacklog& in) { //Extract port, IP, handshake string, backlog const std::string& port = in.m_port; const std::string& ips = in.m_ip; int backlog = in.m_backlog; const std::string& request = in.m_request; //Extract multicast IP, if it exists auto split = [](const std::string& in, std::string& a, std::string& b) { auto p = in.find("/"); a = p == std::string::npos ? in : in.substr(0,p); b = p == std::string::npos ? "" : in.substr(p+1,in.size()-p-1); }; std::string ip, ip_multicast; split(ips, ip, ip_multicast); ip = AppType==server || ip_multicast.empty()==true ? ip : ip_multicast; //m_hints.ai_flags = ip.size()==0 ? AI_PASSIVE : 0; m_hints.ai_flags =ip.size()==0 && AppType==server ? AI_PASSIVE : 0; //Get addrinfo struct, set error if unsccessful addrinfo *info; const char *url = ip.empty() ? 0 : ip.data(); m_error= getaddrinfo(url, port.data(), &m_hints, &info) != 0 ? "getaddrinfo":""; //Loop over addresses while no error, addr not null auto tmp = info; int sock = -1; while( is_valid( false ) && tmp != NULL && sock < 0 ) { //Make socket sock = socket(tmp->ai_family, tmp->ai_socktype, tmp->ai_protocol); m_error = sock < 0 ? "socket" : ""; if(is_valid( false )) { //Set socket options setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &tmp,

sizeof(int) ); //Bind socket to port, if server if(AppType == server) { m_error = bind(sock,tmp->ai_addr,tmp->ai_addrlen)>= 0 ? "" : "bind"; } //Connect to server if app is a client and stream/conn dgram else if(SockType == SOCK_STREAM || m_connected) { m_error=connect(sock,tmp->ai_addr,tmp->ai_addrlen)>=0 ? "" : "connect"; //Send handshake string to stream/conn dgram server if(is_valid( false ) && request.empty()==false) { m_error = send(sock, request.c_str(), request.size()+1, 0)>0? "" : "send"; } } else { //Send handshake string to unconn dgram server if(is_valid( false ) && request.empty()==false) { m_error = sendto(sock, request.c_str(), request.size()+1, 0, tmp->ai_addr, tmp->ai_addrlen) > 0 ? "" : "sendto"; } } //Close socket, set error if bind unsuccessful if(is_valid( false ) == false) { close(sock); sock = -1; tmp = tmp->ai_next; m_error = tmp == 0 ? "bind" : ""; } //Subscribe to multicast group if "listener" (server) else if( ip_multicast.empty() == false && AppType == server) { ip_mreq mreq; mreq.imr_multiaddr.s_addr=inet_addr(ip_multicast.data()); mreq.imr_interface.s_addr=htonl(INADDR_ANY); m_error = setsockopt(sock,IPPROTO_IP,IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq))>=0 ? "" :"multicast"; } } } if( is_valid( false ) ) { freeaddrinfo(info); // If server and stream start listener, record descriptor, // set up fd set and max descriptor val for select() if( AppType == server && SockType == SOCK_STREAM) { m_error = listen(sock, backlog) == -1 ? "listen" : "";

if( is_valid( false ) ) { m_listener_array.insert(sock); FD_SET( sock, &m_descriptors ); m_max = sock; } } //If client or dgram, record descriptor, // set up fd set and max descriptor val for select() else { m_descriptor_array.insert(sock); FD_SET( sock, &m_descriptors ); m_max = sock; } } } /** * Creates multiple sockets, each associated with its own port (if server) * or port and dest address (if client) * * @param in {const std::vector<PortIpRequestBacklog>&} - list of port, * <IP>, handshake string, backlog for each of the sockets to be created */ void create( const std::vector<PortIpRequestBacklog>& in) { for(auto&& i : in ) { create(i); } } enum DescriptorType { listeners = 0, /**< Descriptors will be for listener sockets */ descriptors = 1 /**< Descriptors will be for send/recv sockets */ }; /** * Sets the socket(s) to be running, i.e. ready to accept connections and * send/recv msgs * * \tparam Functor {void(Message& message)} - Callback function to be executed upon recv/recvfrom of msg. * \par * The msg is passed to the callback as a Message& * argument. The callback may extract the port, IP, msg string of sender * and return a response via Message::operator<<(...), * \par * eg message << some_string; */ template< typename Functor, unsigned ThreadCount=1, unsigned TimeOut=5 > void run( Functor& callback) { m_quit = false; //Keep track of num of threads, {descriptor, bytes_recv} returned as //futures by std::async threads unsigned thread_count = 0; std::pair<int, int> descriptor_bytes_recv; std::vector<std::future<std::pair<int, int>> > threads; std::vector<std::pair<int, int>> desc_bytes; std::unordered_set<int> in_use; //Lambda that blocks if max thread count reached

auto wait_for_threads = [&]() { if( thread_count == ThreadCount ) { //If max reached, wait for completion of threads for( auto&& res : threads ) { res.wait(); //store {descriptor, bytes_recvd} returned by thread desc_bytes.push_back(res.get()); } //Clear list of futures and thread count threads.clear(); thread_count = 0; } }; while(is_valid()) { //Block until a socket is ready for reading auto readers = m_descriptors; timeval timeout{TimeOut,0}; m_error = select(m_max+1, &readers, 0, 0, &timeout)==-1 ? "select" : ""; if( is_valid() ) { bool is_active = false; for( int i=0; i<=m_max; ++i) { //If this socket has been flagged as ready to be read ... if( FD_ISSET(i, &readers) ) { is_active = true; //Check to see if socket is a listener if(m_listener_array.find(i) != m_listener_array.end()) { //Accept client sockaddr_storage remote_addr; auto remote = (sockaddr*) &remote_addr; socklen_t len = sizeof(remote_addr); int new_sock = accept(i, remote, &len); m_error = new_sock == -1 ? "accept" : ""; if( is_valid() ) { //Store accepted descriptor and update max val m_descriptor_array.insert(new_sock); FD_SET(new_sock, &m_descriptors); m_max = new_sock > m_max ? new_sock : m_max; } } //If send/recv socket and not in use by another thread ... else if( in_use.find(i)==in_use.end()) { in_use.insert(i); //Lambda called in new thread: recv, run callback auto process_msg=[&, i]() { //recv(recvfrom) according to stream(dgram) sockaddr_storage remote_addr; auto remote = (sockaddr*)(&remote_addr); socklen_t len = sizeof(remote_addr); int bytes = 0;

char buf[BufferSize]; if(SockType==SOCK_STREAM || m_connected) { bytes = recv(i, buf, sizeof(buf), 0); } else { bytes=recvfrom(i,buf,sizeof(buf),0, remote, &len); } //Execute callback, passing in recvd msg if( bytes != 0 ) { Message msg(buf, i, &remote_addr); callback( msg, this ); } //Return {descriptor, bytes_recvd} to parent thread return std::make_pair(i, bytes); }; //Store thread's future to prevent it blocking threads.push_back(std::async(std::launch::async, process_msg)); //Update thread count and block if max reached ++thread_count; wait_for_threads(); } } } //select() timed out (0 readers), delegate action to callback if(is_active == false) { char buf[] ="Sockets::Socket::SockMsg: No activity"; Message msg(buf); callback(msg, this); } } //Wait for completion of threads if thread count max is reached wait_for_threads(); //Loop over{descriptor,bytes_recvd}, close if 0 bytes(conn dropped) for( auto&& res : desc_bytes ) { if( res.second == 0 ) { close(res.first); m_descriptor_array.erase(res.first); FD_CLR(res.first, &m_descriptors); } } desc_bytes.clear(); in_use.clear(); } for(auto&& listener : m_listener_array) close(listener); m_listener_array.clear(); for(auto&& descriptor : m_descriptor_array) close(descriptor); m_descriptor_array.clear(); } /** * Inserts messages onto socket for send/sendto * * @param msg {Message&}

* * Usage example: * * message << msg_1 << msg_2; */ Socket& operator<<( Message& msg ) { //Extract socket, port+IP and msg string from msg arg int descriptor=0; sockaddr_storage *addr; std::pair<const char*, int> buf; msg >> std::reference_wrapper<int>(descriptor) >> std::reference_wrapper<sockaddr_storage*>(addr) >> buf; //Set up fd set for select int bytes = 1; fd_set writer; FD_ZERO(&writer); FD_SET(descriptor, &writer); //While partial send and no send error (bytes<=0) while(buf.second > 0 && bytes > 0) { //Block until socket ready for write select(descriptor+1, 0, &writer, 0, 0); //send/sendto if stream/dgram if( SockType == SOCK_STREAM || m_connected) { bytes = send(descriptor, buf.first, buf.second, 0); } else { bytes = sendto(descriptor, buf.first, buf.second, 0, (sockaddr*)(addr),sizeof(*addr)); } buf.first += bytes; buf.second -= bytes; } //Return *this to allow message << msg_1 << msg_2 ... return *this; } /** * Shutdowm client or server */ void quit( void ) { m_quit = true; } /** * Checks whether client or server is running * * @return {bool} - true iff running */ bool is_running( void ) { return m_quit == false; } /** * Get descriptors of listener sockets *

* @return {const std::vector<int>&} list of listener descriptors */ const std::vector<int>& get_listeners( void ) { return m_listener_array; } /** * Get descriptors of all sockets * * @return {const std::vector<int>&} list of descriptors */ const std::vector<int>& get_descriptors( void ) { return m_descriptor_array; } /** * Returns the remote/local machine (peer/host) address for the socket. * @param descriptor {int} - socket descriptor * @return {SocketInfo<>} - the address container for remote/local machine * * Usage example: * SocketInfo<host> info = my_socket[sock_descriptor]; \n * sockaddr_storage *host = info; \n * or \n * sockaddr_storage *host=(SocketInfo<peer>)my_socket[sock_descriptor]; */ SocketInfo<> operator[]( int descriptor ) { auto deb = SocketInfo<>(descriptor); return SocketInfo<>(descriptor); } /** * Get error that terminated client or server * * @return {const std::string&} - the error. If no error: "" */ const std::string& get_error( void ) { return m_error; } private: std::atomic<bool> m_quit; std::string m_error; addrinfo m_hints; int m_max = 0; bool m_connected; fd_set m_descriptors; std::unordered_set<int> m_listener_array; std::unordered_set<int> m_descriptor_array; }; /** * This function is a substitute for gethostname and gethostbyname, which were * found not to work correctly within a VMWare virtual machine * * @param {std::string& addr} The IP address of this host. * * Notes: If the internet is disconnected, "Error: No internet" is returned. */ void get_ip(std::string& addr) { int file_descriptors[2]; pipe(file_descriptors);

if(!fork()) { close(1); dup(file_descriptors[1]); close(file_descriptors[0]); execlp("hostname", "hostname", "-I", NULL); } else { char buf[256]; close(0); dup(file_descriptors[0]); close(file_descriptors[1]); auto bytes = read(file_descriptors[0], buf, sizeof(buf)); buf[bytes]='\0'; std::string str(buf); str.erase(remove_if(str.begin(), str.end(), isspace), str.end()); addr = std::string(buf).find('.') == std::string::npos ? "Error: No internet" : str; } } /** * Gets the port from sockaddr_storage. * * @param storage {const sockaddr_storage& } * @return {std::string} - the port */ std::string get_port(const sockaddr_storage& storage) { return std::to_string(ntohs( ((sockaddr*)&storage)->sa_family == AF_INET ? (((sockaddr_in*)&storage)->sin_port) : (((sockaddr_in6*)&storage)->sin6_port)) ); } /** * Gets the IP from sockaddr_storage. * * @param storage {const sockaddr_storage& } * @return {std::string} - the IP */ std::string get_address(const sockaddr_storage& storage) { char str[256]; inet_ntop( storage.ss_family, ((sockaddr*)&storage)->sa_family == AF_INET ? (char*)&(((sockaddr_in*)&storage)->sin_addr) : (char*)&(((sockaddr_in6*)&storage)->sin6_addr), str, sizeof(str)); return str; } /** * Gets the port & IP from sockaddr_storage. * * @param storage {const sockaddr_storage& } * @return {std::pair<std::string, std::string>} - the {port,IP} pair */ std::pair<std::string, std::string> get_port_and_address(const sockaddr_storage& storage) { return std::make_pair(get_port(storage), get_address(storage)); } }; /** * @}

*/ /** * \addtogroup UnitTest * @{ */ namespace UnitTest { /** * This class provides basic unit testing capability. * *\tparam {T} type of variable being verified * * Usage: * * Verify<MyType>("Verifying my_float: ", my_variable) == expected_value * */ template< typename T=int> struct Verify { /** * @param {T} - The type of the variable being verified * @param name {const std::string&} - The header to precede each checkpoint * @param actual {const T& } - The actual value of the variable verified * * Notes: This is used in conjunction with operator==(). * * Example: Verify<float>("Verifying my_float: ", my_float) == expected_value; */ Verify( const std::string& name, const T& actual ) : m_name(name), m_actual(actual), m_banner( "" ) { } /** * @param name {const std::string&} - Banner message * * Usage example: * * void test_message_class() \n * { \n * Verify<> banner("Message class"); //prints banner b4 test output \n * tests(); \n * //On exit dtor of "banner" prints banner after test output * \n } */ Verify(const std::string& str = "") : m_name(""), m_actual(T()), m_banner( str ) { //Run banner at start of tests if( str.empty() == false ) { banner( str ); } } /** * Usage example: * void test_message_class() \n * { \n * Verify<> banner("Message class"); //prints banner b4 test output \n * tests(); \n * //On exit dtor of "banner" prints banner after test output \n

* } */ ~Verify(void) { //Run banner at end of tests if( m_banner.empty() == false ) { banner( m_banner, false ); } } /** * Returns number of successful tests * * @return {unsigned&} - number of tests that passed * * Usage example: * * int main() \n * { \n * tests(); \n * std::cout << "Number of tests that passed=" << Verify<>::passes(); * \n } */ static unsigned& passes( void ) { static unsigned pass = 0; return pass; } /** * Returns total number of tests * * @return {unsigned&} - total number of tests * * Usage example: * * int main() \n * { \n * tests(); \n * std::cout << "Total number of tests that failed=" << Verify<>::total(); \n * } */ static unsigned& total( void ) { static unsigned tot = 0; return tot; } /** * Verifies an expected value against an actual value, used with ctor * * Usage: * * Verify<MyType>("Verifying my_float: ", my_variable) == expected_value */ bool operator==(const T& expected) { //Update total number of tests bool passed = true; ++Verify<>::total(); //Print results of comparison // "test message" (Expected: exp) == (Actual: act) passed/failed

std::stringstream ssname, ss; ssname << m_name << ": "; ss << std::left << std::setw(65) << ssname.str() << "(expected: " << "\"" << expected <<"\")" << " == " << "(actual: " << "\"" << m_actual << "\")"; std::cout << std::left << std::setfill('-') << std::setw(150) << ss.str(); if(expected == m_actual) { ++Verify<>::passes(); std::cout << "->passed" << std::endl; } else { passed = false; std::cout << "->failed" << std::endl; } //Return whether pass or fail return passed; } private: /** * Prints banner at start ("Testing" msg_string) * and end of block of tests ("Tests completed for " msg_string) */ static void banner( const std::string& msg, bool begin = true ) { std::cout << "=====================================================" << std::endl << (begin ? "Testing " : "Tests completed for ") << msg << std::endl << "=====================================================" << std::endl; } std::string m_name; const T& m_actual; const std::string m_banner; }; /** * Dummy struct used for Verify<> specialisation */ struct Results{}; /** * This specialisation simply prints out total number of tests and successes * when program exits. * * Usage example: * int main() \n * { \n * Verify<> tests; \n * various_tests(); \n * //on exit, dtor of "tests" prints out total number of tests + successes * \n } */ template<> struct Verify<Results> { ~Verify( void ) { std::cout << "Total: " << Verify<>::total() << ", passes: " << Verify<>::passes()

<< std::endl; } }; /** * Helper function that creates and returns a Verify<Type> object for use with * == expected_value * * \tparam {T} deduced type of variable being verified * @param name {const std::string&} meesage used to identify test in output * @param var {const T& } - actual value of variable to be == with expected val * * Usage example: * * verify("Verifying my_float: ", my_variable) == expected_value */ template<typename T> Verify<T> verify(const std::string& name, const T& var) { return Verify<T>(name, var); } /** * Helper macros that insert function name of caller into test identifier * passed to verify(id, var) */ #define VERIFY(msg, var) \ verify(std::string(__PRETTY_FUNCTION__)+" ["+#var+"] "+msg,var) } /** * @} */ /** * \addtogroup Tests * @{ */ namespace Tests { /** * Tests of Message class */ void test_message( void ) { using namespace UnitTest; //Print test banner Verify<> banner( "Message class" ); using namespace Sockets; //Create msg and add an int and a string to be sent sockaddr_storage addr; char buf[256]; Message msg(buf, 10, &addr); std::string str(" Str with spaces "); msg << 1234 << str; //Extract descriptor, addr, int and string back from msg int test_descriptor=0, test_int=0; sockaddr_storage *test_addr; std::string test_str = ""; const char *test_buf; msg >> std::reference_wrapper<int>(test_descriptor) >> test_int >> std::reference_wrapper<sockaddr_storage*>(test_addr)

>> test_str >> std::reference_wrapper<const char*>(test_buf); //Verify that values extracted match values inserted VERIFY("Test Descriptor", test_descriptor) == 10; VERIFY("Test int", test_int) == 1234; VERIFY("Test addr", test_addr) == &addr; VERIFY("Test str", test_str) == str; VERIFY("Test buf", (float*)test_buf) == (float*)buf; } /** * Tests of Socket class */ template<int Type> void test_socket( const std::string& addr="", bool is_connected=false, bool multicast = false ) { using namespace UnitTest; //Multicast if set true and not a connected dgram multicast = multicast && !is_connected; //Prepare msg string according to msg type std::string test = is_connected ? "Connected " : ""; test += Type == SOCK_STREAM ? "sock stream" : multicast == false ? "datagram" : "multicast"; //Prepare msg string according to whether server or client Verify<> banner(test + " Client/Server"); using namespace Sockets; //Create server socket Socket<Type, server> server_socket; //Create thread function to run server & keep count of msgs recvd unsigned server_msg_count = 0; auto server_callback = [&server_socket, multicast, &server_msg_count, is_connected]() { //Specify port and addr (blank or multicast) for server std::vector<PortIpRequestBacklog> vec{PortIpRequestBacklog("10000", multicast ? "/225.0.0.37":"")}; server_socket.create(vec); //Create callback for recvd msgs auto callback = [&server_socket, &server_msg_count, multicast]( Message& msg, decltype(server_socket) *sock) { #define __PRETTY_FUNCTION__ "server::callback()" //Extract msg string std::pair<const char*, int> str; msg >> str; //If msg is not a "no activity" msg from server ... if(std::string(str.first)!="Sockets::Socket::SockMsg: No activity") { //Define expected msg based on msg count std::string tmp = server_msg_count==0 ? "(1) Client request" : "(3) QUIT"; std::string func = Type == SOCK_STREAM ? "[void sock_stream_server()] Client req msg" :

"[void sock_dgram_server()] Client req msg"; //Verify msg is the one expected VERIFY("Test server recv", std::string(str.first))==tmp.c_str(); //Verify address is correct for stream if(Type==SOCK_STREAM) { //Get socket int desc; msg >> std::reference_wrapper<int>(desc); //Get host IP std::string addr; get_ip(addr); //Get address container sockaddr_storage *host_addr = (SocketInfo<host>)(server_socket[desc]); //Verify server(host) port &addr and client(peer) addr VERIFY("Host port",get_port_and_address(*host_addr).first) == "10000"; VERIFY("Host address",get_port_and_address(*host_addr).second) == addr; sockaddr_storage *peer_addr = (SocketInfo<peer>)server_socket[desc]; VERIFY("Peer address",get_port_and_address(*peer_addr).second) == addr; } //Create response and send back to client via socket std::string resp = "(2) Server response"; if(server_msg_count==0) { ++server_msg_count; msg << std::make_pair<const char*,int>(resp.c_str(), resp.size()+1); *sock << msg; } else { //"no activity", assume client offline & shut down server sock->quit(); } } else { //Have received all msgs, shut down server sock->quit(); } #undef __PRETTY_FUNCTION__ }; //Start server running with above callback function server_socket.run(callback); }; //Launch thread to run server, store returned future to prevent blocking auto res_server = std::async(std::launch::async, server_callback); //Wait for server to be in "ready" state before starting client while(server_socket.is_running() == false) { std::this_thread::yield(); }

//Create client. is_connected=true only applied if dgram Socket<Type, client> client_socket(is_connected); //Create thread function to run client auto client_callback = [&client_socket, multicast, is_connected, addr]() { //Specify server port & addr/multicast addr to client + handshake str std::vector<PortIpRequestBacklog> vec{PortIpRequestBacklog("10000", multicast ? "/225.0.0.37" : addr, "(1) Client request")}; client_socket.create(vec); //Create callback for msgs recvd by client auto callback =[&client_socket, multicast, is_connected] ( Message& msg, decltype(client_socket)*) { #define __PRETTY_FUNCTION__ "client::callback()" std::pair<const char*, int> str; msg >> str; //If not "no activity" msg if(std::string(str.first)!="Sockets::Socket::SockMsg: No activity") { //Verify msg is correct std::string tmp = "(2) Server response"; std::string func = Type == SOCK_STREAM ? "[void sock_stream_client()] Server resp msg" : "[void sock_dgram_client()] Server resp msg"; VERIFY("Server resp msg", std::string(str.first))==tmp; //Verify address is correct (only for strem or connected dgram if(Type==SOCK_STREAM || is_connected) { //Get socket int desc; msg >> std::reference_wrapper<int>(desc); //Get host & peer address containers sockaddr_storage *host_addr = (SocketInfo<host>)(client_socket[desc]); sockaddr_storage *peer_addr = (SocketInfo<peer>)client_socket[desc]; //Get host ip std::string addr; get_ip(addr); //Get address of socket sockaddr_storage *p ; //Verify client(host) addr and server(peer) port & addr msg >> std::reference_wrapper<sockaddr_storage*>(p); VERIFY("Host address",get_port_and_address(*host_addr).second) == addr; VERIFY("Peer port",get_port_and_address(*peer_addr).first) == "10000"; VERIFY("Peer address",get_port_and_address(*peer_addr).second) == addr; } //Send response back to server, telling it to shut down std::cout<<"Shutting down server. Please wait ..."<<std::endl; std::string resp = "(3) QUIT"; msg << std::make_pair<const char*,int>(resp.c_str(), resp.size()+1); client_socket << msg; //Resend, to verify that app doesn't crash if server offline resp = "(4) QUIT"; msg << std::make_pair<const char*,int>(resp.c_str(),

resp.size()+1); client_socket << msg; } else { // "no activty", assume server is offline & shut down client client_socket.quit(); } #undef __PRETTY_FUNCTION__ }; client_socket.run(callback); }; //Run client in a thread, store returned future to prevent blocking auto res_client = std::async(std::launch::async, client_callback); //Wait for server thread to exit res_server.wait(); std::cout << "Server is shut down" << std::endl; //Shut down client std::cout << "Shutting down client. Please wait ..." << std::endl; client_socket.quit(); //Wait for client to shut down while(client_socket.is_running() == true) { std::this_thread::yield(); } //Wait for client thread to exit res_client.wait(); std::cout << "Client is shut down" << std::endl; } } /** * @} */ int main( void ) { using namespace Sockets; using namespace UnitTest; using namespace Tests; //Run tests of Message class Verify<Results> results; test_message(); //Get ip of this machine std::string addr; get_ip(addr); std::cout << std::endl << "Using IP = \"" << addr << "\"" << std::endl; //Run sock stream client/server tests test_socket<SOCK_STREAM>(addr); //Run unconnected dgram client/server tests test_socket<SOCK_DGRAM>(addr); //Run multicast dgram client/server tests test_socket<SOCK_DGRAM>(addr, false, true); //Run connected dgram client/server tests test_socket<SOCK_DGRAM>(addr, true); return 0; }


Recommended