diff --git a/CMakeLists.txt b/CMakeLists.txt index 844d591..a4d8866 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.6) project(frnetlib) include_directories(include) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -m64 -fPIC -pthread") set(SOURCE_FILES main.cpp src/TcpSocket.cpp include/TcpSocket.h src/TcpListener.cpp include/TcpListener.h src/Socket.cpp include/Socket.h src/Packet.cpp include/Packet.h include/NetworkEncoding.h) add_executable(frnetlib ${SOURCE_FILES}) \ No newline at end of file diff --git a/include/NetworkEncoding.h b/include/NetworkEncoding.h index fd6de19..53cd2f0 100644 --- a/include/NetworkEncoding.h +++ b/include/NetworkEncoding.h @@ -46,6 +46,13 @@ inline double ntohd(double val) memcpy(&val, &ret, sizeof(val)); return val; } +inline void *get_sin_addr(struct sockaddr *sa) +{ + if(sa->sa_family == AF_INET) + return &(((sockaddr_in*)sa)->sin_addr); + return &(((sockaddr_in6*)sa)->sin6_addr); +} + //Windows and UNIX require some different headers. //We also need some compatibility defines for cross platform support. diff --git a/include/Packet.h b/include/Packet.h index c875bee..e8d0be2 100644 --- a/include/Packet.h +++ b/include/Packet.h @@ -7,133 +7,181 @@ #include #include #include +#include #include "NetworkEncoding.h" -class Packet +namespace fr { -public: - /*! - * Gets the data added to the packet - * - * @return A string containing all of the data added to the packet - */ - inline const std::string &construct_packet() const + class Packet { - return buffer; - } + public: + /*! + * Gets the data added to the packet + * + * @return A string containing all of the data added to the packet + */ + inline const std::string &get_buffer() const + { + return buffer; + } - /* - * Adds a 16bit variable to the packet - */ - inline Packet &operator<<(uint16_t var) - { - buffer.resize(buffer.size() + sizeof(var)); - var = htons(var); - memcpy(&buffer[buffer.size() - sizeof(var)], &var, sizeof(var)); - return *this; - } + /* + * Adds a 16bit variable to the packet + */ + inline Packet &operator<<(uint16_t var) + { + buffer.resize(buffer.size() + sizeof(var)); + var = htons(var); + memcpy(&buffer[buffer.size() - sizeof(var)], &var, sizeof(var)); + return *this; + } - /* - * Extracts a 16bit variable from the packet - */ - inline Packet &operator>>(uint16_t &var) - { - memcpy(&var, &buffer[0], sizeof(var)); - buffer.erase(0, sizeof(var)); - var = ntohs(var); - return *this; - } + /* + * Extracts a 16bit variable from the packet + */ + inline Packet &operator>>(uint16_t &var) + { + memcpy(&var, &buffer[0], sizeof(var)); + buffer.erase(0, sizeof(var)); + var = ntohs(var); + return *this; + } - /* - * Adds a 32bit variable to the packet - */ - inline Packet &operator<<(uint32_t var) - { - buffer.resize(buffer.size() + sizeof(var)); - var = htonl(var); - memcpy(&buffer[buffer.size() - sizeof(var)], &var, sizeof(var)); - return *this; - } + /* + * Adds a 32bit variable to the packet + */ + inline Packet &operator<<(uint32_t var) + { + buffer.resize(buffer.size() + sizeof(var)); + var = htonl(var); + memcpy(&buffer[buffer.size() - sizeof(var)], &var, sizeof(var)); + return *this; + } - /* - * Extracts a 32bit variable from the packet - */ - inline Packet &operator>>(uint32_t &var) - { - memcpy(&var, &buffer[0], sizeof(var)); - buffer.erase(0, sizeof(var)); - var = ntohl(var); - return *this; - } + /* + * Extracts a 32bit variable from the packet + */ + inline Packet &operator>>(uint32_t &var) + { + memcpy(&var, &buffer[0], sizeof(var)); + buffer.erase(0, sizeof(var)); + var = ntohl(var); + return *this; + } - /* - * Adds a 64bit variable to the packet - */ - inline Packet &operator<<(uint64_t var) - { - buffer.resize(buffer.size() + sizeof(var)); - var = htonll(var); - memcpy(&buffer[buffer.size() - sizeof(var)], &var, sizeof(var)); - return *this; - } + /* + * Adds a 64bit variable to the packet + */ + inline Packet &operator<<(uint64_t var) + { + buffer.resize(buffer.size() + sizeof(var)); + var = htonll(var); + memcpy(&buffer[buffer.size() - sizeof(var)], &var, sizeof(var)); + return *this; + } - /* - * Extracts a 64bit variable from the packet - */ - inline Packet &operator>>(uint64_t &var) - { - memcpy(&var, &buffer[0], sizeof(var)); - buffer.erase(0, sizeof(var)); - var = ntohll(var); - return *this; - } + /* + * Extracts a 64bit variable from the packet + */ + inline Packet &operator>>(uint64_t &var) + { + memcpy(&var, &buffer[0], sizeof(var)); + buffer.erase(0, sizeof(var)); + var = ntohll(var); + return *this; + } - /* - * Adds a float variable to the packet - */ - inline Packet &operator<<(float var) - { - buffer.resize(buffer.size() + sizeof(var)); - var = htonf(var); - memcpy(&buffer[buffer.size() - sizeof(var)], &var, sizeof(var)); - return *this; - } + /* + * Adds a float variable to the packet + */ + inline Packet &operator<<(float var) + { + buffer.resize(buffer.size() + sizeof(var)); + var = htonf(var); + memcpy(&buffer[buffer.size() - sizeof(var)], &var, sizeof(var)); + return *this; + } - /* - * Extracts a float variable from the packet - */ - inline Packet &operator>>(float &var) - { - memcpy(&var, &buffer[0], sizeof(var)); - buffer.erase(0, sizeof(var)); - var = ntohf(var); - return *this; - } + /* + * Extracts a float variable from the packet + */ + inline Packet &operator>>(float &var) + { + memcpy(&var, &buffer[0], sizeof(var)); + buffer.erase(0, sizeof(var)); + var = ntohf(var); + return *this; + } - /* - * Adds a double variable to the packet - */ - inline Packet &operator<<(double var) - { - buffer.resize(buffer.size() + sizeof(var)); - var = htond(var); - memcpy(&buffer[buffer.size() - sizeof(var)], &var, sizeof(var)); - return *this; - } + /* + * Adds a double variable to the packet + */ + inline Packet &operator<<(double var) + { + buffer.resize(buffer.size() + sizeof(var)); + var = htond(var); + memcpy(&buffer[buffer.size() - sizeof(var)], &var, sizeof(var)); + return *this; + } - /* - * Extracts a double variable from the packet - */ - inline Packet &operator>>(double &var) - { - memcpy(&var, &buffer[0], sizeof(var)); - buffer.erase(0, sizeof(var)); - var = ntohd(var); - return *this; - } -private: - std::string buffer; //Packet data buffer -}; + /* + * Extracts a double variable from the packet + */ + inline Packet &operator>>(double &var) + { + memcpy(&var, &buffer[0], sizeof(var)); + buffer.erase(0, sizeof(var)); + var = ntohd(var); + return *this; + } + + /* + * Adds a string variable to the packet + */ + inline Packet &operator<<(const std::string &var) + { + //Strings are prefixed with their length as a 32bit uint :) + *this << (uint32_t)var.length(); + buffer += var; + return *this; + } + + /* + * Removes a string variable from the packet + */ + inline Packet&operator>>(std::string &var) + { + uint32_t length; + *this >> length; + + var = buffer.substr(0, length); + buffer.erase(0, length); + + return *this; + } + + /*! + * Sets the internal data buffer + * + * @param data What to set the packet to + */ + inline void set_buffer(std::string &&data) + { + buffer = std::move(data); + } + + /*! + * Clears all data from the packet + */ + inline void clear() + { + buffer.clear(); + } + + private: + std::string buffer; //Packet data buffer + }; +} #endif //FRNETLIB_PACKET_H diff --git a/include/Socket.h b/include/Socket.h index a210380..66e0641 100644 --- a/include/Socket.h +++ b/include/Socket.h @@ -8,62 +8,81 @@ #include "Packet.h" -class Socket +namespace fr { -public: - enum Status + class Socket { - Unknown = 0, - Success = 1, - ListenFailed = 2, - BindFailed = 3 + public: + enum Status + { + Unknown = 0, + Success = 1, + ListenFailed = 2, + BindFailed = 3, + Disconnected = 4, + Error = 5, + }; + + Socket() + { + + } + + /*! + * Send a packet through the socket + * + * @param packet The packet to send + * @return True on success, false on failure. + */ + virtual Status send(const Packet &packet)=0; + + /*! + * Receive a packet through the socket + * + * @param packet The packet to receive + * @return True on success, false on failure. + */ + virtual Status receive(Packet &packet)=0; + + /*! + * Close the connection. + */ + virtual void close()=0; + + /*! + * Connects the socket to an address. + * + * @param address The address of the socket to connect to + * @param port The port of the socket to connect to + * @return A Socket::Status indicating the status of the operation. + */ + virtual Socket::Status connect(const std::string &address, const std::string &port)=0; + + /*! + * Sets the socket's printable remote address + * + * @param addr The string address + */ + inline virtual void set_remote_address(const std::string &addr) + { + remote_address = addr; + } + + /*! + * Gets the socket's printable remote address + * + * @return The string address + */ + inline virtual const std::string &get_remote_address() + { + return remote_address; + } + + protected: + int socket_descriptor; + std::string remote_address; }; - - /*! - * Send a packet through the socket - * - * @param packet The packet to send - * @return True on success, false on failure. - */ - virtual bool send(const Packet &packet)=0; - - /*! - * Receive a packet through the socket - * - * @param packet The packet to receive - * @return True on success, false on failure. - */ - virtual bool receive(Packet &packet)=0; - - /*! - * Close the connection. - */ - virtual void close()=0; - - /*! - * Sets the socket's printable remote address - * - * @param addr The string address - */ - inline virtual void set_remote_address(const std::string &addr) - { - remote_address = addr; - } - - /*! - * Gets the socket's printable remote address - * - * @return The string address - */ - inline virtual const std::string &get_remote_address() - { - return remote_address; - } - -protected: - int socket_descriptor; - std::string remote_address; -}; +} #endif //FRNETLIB_SOCKET_H diff --git a/include/TcpListener.h b/include/TcpListener.h index f410666..a529df5 100644 --- a/include/TcpListener.h +++ b/include/TcpListener.h @@ -16,8 +16,6 @@ namespace fr class TcpListener { public: - TcpListener(); - /*! * Listens to the given port for connections * @@ -35,8 +33,6 @@ public: Socket::Status accept(TcpSocket &client); private: - void *get_sin_addr(struct sockaddr *sa); - addrinfo hints; int socket_descriptor; }; diff --git a/include/TcpSocket.h b/include/TcpSocket.h index 9786966..0de4e55 100644 --- a/include/TcpSocket.h +++ b/include/TcpSocket.h @@ -5,21 +5,27 @@ #ifndef FRNETLIB_TCPSOCKET_H #define FRNETLIB_TCPSOCKET_H +#include #include "Socket.h" namespace fr { +#define RECV_CHUNK_SIZE 1024 //How much data to try and recv at once class TcpSocket : public Socket { public: + TcpSocket() noexcept; + virtual ~TcpSocket() noexcept; + TcpSocket(TcpSocket &&) noexcept = default; + /*! * Send a packet through the socket * * @param packet The packet to send * @return True on success, false on failure. */ - virtual bool send(const Packet &packet); + virtual Status send(const Packet &packet); /*! * Receive a packet through the socket @@ -27,13 +33,22 @@ public: * @param packet The packet to receive * @return True on success, false on failure. */ - virtual bool receive(Packet &packet); + virtual Status receive(Packet &packet); /*! * Close the connection. */ virtual void close(); + /*! + * Connects the socket to an address. + * + * @param address The address of the socket to connect to + * @param port The port of the socket to connect to + * @return A Socket::Status indicating the status of the operation. + */ + virtual Socket::Status connect(const std::string &address, const std::string &port); + /*! * Sets the socket file descriptor. * @@ -41,9 +56,29 @@ public: */ void set_descriptor(int descriptor); + /*! + * Checks to see if we're connected to a socket or not + * + * @return True if it's connected. False otherwise. + */ + inline bool connected() const + { + return is_connected; + } + private: - ssize_t read_recv(); + /*! + * Reads size bytes into dest from the socket. + * + * @param dest Where to read the data into + * @param size The number of bytes to read + * @return Operation status. + */ + Status read_recv(void *dest, size_t size); + std::string unprocessed_buffer; + std::unique_ptr recv_buffer; + bool is_connected; }; } diff --git a/main.cpp b/main.cpp index ea2d687..bb94fbf 100644 --- a/main.cpp +++ b/main.cpp @@ -1,19 +1,47 @@ #include #include +#include +#include +#include + +void server() +{ + fr::TcpListener listener; + listener.listen("8081"); + + fr::TcpSocket socket; + listener.accept(socket); + + while(socket.connected()) + { + fr::Packet packet; + socket.receive(packet); + + std::string message; + packet >> message; + std::cout << "Got: " << message << std::endl; + } +} + +void client() +{ + fr::TcpSocket socket; + socket.connect("127.0.0.1", "8081"); + + fr::Packet packet; + packet << "Hello, World!"; + socket.send(packet); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); +} int main() { - Packet packet; - packet << (uint16_t)15000 << (uint16_t)200 << (uint32_t)9299221 << (uint64_t)9223372036854775807 << (float)1.22 << (double)192.212; - std::cout << packet.construct_packet() << std::endl; + std::thread t1(&server); - uint16_t result, result2; - uint32_t result3; - uint64_t result4; - float result5; - double result6; - packet >> result >> result2 >> result3 >> result4 >> result5 >> result6; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + client(); - std::cout << result << ", " << result2 << ", " << result3 << ", " << result4 << ", " << result5 << ", " << result6 << std::endl; + t1.join(); return 0; } \ No newline at end of file diff --git a/src/Packet.cpp b/src/Packet.cpp index d34ee26..64a1168 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -3,3 +3,8 @@ // #include "Packet.h" + +namespace fr +{ + +} \ No newline at end of file diff --git a/src/TcpListener.cpp b/src/TcpListener.cpp index 242b71f..2afc037 100644 --- a/src/TcpListener.cpp +++ b/src/TcpListener.cpp @@ -10,23 +10,23 @@ namespace fr const int yes = 1; const int no = 0; - TcpListener::TcpListener() - { - memset(&hints, 0, sizeof(addrinfo)); - hints.ai_family = AF_UNSPEC; //IPv6 or IPv4. NOTE: Might want to make configurable. - hints.ai_socktype = SOCK_STREAM; //TCP - hints.ai_flags = AI_PASSIVE; //Have the IP filled in for us - } - Socket::Status TcpListener::listen(const std::string &port) { addrinfo *info; - if(int status = getaddrinfo(NULL, port.c_str(), &hints, &info) != 0) + addrinfo hints; + + memset(&hints, 0, sizeof(addrinfo)); + + hints.ai_family = AF_UNSPEC; //IPv6 or IPv4. NOTE: Might want to make configurable. + hints.ai_socktype = SOCK_STREAM; //TCP + hints.ai_flags = AI_PASSIVE; //Have the IP filled in for us + + if(getaddrinfo(NULL, port.c_str(), &hints, &info) != 0) { return Socket::Status::Unknown; } //Try each of the results until we listen successfully - addrinfo *c; + addrinfo *c = nullptr; for(c = info; c != nullptr; c = c->ai_next) { //Attempt to connect @@ -90,11 +90,4 @@ namespace fr return Socket::Success; } - - void *TcpListener::get_sin_addr(struct sockaddr *sa) - { - if(sa->sa_family == AF_INET) - return &(((sockaddr_in*)sa)->sin_addr); - return &(((sockaddr_in6*)sa)->sin6_addr); - } } \ No newline at end of file diff --git a/src/TcpSocket.cpp b/src/TcpSocket.cpp index 200ee85..4a96368 100644 --- a/src/TcpSocket.cpp +++ b/src/TcpSocket.cpp @@ -2,49 +2,163 @@ // Created by fred on 06/12/16. // +#include #include "TcpSocket.h" namespace fr { - bool TcpSocket::send(const Packet &packet) + TcpSocket::TcpSocket() noexcept + : recv_buffer(new char[RECV_CHUNK_SIZE]), + is_connected(false) + { + + } + + TcpSocket::~TcpSocket() noexcept + { + close(); + } + + Socket::Status TcpSocket::send(const Packet &packet) { size_t send_index = 0; size_t sent = 0; - while(sent < packet.construct_packet().size()) + while(sent < packet.get_buffer().size()) { - ssize_t a = ::send(socket_descriptor, &packet.construct_packet()[send_index], packet.construct_packet().size(), 0); - if(a < 1) - return false; - sent += a; + ssize_t status = ::send(socket_descriptor, &packet.get_buffer()[send_index], packet.get_buffer().size(), 0); + if(status > 0) + { + sent += status; + } + else + { + if(status == -1) + { + return Socket::Status::Error; + } + else + { + is_connected = false; + return Socket::Status::Disconnected; + } + } } - return true; + return Socket::Status::Success; } - bool TcpSocket::receive(Packet &packet) + Socket::Status TcpSocket::receive(Packet &packet) { - std::string recv_buffer; + Socket::Status status; - //Read packet length + //Try to read packet length uint32_t packet_length = 0; + status = read_recv(&packet_length, sizeof(packet_length)); + if(status != Socket::Status::Success) + return status; + packet_length = ntohl(packet_length); - return false; + //Now we've got the length, read the rest of the data in + std::string data(packet_length, '\0'); + read_recv(&data[0], packet_length); + + //Set the packet to what we've read + packet.set_buffer(std::move(data)); + + return Socket::Status::Success; } void TcpSocket::close() { - ::close(socket_descriptor); + if(!is_connected) + { + ::close(socket_descriptor); + is_connected = false; + } } - ssize_t TcpSocket::read_recv() + Socket::Status TcpSocket::read_recv(void *dest, size_t size) { - return 0; + //See if there's enough data in the unprocessed buffer first + if(size < unprocessed_buffer.size()) + { + memcpy(dest, &unprocessed_buffer[0], size); + unprocessed_buffer.erase(0, size); + return Socket::Status::Success; + } + + //Else, keep calling recv until there's enough data in the buffer + while(unprocessed_buffer.size() < size) + { + //Read RECV_CHUNK_SIZE bytes into the recv buffer + ssize_t status = ::recv(socket_descriptor, recv_buffer.get(), RECV_CHUNK_SIZE, 0); + if(status > 0) + { + unprocessed_buffer += {recv_buffer.get(), (size_t)status}; + } + else + { + if(status == -1) + { + return Socket::Status::Error; + } + else + { + is_connected = false; + return Socket::Status::Disconnected; + } + } + } + return Socket::Status::Success; } void TcpSocket::set_descriptor(int descriptor) { socket_descriptor = descriptor; + is_connected = true; } + + Socket::Status TcpSocket::connect(const std::string &address, const std::string &port) + { + addrinfo *info; + addrinfo hints; + + memset(&hints, 0, sizeof(addrinfo)); + + hints.ai_family = AF_UNSPEC; //IPv6 or IPv4 + hints.ai_socktype = SOCK_STREAM; //TCP + hints.ai_flags = AI_PASSIVE; //Have the IP filled in for us + + if(getaddrinfo(address.c_str(), port.c_str(), &hints, &info) != 0) + { + return Socket::Status::Error; + } + + addrinfo *c; + for(c = info; c != nullptr; c = c->ai_next) + { + socket_descriptor = ::socket(c->ai_family, c->ai_socktype, c->ai_protocol); + if(socket_descriptor == INVALID_SOCKET) + { + continue; + } + + if(::connect(socket_descriptor, c->ai_addr, c->ai_addrlen) == INVALID_SOCKET) + { + continue; + } + + break; + } + + //We're done with this now, cleanup + freeaddrinfo(info); + + if(c == nullptr) + return Socket::Status::Error; + is_connected = true; + } + } \ No newline at end of file