Skip to content

Commit

Permalink
Add UDP (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
markaren authored May 15, 2024
1 parent e7350c0 commit 403ea28
Show file tree
Hide file tree
Showing 12 changed files with 250 additions and 70 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SimpleSocket

A simple cross-platform Socket implementation for C++ (no external dependencies)
A simple cross-platform Socket (TCP/UDP) implementation for C++ (no external dependencies)
for education and hobby usage.


Expand Down
16 changes: 7 additions & 9 deletions include/Socket.hpp → include/TCPSocket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@

class Connection {
public:
virtual bool read(std::vector<unsigned char>& buffer,
size_t* bytesRead = nullptr) = 0;
virtual bool read(std::vector<unsigned char>& buffer, size_t* bytesRead = nullptr) = 0;

virtual bool read(unsigned char* buffer, size_t size,
size_t* bytesRead = nullptr) = 0;
virtual bool read(unsigned char* buffer, size_t size, size_t* bytesRead = nullptr) = 0;

virtual bool readExact(std::vector<unsigned char>& buffer) = 0;

Expand All @@ -27,9 +25,9 @@ class Connection {
virtual ~Connection() = default;
};

class Socket: public Connection {
class TCPSocket: public Connection {
public:
Socket();
TCPSocket();

bool read(std::vector<unsigned char>& buffer, size_t* bytesRead) override;

Expand All @@ -45,7 +43,7 @@ class Socket: public Connection {

void close();

~Socket() override;
~TCPSocket() override;

protected:
void bind(int port);
Expand All @@ -61,12 +59,12 @@ class Socket: public Connection {
std::unique_ptr<Impl> pimpl_;
};

class ClientSocket: public Socket {
class TCPClient: public TCPSocket {
public:
bool connect(const std::string& ip, int port);
};

class ServerSocket: public Socket {
class TCPServer: public TCPSocket {
public:
void bind(int port);

Expand Down
27 changes: 27 additions & 0 deletions include/UDPSocket.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

#ifndef SIMPLE_SOCKET_UDPSOCKET_HPP
#define SIMPLE_SOCKET_UDPSOCKET_HPP


#include <memory>
#include <string>
#include <vector>

class UDPSocket{
public:
UDPSocket(int port);

bool sendTo(const std::string& address, uint16_t port, const std::string& data);

int recvFrom(const std::string& address, uint16_t port, std::vector<unsigned char>& buffer);

void close();

~UDPSocket();

private:
struct Impl;
std::unique_ptr<Impl> pimpl_;
};

#endif//SIMPLE_SOCKET_UDPSOCKET_HPP
6 changes: 1 addition & 5 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@

set(sources
"Socket.cpp"
)

add_library(simple_socket ${sources})
add_library(simple_socket TCPSocket.cpp UDPSocket.cpp)
target_compile_features(simple_socket PUBLIC "cxx_std_17")

if (WIN32)
Expand Down
50 changes: 25 additions & 25 deletions src/Socket.cpp → src/TCPSocket.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

#include "Socket.hpp"
#include "TCPSocket.hpp"

#ifdef _WIN32
#include <winsock2.h>
Expand All @@ -14,7 +14,7 @@ using SOCKET = int;
#include <iostream>
#include <stdexcept>

struct Socket::Impl {
struct TCPSocket::Impl {

Impl() {
#ifdef _WIN32
Expand All @@ -23,7 +23,7 @@ struct Socket::Impl {
throw std::runtime_error("Failed to initialize winsock");
}
#endif
sockfd = socket(AF_INET, SOCK_STREAM, 0);
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
}

bool connect(const std::string& ip, int port) {
Expand Down Expand Up @@ -78,7 +78,7 @@ struct Socket::Impl {
}
#endif

auto conn = std::make_unique<Socket>();
auto conn = std::make_unique<TCPSocket>();
conn->pimpl_->assign(new_sock);

return conn;
Expand Down Expand Up @@ -174,81 +174,81 @@ struct Socket::Impl {
bool closed{false};
};

Socket::Socket(): pimpl_(std::make_unique<Impl>()) {}
TCPSocket::TCPSocket(): pimpl_(std::make_unique<Impl>()) {}

bool Socket::read(std::vector<unsigned char>& buffer, size_t* bytesRead) {
bool TCPSocket::read(std::vector<unsigned char>& buffer, size_t* bytesRead) {

return pimpl_->read(buffer.data(), buffer.size(), bytesRead);
}

bool Socket::read(unsigned char* buffer, size_t size, size_t* bytesRead) {
bool TCPSocket::read(unsigned char* buffer, size_t size, size_t* bytesRead) {

return pimpl_->read(buffer, size, bytesRead);
}

bool Socket::readExact(std::vector<unsigned char>& buffer) {
bool TCPSocket::readExact(std::vector<unsigned char>& buffer) {

return pimpl_->readExact(buffer.data(), buffer.size());
}

bool Socket::readExact(unsigned char* buffer, size_t size) {
bool TCPSocket::readExact(unsigned char* buffer, size_t size) {

return pimpl_->readExact(buffer, size);
}

bool Socket::write(const std::string& buffer) {
bool TCPSocket::write(const std::string& buffer) {

return pimpl_->write(buffer);
}

bool Socket::write(const std::vector<unsigned char>& buffer) {
bool TCPSocket::write(const std::vector<unsigned char>& buffer) {

return pimpl_->write(buffer);
}

bool Socket::connect(const std::string& ip, int port) {
bool TCPSocket::connect(const std::string& ip, int port) {

return pimpl_->connect(ip, port);
}

void Socket::bind(int port) {
void TCPSocket::bind(int port) {

pimpl_->bind(port);
}

void Socket::listen(int backlog) {
void TCPSocket::listen(int backlog) {

pimpl_->listen(backlog);
}

std::unique_ptr<Connection> Socket::accept() {
std::unique_ptr<Connection> TCPSocket::accept() {

return pimpl_->accept();
}

void Socket::close() {
void TCPSocket::close() {

pimpl_->close();
}

Socket::~Socket() = default;
TCPSocket::~TCPSocket() = default;

bool ClientSocket::connect(const std::string& ip, int port) {
bool TCPClient::connect(const std::string& ip, int port) {

return Socket::connect(ip, port);
return TCPSocket::connect(ip, port);
}

void ServerSocket::bind(int port) {
void TCPServer::bind(int port) {

Socket::bind(port);
TCPSocket::bind(port);
}

void ServerSocket::listen(int backlog) {
void TCPServer::listen(int backlog) {

Socket::listen(backlog);
TCPSocket::listen(backlog);
}

std::unique_ptr<Connection> ServerSocket::accept() {
std::unique_ptr<Connection> TCPServer::accept() {

return Socket::accept();
return TCPSocket::accept();
}
126 changes: 126 additions & 0 deletions src/UDPSocket.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@

#include "UDPSocket.hpp"

#ifdef _WIN32
#include <WS2tcpip.h>
#include <winsock2.h>
#else
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#define SOCKET int
#define INVALID_SOCKET (SOCKET)(~0)
#define SOCKET_ERROR (-1)
#endif

#include <stdexcept>
#include <system_error>

struct UDPSocket::Impl {

explicit Impl(int port) {
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {

throw std::system_error(WSAGetLastError(), std::system_category(), "Failed to initialize winsock");
}
#endif

sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd == INVALID_SOCKET) {
#ifdef _WIN32
throw std::system_error(WSAGetLastError(), std::system_category(), "Failed to create socket");
#else
throw std::system_error(errno, std::generic_category(), "Failed to create socket");
#endif
}

sockaddr_in local{};
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_port = htons(port);

if (::bind(sockfd, (sockaddr*) &local, sizeof(local)) == SOCKET_ERROR) {
#if WIN32
throw std::system_error(WSAGetLastError(), std::system_category(), "Bind failed");
#else
throw std::system_error(errno, std::generic_category(), "Bind failed");
#endif
}
}

void bind(int port) {
}

bool sendTo(const std::string& address, uint16_t port, const std::string& data) {
sockaddr_in to{};
to.sin_family = AF_INET;
to.sin_port = htons(port);
if (!inet_pton(AF_INET, address.c_str(), &to.sin_addr)) {
return false;
}

return sendto(sockfd, data.c_str(), data.size(), 0, (sockaddr*) &to, sizeof(to)) != SOCKET_ERROR;
}

int recvFrom(const std::string& address, uint16_t port, std::vector<unsigned char>& buffer) {

sockaddr_in from{};
from.sin_family = AF_INET;
from.sin_port = htons(port);
if (!inet_pton(AF_INET, address.c_str(), &from.sin_addr)) {
return false;
}
socklen_t fromLength = sizeof(from);

int receive = recvfrom(sockfd, reinterpret_cast<char*>(buffer.data()), buffer.size(), 0, (sockaddr*) &from, &fromLength);
if (receive == SOCKET_ERROR) {
return -1;
}

return receive;
}

void close() {
if (!closed) {
closed = true;
#ifdef _WIN32
closesocket(sockfd);
#else
::close(sockfd);
#endif
}
}
~Impl() {
close();
#ifdef _WIN32
WSACleanup();
#endif
}

private:
bool closed{false};
SOCKET sockfd;
};


UDPSocket::UDPSocket(int port)
: pimpl_(std::make_unique<Impl>(port)) {}

bool UDPSocket::sendTo(const std::string& address, uint16_t port, const std::string& data) {

return pimpl_->sendTo(address, port, data);
}

int UDPSocket::recvFrom(const std::string& address, uint16_t port, std::vector<unsigned char>& buffer) {

return pimpl_->recvFrom(address, port, buffer);
}

void UDPSocket::close() {

pimpl_->close();
}

UDPSocket::~UDPSocket() = default;
8 changes: 6 additions & 2 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@

add_executable(test_socketlib test_socketlib.cpp)
target_link_libraries(test_socketlib PRIVATE simple_socket Catch2::Catch2WithMain)
add_executable(test_tcp test_tcp.cpp)
target_link_libraries(test_tcp PRIVATE simple_socket Catch2::Catch2WithMain)


add_executable(test_udp test_udp.cpp)
target_link_libraries(test_udp PRIVATE simple_socket Catch2::Catch2WithMain)

add_subdirectory(integration)
12 changes: 6 additions & 6 deletions tests/integration/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@

add_executable(run_server run_server.cpp)
target_link_libraries(run_server PRIVATE simple_socket)
add_executable(run_tcp_server run_tcp_server.cpp)
target_link_libraries(run_tcp_server PRIVATE simple_socket)

add_executable(run_client run_client.cpp)
target_link_libraries(run_client PRIVATE simple_socket)
add_executable(run_tcp_client run_tcp_client.cpp)
target_link_libraries(run_tcp_client PRIVATE simple_socket)

if (UNIX)
target_link_libraries(run_server PRIVATE pthread)
target_link_libraries(run_client PRIVATE pthread)
target_link_libraries(run_tcp_server PRIVATE pthread)
target_link_libraries(run_tcp_client PRIVATE pthread)
endif ()
Loading

0 comments on commit 403ea28

Please sign in to comment.