diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..10433cc --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.20) + +project(udp_server) + +set(udp_server main.cpp) + +#source_group(source FILES ${${PROJECT_NAME}_SRC}) + +add_executable(main.exe main.cpp socket_wrapper.cpp socket.cpp) +target_link_libraries(main.exe ws2_32.lib) + +#if(WIN32) +# target_link_libraries(udp_server wsock32 ws2_32) +#endif() + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..a8e4e3a --- /dev/null +++ b/main.cpp @@ -0,0 +1,107 @@ +#include +#include +#include +#include +#include + +#include "socket_headers.h" +#include "socket_wrapper.h" +#include "socket_class.h" +#include "socket_wrapper_windows.h" + +// Trim from end (in place). +static inline std::string& rtrim(std::string& s) +{ + s.erase(std::find_if(s.rbegin(), s.rend(), [](int c) { return !std::isspace(c); }).base(), s.end()); + return s; +} + + +int main(int argc, char const *argv[]) +{ + + if (argc != 2) + { + std::cout << "Usage: " << argv[0] << " " << std::endl; + return EXIT_FAILURE; + } + + socket_wrapper::SocketWrapper sock_wrap; + const int port { std::stoi(argv[1]) }; + + socket_wrapper::Socket sock = {AF_INET, SOCK_DGRAM, IPPROTO_UDP}; + + std::cout << "Starting echo server on the port " << port << "...\n"; + + if (!sock) + { + std::cerr << sock_wrap.get_last_error_string() << std::endl; + return EXIT_FAILURE; + } + + sockaddr_in addr = + { + .sin_family = PF_INET, + .sin_port = htons(port), + }; + + addr.sin_addr.s_addr = INADDR_ANY; + + if (bind(sock, reinterpret_cast(&addr), sizeof(addr)) != 0) + { + std::cerr << sock_wrap.get_last_error_string() << std::endl; + // Socket will be closed in the Socket destructor. + return EXIT_FAILURE; + } + + char buffer[256]; + + // socket address used to store client address + struct sockaddr_in client_address = {0}; + socklen_t client_address_len = sizeof(sockaddr_in); + ssize_t recv_len = 0; + + std::cout << "Running echo server...\n" << std::endl; + char client_address_buf[INET_ADDRSTRLEN]; + + while (true) + { + // Read content into buffer from an incoming client. + recv_len = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, + reinterpret_cast(&client_address), + &client_address_len); + + if (recv_len > 0) + { + buffer[recv_len] = '\0'; + std::cout + << "Client with address " + << inet_ntoa( client_address.sin_addr ) + << ":" << ntohs(client_address.sin_port) + << " sent datagram " + << "[length = " + << recv_len + << "]:\n'''\n" + << buffer + << "\n'''" + << std::endl; + std::string command_string = {buffer, 0, recv_len}; + if ("exit" == command_string) + EXIT_SUCCESS; + send(sock, buffer, recv_len, 0); + + + rtrim(command_string); + std::cout << command_string << std::endl; + + // Send same content back to the client ("echo"). + sendto(sock, buffer, recv_len, 0, reinterpret_cast(&client_address), + client_address_len); + } + + std::cout << std::endl; + } + + return EXIT_SUCCESS; +} + diff --git a/socket.cpp b/socket.cpp new file mode 100644 index 0000000..d8bcdab --- /dev/null +++ b/socket.cpp @@ -0,0 +1,78 @@ +#include "socket_class.h" +#include "socket_headers.h" + +#include + +#ifdef _WIN32 + constexpr auto close_type = SD_BOTH; +# define close_socket closesocket +#else + constexpr auto close_type = SHUT_RDWR; +# define close_socket ::close +#endif + + +namespace socket_wrapper +{ + +Socket::Socket(int domain, int type, int protocol) : socket_descriptor_(INVALID_SOCKET) +{ + open(domain, type, protocol); +} + + +Socket::Socket(SocketDescriptorType socket_descriptor) : socket_descriptor_(socket_descriptor) +{ +} + + +Socket::Socket(Socket &&s) +{ + socket_descriptor_ = s.socket_descriptor_; + s.socket_descriptor_ = INVALID_SOCKET; +} + + +Socket &Socket::operator=(Socket &&s) +{ + if (&s == this) return *this; + + if (opened()) close(); + std::swap(socket_descriptor_, s.socket_descriptor_); + + return *this; +} + + +Socket::~Socket() +{ + if (opened()) close(); +} + + +bool Socket::opened() const +{ + return socket_descriptor_ != INVALID_SOCKET; +} + + +void Socket::open(int domain, int type, int protocol) +{ + if (opened()) close(); + socket_descriptor_ = socket(domain, type, protocol); +} + + +int Socket::close() +{ + int status = 0; + + status = close_socket(socket_descriptor_); + socket_descriptor_ = INVALID_SOCKET; + + return status; + +} + +} + diff --git a/socket_class.h b/socket_class.h new file mode 100644 index 0000000..96fb85c --- /dev/null +++ b/socket_class.h @@ -0,0 +1,38 @@ +#pragma once + +#include "socket_headers.h" + +namespace socket_wrapper +{ + +class Socket +{ +public: + Socket(int domain, int type, int protocol); + Socket(SocketDescriptorType socket_descriptor); + + Socket(const Socket&) = delete; + Socket(Socket &&s); + Socket &operator=(const Socket &s) = delete; + Socket &operator=(Socket &&s); + + virtual ~Socket(); + +public: + bool opened() const; + +public: + operator bool() const { return opened(); } + operator SocketDescriptorType() const { return socket_descriptor_; } + +public: + int close(); + +protected: + void open(int domain, int type, int protocol); + +private: + SocketDescriptorType socket_descriptor_; +}; + +} // socket_wrapper diff --git a/socket_headers.h b/socket_headers.h new file mode 100644 index 0000000..223dc8d --- /dev/null +++ b/socket_headers.h @@ -0,0 +1,43 @@ +#pragma once + +extern "C" +{ +#ifdef _WIN32 +# include +# include + + /* See http://stackoverflow.com/questions/12765743/getaddrinfo-on-win32 */ +# if !defined(_WIN32_WINNT) +# define _WIN32_WINNT 0x0501 /* Windows XP. */ +# endif +typedef SOCKET SocketDescriptorType; +//typedef int ssize_t; +typedef unsigned long IoctlType; + +# if !defined(in_addr_t) +# include +typedef uint32_t in_addr_t; +# endif + +#else +/* Assume that any non-Windows platform uses POSIX-style sockets instead. */ +# include +# include +# include /* Needed for getaddrinfo() and freeaddrinfo() */ +# include /* Needed for close() */ +typedef int SocketDescriptorType; +typedef int IoctlType; + +#endif + +// Defined for Windows Sockets. +#if !defined(INVALID_SOCKET) +# define INVALID_SOCKET (-1) +#endif + +// Defined for Windows Sockets. +#if !defined(SOCKET_ERROR) +# define SOCKET_ERROR (-1) +#endif + +} diff --git a/socket_wrapper.cpp b/socket_wrapper.cpp new file mode 100644 index 0000000..63852a1 --- /dev/null +++ b/socket_wrapper.cpp @@ -0,0 +1,44 @@ +#include "socket_wrapper.h" + +#ifdef _WIN32 +# include "socket_wrapper_windows.h" +#else +# include "socket_wrapper_unix.h" +#endif + + +namespace socket_wrapper +{ + + +SocketWrapper::SocketWrapper() : impl_{std::make_unique()} +{ + impl_->initialize(); +} + + +SocketWrapper::~SocketWrapper() +{ + impl_->deinitialize(); +} + + +bool SocketWrapper::initialized() const +{ + return impl_->initialized(); +} + + +int SocketWrapper::get_last_error_code() const +{ + return impl_->get_last_error_code(); +} + + +std::string SocketWrapper::get_last_error_string() const +{ + return impl_->get_last_error_string(); +} + +} + diff --git a/socket_wrapper.h b/socket_wrapper.h new file mode 100644 index 0000000..bf6922c --- /dev/null +++ b/socket_wrapper.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + + +namespace socket_wrapper +{ + +class SocketWrapperImpl; + +class SocketWrapper +{ +public: + SocketWrapper(); + ~SocketWrapper(); + +public: + bool initialized() const; + int get_last_error_code() const; + std::string get_last_error_string() const; + +private: + std::unique_ptr impl_; +}; + +} diff --git a/socket_wrapper_impl.h b/socket_wrapper_impl.h new file mode 100644 index 0000000..36099ee --- /dev/null +++ b/socket_wrapper_impl.h @@ -0,0 +1,15 @@ +#pragma once + +#include + + +class ISocketWrapperImpl +{ +public: + virtual void initialize() = 0; + virtual bool initialized() const = 0; + virtual void deinitialize() = 0; + virtual int get_last_error_code() const = 0; + virtual std::string get_last_error_string() const = 0; +}; + diff --git a/socket_wrapper_windows.h b/socket_wrapper_windows.h new file mode 100644 index 0000000..b587ff0 --- /dev/null +++ b/socket_wrapper_windows.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include + +#include "socket_wrapper_impl.h" + + +namespace socket_wrapper +{ + +class SocketWrapperImpl : ISocketWrapperImpl +{ +public: + SocketWrapperImpl() : initialized_(false) {} + + void initialize() + { + WSADATA wsaData; + // Initialize Winsock + auto result = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (result != 0) + { + throw std::runtime_error(get_last_error_string()); + } + } + + bool initialized() const + { + return initialized_; + } + + void deinitialize() + { + WSACleanup(); + } + + int get_last_error_code() const + { + return WSAGetLastError(); + } + + std::string get_last_error_string() const + { + // return std::strerror(std::errno); + char *s = NULL; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, get_last_error_code(), MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), (LPSTR)&s, 0, NULL); + std::string result{s}; + LocalFree(s); + + return result; + }; + +private: + bool initialized_; +}; + +}