Chausette: Starting to proxy

In the previous installment, we started to echo the data we received back to where it came from. That’s all fine and dandy, but it isn’t really all that interesting. In this installment, we will set up a pair of connections and proxy between the two – which is the core of what a proxy server should do.

One of the first things we will need to do is to build upon what we did in the previous installment and add another attribute to our sockets. As you can see in the following snippet, that is really easy to do: first we rename the attribute we already have

diff --git a/bin/Episode28/Application.cpp b/bin/Episode28/Application.cpp
index eefdc44..bc67f24 100644
--- a/bin/Episode28/Application.cpp
+++ b/bin/Episode28/Application.cpp
@@ -11,7 +11,7 @@ using namespace boost;
 
 Application::Application()
 : server_(0)
-, socket_attribute_id_(Socket::alloc())
+, data_to_send_attribute_id_(Socket::alloc())
 {
 	WSADATA wsadata;
 	WSAStartup(MAKEWORD(2, 2), &wsadata);
@@ -66,14 +66,14 @@ void Application::run(const Application::Arguments &arguments)
 {
 	bool needed_to_initialize(false);
 	std::vector< char >::size_type offset(0);
-	if (socket.get(socket_attribute_id_).empty())
+	if (socket.get(data_to_send_attribute_id_).empty())
 	{
-		socket.get(socket_attribute_id_) = vector< char >(1024);
+		socket.get(data_to_send_attribute_id_) = vector< char >(1024);
 		needed_to_initialize = true;
 	}
 	else
 	{ /* already have a buffer */ }
-	vector< char > &buffer = any_cast< vector< char >& >(socket.get(socket_attribute_id_));
+	vector< char > &buffer = any_cast< vector< char >& >(socket.get(data_to_send_attribute_id_));
 	if (!needed_to_initialize && buffer.empty())
 	{
 		buffer.resize(buffer.capacity());
@@ -99,11 +99,11 @@ void Application::run(const Application::Arguments &arguments)
 
 /*virtual */void Application::onWriteReady(Socket &socket)
 {
-	if (socket.get(socket_attribute_id_).empty())
+	if (socket.get(data_to_send_attribute_id_).empty())
 	{ /* no-op */ }
 	else
 	{
-		vector< char > &buffer = any_cast< vector< char >& >(socket.get(socket_attribute_id_));
+		vector< char > &buffer = any_cast< vector< char >& >(socket.get(data_to_send_attribute_id_));
 		if (buffer.empty())
 		{ /* no-op */ }
 		else

then we add the new attribute, which will hold the socket address of the “remote” or “paired” socket.

diff --git a/bin/Episode28/Application.cpp b/bin/Episode28/Application.cpp
index bc67f24..3f3de16 100644
--- a/bin/Episode28/Application.cpp
+++ b/bin/Episode28/Application.cpp
@@ -59,7 +59,8 @@ void Application::run(const Application::Arguments &arguments)
 
 /*virtual */void Application::onNewConnection(Socket &socket)
 {
-	server_->accept(socket);
+	Socket &new_socket(server_->accept(socket));
+	remote_address_to_socket_.insert(RemoteAddressToSocket::value_type(new_socket.remote_address_, &new_socket));
 }
 
 /*virtual */void Application::onDataReady(Socket &socket)
@@ -118,3 +119,11 @@ void Application::run(const Application::Arguments &arguments)
 /*virtual */void Application::onExceptionalDataReady(Socket &socket)
 {
 }
+
+/*virtual */void Application::onCloseSocket(Socket &socket)
+{
+	RemoteAddressToSocket::iterator where(remote_address_to_socket_.find(socket.remote_address_));
+	assert(where != remote_address_to_socket_.end());
+	assert(where->second == &socket);
+	remote_address_to_socket_.erase(where);
+}
diff --git a/bin/Episode28/Application.h b/bin/Episode28/Application.h
index 30b9400..e151853 100644
--- a/bin/Episode28/Application.h
+++ b/bin/Episode28/Application.h
@@ -2,7 +2,9 @@
 #define chausette_episode28_application_h
 
 #include <string>
+#include <map>
 #include <vector>
+#include <winsock2.h>
 #include "Observer.h"
 
 class Server;
@@ -17,6 +19,15 @@ public :
 	void run(const Arguments &arguments);
 
 private :
+	struct SockAddrStorageCompare
+	{
+		bool operator()(const sockaddr_storage &lhs, const sockaddr_storage &rhs) const
+		{
+			return memcmp(&lhs, &rhs, sizeof(lhs)) < 0;
+		}
+	};
+	typedef std::map< sockaddr_storage, Socket*, SockAddrStorageCompare > RemoteAddressToSocket;
+
 	Application(const Application&);
 	Application& operator=(const Application&);
 
@@ -24,10 +35,12 @@ private :
 	virtual void onDataReady(Socket &socket);
 	virtual void onWriteReady(Socket &socket);
 	virtual void onExceptionalDataReady(Socket &socket);
+	virtual void onCloseSocket(Socket &socket);
 
 	bool done_;
 	Server *server_;
-	unsigned int socket_attribute_id_;
+	unsigned int data_to_send_attribute_id_;
+	RemoteAddressToSocket remote_address_to_socket_;
 };
 
 #endif
diff --git a/bin/Episode28/Observer.h b/bin/Episode28/Observer.h
index a8d6125..f0d4c4a 100644
--- a/bin/Episode28/Observer.h
+++ b/bin/Episode28/Observer.h
@@ -12,6 +12,7 @@ public :
 	virtual void onDataReady(Socket &socket) = 0;
 	virtual void onWriteReady(Socket &socket) = 0;
 	virtual void onExceptionalDataReady(Socket &socket) = 0;
+	virtual void onCloseSocket(Socket &socket) = 0;
 
 private :
 	Observer(const Observer&);
diff --git a/bin/Episode28/Server.cpp b/bin/Episode28/Server.cpp
index 7aa52e8..5871b18 100644
--- a/bin/Episode28/Server.cpp
+++ b/bin/Episode28/Server.cpp
@@ -174,16 +174,18 @@ void Server::detach(Observer *observer)
 	{ /* not found, not a problem */ }
 }
 
-void Server::accept(Socket &socket)
+Socket& Server::accept(Socket &socket)
 {
 	Socket new_socket(accept_(socket));
 	sockets_.push_back(new_socket);
+	return sockets_.back();
 }
 
 void Server::reject(Socket &socket)
 {
 	Socket new_socket(accept_(socket));
 	closesocket(new_socket.fd_);
+	// don't need to notify in this case - the observer has never seen the new socket
 }
 
 void Server::read(Socket &socket, char *buffer, unsigned int *buffer_size)
@@ -194,6 +196,10 @@ void Server::read(Socket &socket, char *buffer, unsigned int *buffer_size)
 		if (recv_result == 0) // EOF
 		{
 			closesocket(socket.fd_);
+			for (Observers::iterator observer(observers_.begin()); observer != observers_.end(); ++observer)
+			{
+				(*observer)->onCloseSocket(socket);
+			}
 			socket.fd_ = -1;
 		}
 		else if (recv_result < 0)
@@ -235,8 +241,11 @@ void Server::write(Socket &socket, const char *buffer, unsigned int *buffer_size
 
 Socket Server::accept_(Socket &socket)
 {
-	Socket new_socket(::accept(socket.fd_, 0, 0), socket.fd_);
+	sockaddr_storage remote_address;
+	int remote_address_size(sizeof(remote_address));
+	Socket new_socket(::accept(socket.fd_, (sockaddr*)&remote_address, &remote_address_size), socket.fd_);
 	socket.read_avail_ = false;
+	new_socket.remote_address_ = remote_address;
 	return new_socket;
 }
 
diff --git a/bin/Episode28/Server.h b/bin/Episode28/Server.h
index 361a3d3..f8d1131 100644
--- a/bin/Episode28/Server.h
+++ b/bin/Episode28/Server.h
@@ -18,7 +18,7 @@ public :
 	void attach(Observer *observer);
 	void detach(Observer *observer);
 
-	void accept(Socket &socket);
+	Socket& accept(Socket &socket);
 	void reject(Socket &socket);
 
 	void read(Socket &socket, char *buffer, unsigned int *buffer_size);
diff --git a/bin/Episode28/Socket.h b/bin/Episode28/Socket.h
index 26082ae..1fe451d 100644
--- a/bin/Episode28/Socket.h
+++ b/bin/Episode28/Socket.h
@@ -11,16 +11,19 @@ struct Socket : private Vlinder::Chausette::Core::Attributes
 		, read_avail_(false)
 		, write_avail_(false)
 		, exc_avail_(false)
-	{ /* no-op */ }
+	{
+		memset(&remote_address_, 0, sizeof(remote_address_));
+	}
 
-	using Vlinder::Chausette::Core::Attributes::alloc;
-	using Vlinder::Chausette::Core::Attributes::get;
+	using Vlinder::Chausette::Core::Attributes::alloc;
+	using Vlinder::Chausette::Core::Attributes::get;
 
 	int fd_;
 	int parent_fd_;
 	bool read_avail_;
 	bool write_avail_;
 	bool exc_avail_;
+	sockaddr_storage remote_address_;
 };
 
 #endif

Note that we now need to know when a socket is closed, to we can do the appropriate clean-up. We also need to know the remote addresses of incoming connections, which we store in the new remote_address_ member and accept now returns a reference to the accepted socket, so we can keep that reference and associate it with another socket later on. The following snippet of code will finish the deal:

diff --git a/bin/Episode28/Application.cpp b/bin/Episode28/Application.cpp
index 3f3de16..70ba6e4 100644
--- a/bin/Episode28/Application.cpp
+++ b/bin/Episode28/Application.cpp
@@ -12,6 +12,8 @@ using namespace boost;
 Application::Application()
 : server_(0)
 , data_to_send_attribute_id_(Socket::alloc())
+, target_address_attribute_id_(Socket::alloc())
+, un_paired_socket_(0)
 {
 	WSADATA wsadata;
 	WSAStartup(MAKEWORD(2, 2), &wsadata);
@@ -61,20 +63,29 @@ void Application::run(const Application::Arguments &arguments)
 {
 	Socket &new_socket(server_->accept(socket));
 	remote_address_to_socket_.insert(RemoteAddressToSocket::value_type(new_socket.remote_address_, &new_socket));
+	pairSocket(new_socket);
 }
 
 /*virtual */void Application::onDataReady(Socket &socket)
 {
+	vector< char > temp; // in case the socket is un-paired
 	bool needed_to_initialize(false);
-	std::vector< char >::size_type offset(0);
-	if (socket.get(data_to_send_attribute_id_).empty())
+	vector< char >::size_type offset(0);
+	Socket *partner((&socket == un_paired_socket_) ? 0 : remote_address_to_socket_[any_cast< sockaddr_storage >(socket.get(target_address_attribute_id_))]);
+	if (partner &&
+		partner->get(data_to_send_attribute_id_).empty())
 	{
-		socket.get(data_to_send_attribute_id_) = vector< char >(1024);
+		partner->get(data_to_send_attribute_id_) = vector< char >(1024);
 		needed_to_initialize = true;
 	}
-	else
+	else if (partner)
 	{ /* already have a buffer */ }
-	vector< char > &buffer = any_cast< vector< char >& >(socket.get(data_to_send_attribute_id_));
+	else
+	{
+		temp.resize(1024);
+		needed_to_initialize = true;
+	}
+	vector< char > &buffer = partner ? any_cast< vector< char >& >(partner->get(data_to_send_attribute_id_)) : temp;
 	if (!needed_to_initialize && buffer.empty())
 	{
 		buffer.resize(buffer.capacity());
@@ -82,20 +93,37 @@ void Application::run(const Application::Arguments &arguments)
 	else if (!needed_to_initialize)
 	{
 		offset = buffer.size();
-		buffer.resize(offset + 1024);
+		if (buffer.capacity() <= offset + 1024)
+		{
+			buffer.resize(offset + 1024);
+		}
+		else
+		{
+			buffer.resize(buffer.capacity());
+		}
 	}
 	else
 	{ /* needed to initialize - so no need to account for data already in the buffer */ }
 	unsigned int data_read(buffer.size() - offset);
 	char *read_ptr(&buffer[0]);
 	read_ptr += offset;
-	server_->read(socket, read_ptr, &data_read);
-	buffer.resize(offset + (data_read * 4));
-	std::copy(buffer.begin() + offset, buffer.begin() + offset + data_read, buffer.begin() + offset + data_read);
-	std::copy(buffer.begin() + offset, buffer.begin() + offset + (data_read * 2), buffer.begin() + offset + (data_read * 2));
-	unsigned int data_written(buffer.size());
-	server_->write(socket, &buffer[0], &data_written);
-	buffer.erase(buffer.begin(), buffer.begin() + data_written);
+	try
+	{
+		server_->read(socket, read_ptr, &data_read);
+		buffer.resize(offset + data_read);
+		unsigned int data_written(buffer.size());
+		if (partner)
+		{
+			server_->write(*partner, &buffer[0], &data_written);
+		}
+		else
+		{ /* no partner to send data to */ }
+		buffer.erase(buffer.begin(), buffer.begin() + data_written);
+	}
+	catch (const Server::NetworkError&)
+	{
+		// ignore this for now: the socket will have been dealt with but this is no reason for us to crash.
+	}
 }
 
 /*virtual */void Application::onWriteReady(Socket &socket)
@@ -126,4 +154,36 @@ void Application::run(const Application::Arguments &arguments)
 	assert(where != remote_address_to_socket_.end());
 	assert(where->second == &socket);
 	remote_address_to_socket_.erase(where);
+	unpairSocket(socket);
+}
+
+void Application::pairSocket(Socket &socket)
+{
+	if (un_paired_socket_)
+	{
+		un_paired_socket_->get(target_address_attribute_id_) = socket.remote_address_;
+		socket.get(target_address_attribute_id_) = un_paired_socket_->remote_address_;
+		un_paired_socket_ = 0;
+	}
+	else
+	{
+		un_paired_socket_ = &socket;
+	}
+}
+
+void Application::unpairSocket(Socket &socket)
+{
+	// find the socket this one was paired to
+	if (un_paired_socket_ == &socket)
+	{
+		un_paired_socket_ = 0;
+	}
+	else
+	{
+		assert(!socket.get(target_address_attribute_id_).empty());
+		sockaddr_storage target_address(any_cast< sockaddr_storage >(socket.get(target_address_attribute_id_)));
+		Socket *other_socket(remote_address_to_socket_[target_address]);
+		other_socket->get(target_address_attribute_id_) = any();
+		pairSocket(*other_socket);
+	}
 }
diff --git a/bin/Episode28/Application.h b/bin/Episode28/Application.h
index e151853..4f478da 100644
--- a/bin/Episode28/Application.h
+++ b/bin/Episode28/Application.h
@@ -36,11 +36,15 @@ private :
 	virtual void onWriteReady(Socket &socket);
 	virtual void onExceptionalDataReady(Socket &socket);
 	virtual void onCloseSocket(Socket &socket);
+	void pairSocket(Socket &socket);
+	void unpairSocket(Socket &socket);
 
 	bool done_;
 	Server *server_;
 	unsigned int data_to_send_attribute_id_;
+	unsigned int target_address_attribute_id_;
 	RemoteAddressToSocket remote_address_to_socket_;
+	Socket *un_paired_socket_;
 };
 
 #endif
diff --git a/bin/Episode28/Server.cpp b/bin/Episode28/Server.cpp
index 5871b18..edc60d9 100644
--- a/bin/Episode28/Server.cpp
+++ b/bin/Episode28/Server.cpp
@@ -2,8 +2,11 @@
 #include <algorithm>
 #include <functional>
 #include <ctime>
+#include <stdexcept>
 #include "Observer.h"
 
+using namespace std;
+
 Server::Server(sockaddr_storage address)
 : address_(address)
 , server_fd_(-1)
@@ -15,7 +18,7 @@ Server::Server(sockaddr_storage address)
 	}
 	else
 	{ /* all is well */ }
-	if (bind(server_fd_, (const sockaddr*)&address_, sizeof(address_)) == -1)
+	if (::bind(server_fd_, (const sockaddr*)&address_, sizeof(address_)) == -1)
 	{
 		throw "Something more eloquent here";
 	}
@@ -132,23 +135,23 @@ void Server::update(unsigned int timeout/*in ms*/)
 	int highest_fd(server_fd_);
 	fd_set read_fds;
 	FD_ZERO(&read_fds);
-	std::for_each(sockets_.begin(), sockets_.end(), Functor(read_fds, &Socket::read_avail_, highest_fd));
+	for_each(sockets_.begin(), sockets_.end(), Functor(read_fds, &Socket::read_avail_, highest_fd));
 	fd_set write_fds;
 	FD_ZERO(&write_fds);
-	std::for_each(sockets_.begin(), sockets_.end(), Functor(write_fds, &Socket::write_avail_, highest_fd));
+	for_each(sockets_.begin(), sockets_.end(), Functor(write_fds, &Socket::write_avail_, highest_fd));
 	fd_set exc_fds;
 	FD_ZERO(&exc_fds);
-	std::for_each(sockets_.begin(), sockets_.end(), Functor(exc_fds, &Socket::exc_avail_, highest_fd));
+	for_each(sockets_.begin(), sockets_.end(), Functor(exc_fds, &Socket::exc_avail_, highest_fd));
 	timeval to;
 	to.tv_sec = timeout / 1000;
 	to.tv_usec = (timeout % 1000) * 1000;
 	int select_result(select(highest_fd + 1, &read_fds, &write_fds, &exc_fds, &to));
 	if (select_result > 0)
 	{
-		std::for_each(sockets_.begin(), sockets_.end(), Notifier< Predicate< true >, Observers >(read_fds, &Socket::read_avail_, observers_, &Observer::onNewConnection));
-		std::for_each(sockets_.begin(), sockets_.end(), Notifier< Predicate<>, Observers >(read_fds, &Socket::read_avail_, observers_, &Observer::onDataReady));
-		std::for_each(sockets_.begin(), sockets_.end(), Notifier< Predicate<>, Observers >(write_fds, &Socket::write_avail_, observers_, &Observer::onWriteReady));
-		std::for_each(sockets_.begin(), sockets_.end(), Notifier< Predicate<>, Observers >(exc_fds, &Socket::exc_avail_, observers_, &Observer::onExceptionalDataReady));
+		for_each(sockets_.begin(), sockets_.end(), Notifier< Predicate< true >, Observers >(read_fds, &Socket::read_avail_, observers_, &Observer::onNewConnection));
+		for_each(sockets_.begin(), sockets_.end(), Notifier< Predicate<>, Observers >(read_fds, &Socket::read_avail_, observers_, &Observer::onDataReady));
+		for_each(sockets_.begin(), sockets_.end(), Notifier< Predicate<>, Observers >(write_fds, &Socket::write_avail_, observers_, &Observer::onWriteReady));
+		for_each(sockets_.begin(), sockets_.end(), Notifier< Predicate<>, Observers >(exc_fds, &Socket::exc_avail_, observers_, &Observer::onExceptionalDataReady));
 	}
 	else if (select_result < 0)
 	{
@@ -165,7 +168,7 @@ void Server::attach(Observer *observer)
 
 void Server::detach(Observer *observer)
 {
-	Observers::iterator where(std::find(observers_.begin(), observers_.end(), observer));
+	Observers::iterator where(find(observers_.begin(), observers_.end(), observer));
 	if (where != observers_.end())
 	{
 		observers_.erase(where);
@@ -188,6 +191,17 @@ void Server::reject(Socket &socket)
 	// don't need to notify in this case - the observer has never seen the new socket
 }
 
+void Server::close(Socket &socket)
+{
+	closesocket(socket.fd_);
+	for (Observers::iterator observer(observers_.begin()); observer != observers_.end(); ++observer)
+	{
+		(*observer)->onCloseSocket(socket);
+	}
+	socket.fd_ = -1;
+	// it will be cleaned up on the next round of select
+}
+
 void Server::read(Socket &socket, char *buffer, unsigned int *buffer_size)
 {
 	if (socket.read_avail_)
@@ -195,16 +209,65 @@ void Server::read(Socket &socket, char *buffer, unsigned int *buffer_size)
 		int recv_result(::recv(socket.fd_, buffer, *buffer_size, 0));
 		if (recv_result == 0) // EOF
 		{
-			closesocket(socket.fd_);
-			for (Observers::iterator observer(observers_.begin()); observer != observers_.end(); ++observer)
-			{
-				(*observer)->onCloseSocket(socket);
-			}
-			socket.fd_ = -1;
+			close(socket);
 		}
 		else if (recv_result < 0)
 		{
-			throw "something more eloquent here";
+			switch (WSAGetLastError())
+			{
+			case WSANOTINITIALISED :
+				throw logic_error("A successful WSAStartup call must occur before using this function");
+			case WSAENETDOWN :
+				close(socket);
+				throw NetworkDown("The network subsystem has failed");
+			case WSAEACCES :
+				close(socket);
+				throw WrongAddressType("The requested address is a broadcast address");
+			case WSAEINTR :
+				// all calls should be non-blocking
+				throw logic_error("A blocking Windows Sockets 1.1 call was canceled through WSACancelBlockingCall");
+			case WSAEINPROGRESS :
+				// all calls should be non-blocking
+				throw logic_error("A blocking Windows Sockets 1.1 call is in progress, or the service provider is still processing a callback function");
+			case WSAEFAULT :
+				throw logic_error("The buf parameter is not completely contained in a valid part of the user address space");
+			case WSAENETRESET :
+				close(socket);
+				throw KeepAliveFailed("The connection has been broken due to the keep-alive activity detecting a failure while the operation was in progress");
+			case WSAENOBUFS :
+				throw bad_alloc("No buffer space is available");
+			case WSAENOTCONN :
+				close(socket);
+				throw SocketNotConnected("The socket is not connected");
+			case WSAENOTSOCK :
+				throw logic_error("The descriptor is not a socket");
+			case WSAEOPNOTSUPP :
+				throw logic_error("MSG_OOB was specified, but the socket is not stream-style such as type SOCK_STREAM, OOB data is not supported in the communication domain associated with this socket, or the socket is unidirectional and supports only receive operations");
+			case WSAESHUTDOWN :
+				throw logic_error("The socket has been shut down; it is not possible to send on a socket after shutdown has been invoked with how set to SD_SEND or SD_BOTH");
+			case WSAEWOULDBLOCK :		// The socket is marked as nonblocking and the requested operation would block.
+				// not really an error
+				*buffer_size = 0;
+				break;
+			case WSAEMSGSIZE :
+				throw logic_error("The socket is message oriented, and the message is larger than the maximum supported by the underlying transport");
+			case WSAEHOSTUNREACH :
+				close(socket);
+				throw HostUnreachable("The remote host cannot be reached from this host at this time");
+			case WSAEINVAL :
+				throw logic_error("The socket has not been bound with bind, or an unknown flag was specified, or MSG_OOB was specified for a socket with SO_OOBINLINE enabled");
+			case WSAECONNABORTED :
+				close(socket);
+				throw ConnectionAborted("The virtual circuit was terminated due to a time-out or other failure");
+			case WSAECONNRESET :
+				close(socket);
+				throw ConnectionReset("The virtual circuit was reset by the remote side executing a hard or abortive close. For UDP sockets, the remote host was unable to deliver a previously sent UDP datagram and responded with a \"Port Unreachable\" ICMP packet");
+			case WSAETIMEDOUT :
+				close(socket);
+				throw ConnectionDropped("The connection has been dropped, because of a network failure or because the system on the other end went down without notice");
+			default :
+				throw logic_error("Unknown error");
+			}
 		}
 		else
 		{
diff --git a/bin/Episode28/Server.h b/bin/Episode28/Server.h
index f8d1131..fa616e3 100644
--- a/bin/Episode28/Server.h
+++ b/bin/Episode28/Server.h
@@ -5,11 +5,33 @@
 #include <list>
 #include "Socket.h"
 #include "config.h"
+#include "exceptions/Exception.h"
 
 class Observer;
 class Server
 {
 public :
+	enum Errors {
+		wrong_address_type__,
+		network_error__,
+		network_down__,
+		keep_alive_failed__,
+		socket_not_connected__,
+		host_unreachable__,
+		connection_aborted__,
+		connection_reset__,
+		connection_dropped__,
+	};
+	typedef Vlinder::Exceptions::Exception< std::runtime_error, Errors, wrong_address_type__ > WrongAddressType;		// user error or logic error, 
+	typedef Vlinder::Exceptions::Exception< std::runtime_error, Errors, network_error__ > NetworkError;
+	typedef Vlinder::Exceptions::Exception< NetworkError, Errors, network_down__ > NetworkDown;
+	typedef Vlinder::Exceptions::Exception< NetworkError, Errors, keep_alive_failed__ > KeepAliveFailed;	
+	typedef Vlinder::Exceptions::Exception< NetworkError, Errors, socket_not_connected__ > SocketNotConnected;
+	typedef Vlinder::Exceptions::Exception< NetworkError, Errors, host_unreachable__ > HostUnreachable;
+	typedef Vlinder::Exceptions::Exception< NetworkError, Errors, connection_aborted__ > ConnectionAborted;
+	typedef Vlinder::Exceptions::Exception< NetworkError, Errors, connection_reset__ > ConnectionReset;
+	typedef Vlinder::Exceptions::Exception< NetworkError, Errors, connection_reset__ > ConnectionDropped;
+
 	Server(sockaddr_storage address);
 	~Server();
 
@@ -20,6 +42,7 @@ public :
 
 	Socket& accept(Socket &socket);
 	void reject(Socket &socket);
+	void close(Socket &socket);
 
 	void read(Socket &socket, char *buffer, unsigned int *buffer_size);
 	void write(Socket &socket, const char *buffer, unsigned int *buffer_size);
diff --git a/lib/core/Attributes.cpp b/lib/core/Attributes.cpp
index dbfafa7..1fee546 100644
--- a/lib/core/Attributes.cpp
+++ b/lib/core/Attributes.cpp
@@ -2,44 +2,44 @@
 #include <new>
 #include <stdexcept>
 
-namespace Vlinder { namespace Chausette { namespace Core {
-	/*static */unsigned int Attributes::next_id__(0);
-
-	/*static */unsigned int Attributes::alloc()
-	{
-		/* note that this is not thread-safe! */
-		if (next_id__ == id_max__)
-		{
-			throw std::bad_alloc();
-		}
-		else
-		{
-			return next_id__++;
-		}
-	}
-
-	boost::any& Attributes::get(unsigned int index)
-	{
-		if (index < next_id__)
-		{
-			return attributes_[index];
-		}
-		else
-		{
-			throw std::logic_error("Trying to access unallocated attribute");
-		}
-	}
-
-	const boost::any& Attributes::get(unsigned int index) const
-	{
-		if (index < next_id__)
-		{
-			return attributes_[index];
-		}
-		else
-		{
-			throw std::logic_error("Trying to access unallocated attribute");
-		}
-	}
+namespace Vlinder { namespace Chausette { namespace Core {
+	/*static */unsigned int Attributes::next_id__(0);
+
+	/*static */unsigned int Attributes::alloc()
+	{
+		/* note that this is not thread-safe! */
+		if (next_id__ == id_max__)
+		{
+			throw std::bad_alloc();
+		}
+		else
+		{
+			return next_id__++;
+		}
+	}
+
+	boost::any& Attributes::get(unsigned int index)
+	{
+		if (index < next_id__)
+		{
+			return attributes_[index];
+		}
+		else
+		{
+			throw std::logic_error("Trying to access unallocated attribute");
+		}
+	}
+
+	const boost::any& Attributes::get(unsigned int index) const
+	{
+		if (index < next_id__)
+		{
+			return attributes_[index];
+		}
+		else
+		{
+			throw std::logic_error("Trying to access unallocated attribute");
+		}
+	}
 }}}
 
diff --git a/lib/core/Attributes.h b/lib/core/Attributes.h
index 288f0f7..f15ace2 100644
--- a/lib/core/Attributes.h
+++ b/lib/core/Attributes.h
@@ -1,24 +1,24 @@
 #ifndef vlinder_chausette_core_attributes_h
 #define vlinder_chausette_core_attributes_h
 
-#include "Details/prologue.h"
-#include <boost/any.hpp>
-
-namespace Vlinder { namespace Chausette { namespace Core {
-	class VLINDER_CHAUSETTE_CORE_API Attributes
-	{
-	public :
-		static unsigned int alloc();
-
-		boost::any& get(unsigned int index);
-		const boost::any& get(unsigned int index) const;
-
-	private :
-		static const unsigned int id_max__ = 48;
-
-		boost::any attributes_[id_max__];
-		static unsigned int next_id__;
-	};
+#include "Details/prologue.h"
+#include <boost/any.hpp>
+
+namespace Vlinder { namespace Chausette { namespace Core {
+	class VLINDER_CHAUSETTE_CORE_API Attributes
+	{
+	public :
+		static unsigned int alloc();
+
+		boost::any& get(unsigned int index);
+		const boost::any& get(unsigned int index) const;
+
+	private :
+		static const unsigned int id_max__ = 48;
+
+		boost::any attributes_[id_max__];
+		static unsigned int next_id__;
+	};
 }}}
 
 #endif

Most of the code in onDataReady speaks for itself: if a given socket doesn’t have an associated socket, the data from the socket is read and thrown away. If it does have an associated socket, it will have the remote address of that socket in its attributes, so we can find it (and call it its “partner” in the code). We then take the associated vector from the partner socket and use it as a buffer, into which we read all of our data. Server::write is already nice enough to not try to write on a socket that’s not known to be ready for writing, so we can just call it with the partner socket and its buffer.

Associating sockets with each other is done in the pairSocket method, which simply checks if there’s another un-paired socket. If so, the two are paired. If not, the to-be-paired socket is put on hold for the next to-be-paired socket.

This means the proxying we do here is more or less random: sockets are paired with new sockets if they lose their current partner and another socket is waiting, if they connect to the server and another socket is waiting to be paired, or if they were waiting for a partner and another socket loses theirs, or connects to the server.

The unpairSocket method takes care of leaving sockets and calls pairSocket if the leaving socket wasn’t un-paired in the first place.

Perhaps you’ve also noticed that I’ve added a lot of error handling code – treating all of the possible error codes. That’s because we now need to handle those errors correctly, as we want to keep the server alive if a client disconnects. The code isn’t perfect yet, of course, but it may be worth taking a look at, to get a feel of how error handling will work in this setting.

Specializing the map

The standard map can be specialized to better suit our needs. In this case, I’ve added a comparator to the map, to allow us to compare instances of sockaddr_storage and use it as a key in the map. Note that, because this is a template specialization of the map class, the comparator’s type is now a part of the map’s type. This means it’s also part of any of the nested types of the map, such as its iterator types.

Without this specialization, it would not have been possible to use sockaddr_storage as a key in the map, because there’s no less-than operator for the sockaddr_storage type.

About rlc

Software Analyst in embedded systems and C++, C and VHDL developer, I specialize in security, communications protocols and time synchronization, and am interested in concurrency, generic meta-programming and functional programming and their practical applications. I take a pragmatic approach to project management, focusing on the management of risk and scope. I have over two decades of experience as a software professional and a background in science.
This entry was posted in C++ for the self-taught and tagged . Bookmark the permalink.