In my ongoing attempts to become a better blog writer I have some written some more code that needs reviewing.
This is a simple C++ wrapper for Sockets.
Here the bit I am not 100% happy with is needing to introduce a Socket.tpp file to hold getMessageData()
. This is because the function is templated with a callback function that is used to decide if we have read enough. This is not used by the ProtocolSimple
(in Version 2) but will be used by ProtocolHTTP
(in Version 3).
##Socket.h
#ifndef THORSANVIL_SOCKET_SOCKET_H
#define THORSANVIL_SOCKET_SOCKET_H
#include <string>
#include <vector>
#include <sstream>
namespace ThorsAnvil
{
namespace Socket
{
// An RAII base class for handling sockets.
// Socket is movable but not copyable.
class BaseSocket
{
int socketId;
protected:
// Designed to be a base class not used used directly.
BaseSocket(int socketId);
int getSocketId() const {return socketId;}
public:
~BaseSocket();
// Moveable but not Copyable
BaseSocket(BaseSocket&& move) noexcept;
BaseSocket& operator=(BaseSocket&& move) noexcept;
void swap(BaseSocket& other) noexcept;
BaseSocket(BaseSocket const&) = delete;
BaseSocket& operator=(BaseSocket const&) = delete;
// User can manually call close
void close();
};
// A class that can read/write to a socket
class DataSocket: public BaseSocket
{
public:
DataSocket(int socketId)
: BaseSocket(socketId)
{}
template<typename F>
std::size_t getMessageData(char* buffer, std::size_t size, F scanForEnd = [](std::size_t){return false;});
void putMessageData(char const* buffer, std::size_t size);
void putMessageClose();
};
// A class the conects to a remote machine
// Allows read/write accesses to the remote machine
class ConnectSocket: public DataSocket
{
public:
ConnectSocket(std::string const& host, int port);
};
/ A server socket that listens on a port for a connection
class ServerSocket: public BaseSocket
{
public:
ServerSocket(int port);
// An accepts waits for a connection and returns a socket
// object that can be used by the client for communication
DataSocket accept();
};
}
}
#include "Socket.tpp"
#endif
##Socket.tpp
#include "Utility.h"
#include <stdexcept>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
namespace ThorsAnvil
{
namespace Socket
{
template<typename F>
std::size_t DataSocket::getMessageData(char* buffer, std::size_t size, F scanForEnd)
{
if (getSocketId() == 0)
{
throw std::logic_error(buildErrorMessage("DataSocket::getMessage: accept called on a bad socket object (this object was moved)"));
}
std::size_t dataRead = 0;
while(dataRead < size)
{
// The inner loop handles interactions with the socket.
std::size_t get = read(getSocketId(), buffer + dataRead, size - dataRead);
if (get == static_cast<std::size_t>(-1))
{
switch(errno)
{
case EBADF:
case EFAULT:
case EINVAL:
case ENXIO:
{
// Fatal error. Programming bug
throw std::domain_error(buildErrorMessage("DataSocket::getMessageDataFromStream: read: critical error: ", strerror(errno)));
}
case EIO:
case ENOBUFS:
case ENOMEM:
{
// Resource acquisition failure or device error
throw std::runtime_error(buildErrorMessage("DataSocket::getMessageDataFromStream: read: resource failure: ", strerror(errno)));
}
case EINTR:
// TODO: Check for user interrupt flags.
// Beyond the scope of this project
// so continue normal operations.
case ETIMEDOUT:
case EAGAIN:
{
// Temporary error.
// Simply retry the read.
continue;
}
case ECONNRESET:
case ENOTCONN:
{
// Connection broken.
// Return the data we have available and exit
// as if the connection was closed correctly.
get = 0;
break;
}
default:
{
throw std::runtime_error(buildErrorMessage("DataSocket::getMessageDataFromStream: read: returned -1: ", strerror(errno)));
}
}
}
if (get == 0)
{
break;
}
dataRead += get;
if (scanForEnd(dataRead))
{
break;
}
}
return dataRead;
}
}
}
##Socket.cpp
#include "Socket.h"
#include "Utility.h"
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sstream>
#include <stdexcept>
#include <iostream>
using namespace ThorsAnvil::Socket;
BaseSocket::BaseSocket(int socketId)
: socketId(socketId)
{
if (socketId == -1)
{
throw std::runtime_error(buildErrorMessage("BaseSocket::BaseSocket: bad socket: ", strerror(errno)));
}
}
BaseSocket::~BaseSocket()
{
if (socketId == 0)
{
// This object has been closed or moved.
// So we don't need to call close.
return;
}
try
{
close();
}
catch(...)
{
// We should log this
// TODO: LOGGING CODE HERE
// If the user really want to catch close errors
// they should call close() manually and handle
// any generated exceptions. By using the
// destructor they are indicating that failures is
// an OK condition.
}
}
void BaseSocket::close()
{
if (socketId == 0)
{
throw std::logic_error(buildErrorMessage("DataSocket::close: accept called on a bad socket object (this object was moved)"));
}
while(true)
{
int state = ::close(socketId);
if (state == 0)
{
break;
}
switch(errno)
{
case EBADF: throw std::domain_error(buildErrorMessage("BaseSocket::close: close: EBADF: ", socketId, " ", strerror(errno)));
case EIO: throw std::runtime_error(buildErrorMessage("BaseSocket::close: close: EIO: ", socketId, " ", strerror(errno)));
case EINTR:
{
// TODO: Check for user interrupt flags.
// Beyond the scope of this project
// so continue normal operations.
break;
}
default: throw std::runtime_error(buildErrorMessage("BaseSocket::close: close: ???: ", socketId, " ", strerror(errno)));
}
}
socketId = 0;
}
void BaseSocket::swap(BaseSocket& other) noexcept
{
using std::swap;
swap(socketId, other.socketId);
}
BaseSocket::BaseSocket(BaseSocket&& move) noexcept
: socketId(0)
{
move.swap(*this);
}
BaseSocket& BaseSocket::operator=(BaseSocket&& move) noexcept
{
move.swap(*this);
return *this;
}
ConnectSocket::ConnectSocket(std::string const& host, int port)
: DataSocket(::socket(PF_INET, SOCK_STREAM, 0))
{
struct sockaddr_in serverAddr;
bzero((char*)&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
serverAddr.sin_addr.s_addr = inet_addr(host.c_str());
if (::connect(getSocketId(), (struct sockaddr*)&serverAddr, sizeof(serverAddr)) != 0)
{
close();
throw std::runtime_error(buildErrorMessage("ConnectSocket::ConnectSocket: connect: ", strerror(errno)));
}
}
ServerSocket::ServerSocket(int port)
: BaseSocket(::socket(PF_INET, SOCK_STREAM, 0))
{
struct sockaddr_in serverAddr;
bzero((char*)&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
serverAddr.sin_addr.s_addr = INADDR_ANY;
if (::bind(getSocketId(), (struct sockaddr *) &serverAddr, sizeof(serverAddr)) != 0)
{
close();
throw std::runtime_error(buildErrorMessage("ServerSocket::ServerSocket: bind: ", strerror(errno)));
}
if (::listen(getSocketId(), 255) != 0)
{
close();
throw std::runtime_error(buildErrorMessage("ServerSocket::ServerSocket: listen: ", strerror(errno)));
}
}
DataSocket ServerSocket::accept()
{
if (getSocketId() == 0)
{
throw std::logic_error(buildErrorMessage("ServerSocket::accept: accept called on a bad socket object (this object was moved)"));
}
struct sockaddr_storage serverStorage;
socklen_t addr_size = sizeof serverStorage;
int newSocket = ::accept(getSocketId(), (struct sockaddr*)&serverStorage, &addr_size);
if (newSocket == -1)
{
throw std::runtime_error(buildErrorMessage("ServerSocket:accept: accept: ", strerror(errno)));
}
return DataSocket(newSocket);
}
void DataSocket::putMessageData(char const* buffer, std::size_t size)
{
std::size_t dataWritten = 0;
while(dataWritten < size)
{
std::size_t put = write(getSocketId(), buffer + dataWritten, size - dataWritten);
if (put == static_cast<std::size_t>(-1))
{
switch(errno)
{
case EINVAL:
case EBADF:
case ECONNRESET:
case ENXIO:
case EPIPE:
{
// Fatal error. Programming bug
throw std::domain_error(buildErrorMessage("DataSocket::putMessageData: write: critical error: ", strerror(errno)));
}
case EDQUOT:
case EFBIG:
case EIO:
case ENETDOWN:
case ENETUNREACH:
case ENOSPC:
{
// Resource acquisition failure or device error
throw std::runtime_error(buildErrorMessage("DataSocket::putMessageData: write: resource failure: ", strerror(errno)));
}
case EINTR:
// TODO: Check for user interrupt flags.
// Beyond the scope of this project
// so continue normal operations.
case EAGAIN:
{
// Temporary error.
// Simply retry the read.
continue;
}
default:
{
throw std::runtime_error(buildErrorMessage("DataSocket::putMessageData: write: returned -1: ", strerror(errno)));
}
}
}
dataWritten += put;
}
return;
}
void DataSocket::putMessageClose()
{
if (::shutdown(getSocketId(), SHUT_WR) != 0)
{
throw std::domain_error(buildErrorMessage("HTTPProtocol::putMessageClose: shutdown: critical error: ", strerror(errno)));
}
}
std::size_t get
seems like it should bebytesRead
or something, when I seeget
it makes me think method, not variable. \$\endgroup\$