diff --git b/CMakeLists.txt a/CMakeLists.txt
new file mode 100644
index 0000000..1aa89c0
--- /dev/null
+++ a/CMakeLists.txt
@@ -0,0 +1,17 @@
+cmake_minmum_required(VERSION 3.20)
+project(osdev-socket-cpp)
+
+LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
+
+# ===========================================================================
+# == Include build information
+include(osdevversion)
+include(projectheader)
+
+project_header(osdev-socket-cpp)
+
+include(compiler)
+
+add_subdirectory(src)
+add_subdirectory(examples)
+add_subdirectory(tests)
diff --git b/examples/linux/canrecv.cpp a/examples/linux/canrecv.cpp
new file mode 100644
index 0000000..02675c4
--- /dev/null
+++ a/examples/linux/canrecv.cpp
@@ -0,0 +1,96 @@
+// canrecv.cpp
+//
+// Linux SoxketCAN reader example.
+//
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2021 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include <iostream>
+#include <iomanip>
+#include <string>
+#include <chrono>
+#include <thread>
+#include "sockpp/can_socket.h"
+#include "sockpp/can_frame.h"
+#include "sockpp/version.h"
+
+#include <net/if.h>
+#include <sys/ioctl.h>
+
+using namespace std;
+
+// The clock to use to get time and pace the app.
+using sysclock = chrono::system_clock;
+
+// --------------------------------------------------------------------------
+
+int main(int argc, char* argv[])
+{
+	cout << "Sample SocketCAN writer for 'sockpp' "
+		<< sockpp::SOCKPP_VERSION << endl;
+
+	string canIface = (argc > 1) ? argv[1] : "can0";
+	canid_t canID = (argc > 2) ? atoi(argv[2]) : 0x20;
+
+	sockpp::socket_initializer sockInit;
+
+	sockpp::can_address addr(canIface);
+	sockpp::can_socket sock(addr);
+
+	if (!sock) {
+		cerr << "Error binding to the CAN interface " << canIface << "\n\t"
+			<< sock.last_error_str() << endl;
+		return 1;
+	}
+
+	cout << "Created CAN socket on " << sock.address() << endl;
+	time_t t = sysclock::to_time_t(sysclock::now());
+
+	cout.setf(ios::fixed, ios::floatfield);
+	cout << setfill('0');
+
+	while (true) {
+		sockpp::can_frame frame;
+		sock.recv(&frame);
+		auto t = sock.last_frame_timestamp();
+
+		cout << t << "  ";
+		for (uint8_t i=0; i<frame.can_dlc; ++i)
+			cout << hex << uppercase << setw(2) << unsigned(frame.data[i]) << " ";
+		cout << endl;
+	}
+
+	return (!sock) ? 1 : 0;
+}
diff --git b/examples/linux/cantime.cpp a/examples/linux/cantime.cpp
new file mode 100644
index 0000000..91dcfa3
--- /dev/null
+++ a/examples/linux/cantime.cpp
@@ -0,0 +1,98 @@
+// cantime.cpp
+//
+// Linux SoxketCAN writer example.
+//
+// This writes the 1-sec, 32-bit, Linux time_t value to the CAN bus each
+// time it ticks. This is a simple (though not overly precise) way to
+// synchronize the time for nodes on the bus
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2021 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include <iostream>
+#include <string>
+#include <chrono>
+#include <thread>
+#include "sockpp/can_socket.h"
+#include "sockpp/can_frame.h"
+#include "sockpp/version.h"
+
+#include <net/if.h>
+#include <sys/ioctl.h>
+
+using namespace std;
+
+// The clock to use to get time and pace the app.
+using sysclock = chrono::system_clock;
+
+// --------------------------------------------------------------------------
+
+int main(int argc, char* argv[])
+{
+	cout << "Sample SocketCAN writer for 'sockpp' "
+		<< sockpp::SOCKPP_VERSION << endl;
+
+	string canIface = (argc > 1) ? argv[1] : "can0";
+	canid_t canID = (argc > 2) ? atoi(argv[2]) : 0x20;
+
+	sockpp::socket_initializer sockInit;
+
+	sockpp::can_address addr(canIface);
+	sockpp::can_socket sock(addr);
+
+	if (!sock) {
+		cerr << "Error binding to the CAN interface " << canIface << "\n\t"
+			<< sock.last_error_str() << endl;
+		return 1;
+	}
+
+	cout << "Created CAN socket on " << sock.address() << endl;
+	time_t t = sysclock::to_time_t(sysclock::now());
+
+	while (true) {
+		// Sleep until the clock ticks to the next second
+		this_thread::sleep_until(sysclock::from_time_t(t+1));
+
+		// Re-read the time in case we fell behind
+		t = sysclock::to_time_t(sysclock::now());
+
+		// Write the time to the CAN bus as a 32-bit int
+		auto nt = uint32_t(t);
+
+		sockpp::can_frame frame { canID, &nt, sizeof(nt) };
+		sock.send(frame);
+	}
+
+	return (!sock) ? 1 : 0;
+}
diff --git b/examples/tcp/tcp6echo.cpp a/examples/tcp/tcp6echo.cpp
new file mode 100644
index 0000000..db045ef
--- /dev/null
+++ a/examples/tcp/tcp6echo.cpp
@@ -0,0 +1,99 @@
+// tcpecho.cpp
+//
+// Simple TCP echo client
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2014-2017 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include <iostream>
+#include <string>
+#include "sockpp/tcp6_connector.h"
+#include "sockpp/version.h"
+
+using namespace std;
+using namespace std::chrono;
+
+// --------------------------------------------------------------------------
+
+int main(int argc, char* argv[])
+{
+	cout << "Sample IPv6 TCP echo client for 'sockpp' "
+		<< sockpp::SOCKPP_VERSION << '\n' << endl;
+
+	std::string host = (argc > 1) ? argv[1] : "::1";
+	in_port_t port = (argc > 2) ? atoi(argv[2]) : 12345;
+
+	sockpp::socket_initializer sockInit;
+
+	// Implicitly creates an inet6_address from {host,port}
+	// and then tries the connection.
+
+	sockpp::tcp6_connector conn({host, port});
+	if (!conn) {
+		cerr << "Error connecting to server at "
+			<< sockpp::inet6_address(host, port)
+			<< "\n\t" << conn.last_error_str() << endl;
+		return 1;
+	}
+
+	cout << "Created a connection from " << conn.address() << endl;
+
+    // Set a timeout for the responses
+    if (!conn.read_timeout(seconds(5))) {
+        cerr << "Error setting timeout on TCP stream: "
+                << conn.last_error_str() << endl;
+    }
+
+	string s, sret;
+	while (getline(cin, s) && !s.empty()) {
+		if (conn.write(s) != ssize_t(s.length())) {
+			cerr << "Error writing to the TCP stream: "
+				<< conn.last_error_str() << endl;
+			break;
+		}
+
+		sret.resize(s.length());
+		ssize_t n = conn.read_n(&sret[0], s.length());
+
+		if (n != ssize_t(s.length())) {
+			cerr << "Error reading from TCP stream: "
+				<< conn.last_error_str() << endl;
+			break;
+		}
+
+		cout << sret << endl;
+	}
+
+	return (!conn) ? 1 : 0;
+}
diff --git b/examples/tcp/tcp6echosvr.cpp a/examples/tcp/tcp6echosvr.cpp
new file mode 100644
index 0000000..8352207
--- /dev/null
+++ a/examples/tcp/tcp6echosvr.cpp
@@ -0,0 +1,109 @@
+// tcp6echosvr.cpp
+//
+// A multi-threaded TCP v6 echo server for sockpp library.
+// This is a simple thread-per-connection TCP server for IPv6.
+//
+// USAGE:
+//  	tcp6echosvr [port]
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include <iostream>
+#include <thread>
+#include "sockpp/tcp6_acceptor.h"
+#include "sockpp/version.h"
+
+using namespace std;
+
+// --------------------------------------------------------------------------
+// The thread function. This is run in a separate thread for each socket.
+// Ownership of the socket object is transferred to the thread, so when this
+// function exits, the socket is automatically closed.
+
+void run_echo(sockpp::tcp6_socket sock)
+{
+	ssize_t n;
+	char buf[512];
+
+    while ((n = sock.read(buf, sizeof(buf))) > 0)
+		sock.write_n(buf, n);
+
+	cout << "Connection closed from " << sock.peer_address() << endl;
+}
+
+// --------------------------------------------------------------------------
+// The main thread runs the TCP port acceptor. Each time a connection is
+// made, a new thread is spawned to handle it, leaving this main thread to
+// immediately wait for the next connection.
+
+int main(int argc, char* argv[])
+{
+	cout << "Sample IPv6 TCP echo server for 'sockpp' "
+		<< sockpp::SOCKPP_VERSION << '\n' << endl;
+
+	in_port_t port = (argc > 1) ? atoi(argv[1]) : 12345;
+
+	sockpp::socket_initializer sockInit;
+	sockpp::tcp6_acceptor acc(port);
+
+	if (!acc) {
+		cerr << "Error creating the acceptor: " << acc.last_error_str() << endl;
+		return 1;
+	}
+	cout << "Awaiting connections on port " << port << "..." << endl;
+
+	while (true) {
+		sockpp::inet6_address peer;
+
+		// Accept a new client connection
+		sockpp::tcp6_socket sock = acc.accept(&peer);
+		cout << "Received a connection request from " << peer << endl;
+
+		if (!sock) {
+			cerr << "Error accepting incoming connection: " 
+				<< acc.last_error_str() << endl;
+		}
+		else {
+			// Create a thread and transfer the new stream to it.
+			thread thr(run_echo, std::move(sock));
+			thr.detach();
+		}
+	}
+
+	return 0;
+}
+
+
+
diff --git b/examples/tcp/tcpecho.cpp a/examples/tcp/tcpecho.cpp
new file mode 100644
index 0000000..03e0df8
--- /dev/null
+++ a/examples/tcp/tcpecho.cpp
@@ -0,0 +1,97 @@
+// tcpecho.cpp
+//
+// Simple TCP echo client
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2014-2017 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include <iostream>
+#include <string>
+#include "sockpp/tcp_connector.h"
+#include "sockpp/version.h"
+
+using namespace std;
+using namespace std::chrono;
+
+int main(int argc, char* argv[])
+{
+	cout << "Sample TCP echo client for 'sockpp' "
+		<< sockpp::SOCKPP_VERSION << '\n' << endl;
+
+	string host = (argc > 1) ? argv[1] : "localhost";
+	in_port_t port = (argc > 2) ? atoi(argv[2]) : 12345;
+
+	sockpp::socket_initializer sockInit;
+
+	// Implicitly creates an inet_address from {host,port}
+	// and then tries the connection.
+
+	sockpp::tcp_connector conn({host, port});
+	if (!conn) {
+		cerr << "Error connecting to server at "
+			<< sockpp::inet_address(host, port)
+			<< "\n\t" << conn.last_error_str() << endl;
+		return 1;
+	}
+
+	cout << "Created a connection from " << conn.address() << endl;
+
+    // Set a timeout for the responses
+    if (!conn.read_timeout(seconds(5))) {
+        cerr << "Error setting timeout on TCP stream: "
+                << conn.last_error_str() << endl;
+    }
+
+	string s, sret;
+	while (getline(cin, s) && !s.empty()) {
+		if (conn.write(s) != ssize_t(s.length())) {
+			cerr << "Error writing to the TCP stream: "
+				<< conn.last_error_str() << endl;
+			break;
+		}
+
+		sret.resize(s.length());
+		ssize_t n = conn.read_n(&sret[0], s.length());
+
+		if (n != ssize_t(s.length())) {
+			cerr << "Error reading from TCP stream: "
+				<< conn.last_error_str() << endl;
+			break;
+		}
+
+		cout << sret << endl;
+	}
+
+	return (!conn) ? 1 : 0;
+}
diff --git b/examples/tcp/tcpechomt.cpp a/examples/tcp/tcpechomt.cpp
new file mode 100644
index 0000000..a739534
--- /dev/null
+++ a/examples/tcp/tcpechomt.cpp
@@ -0,0 +1,124 @@
+// tcpechomt.cpp
+//
+// TCP echo client that uses separate read and write threads.
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2014-2017 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include <iostream>
+#include <string>
+#include <thread>
+#include "sockpp/tcp_connector.h"
+#include "sockpp/version.h"
+
+using namespace std;
+
+// --------------------------------------------------------------------------
+// The read thread will run independently, retrieving packets from the
+// server and writing them to the console. When the main (write) thread
+// shuts down the socket, we exit.
+
+void read_thr(sockpp::tcp_socket rdSock)
+{
+	char buf[512];
+	ssize_t n;
+
+	while ((n = rdSock.read(buf, sizeof(buf))) > 0) {
+		cout.write(buf, n);
+		cout << endl;
+	}
+
+	if (n < 0) {
+		cout << "Read error [" << rdSock.last_error() << "]: " 
+			<< rdSock.last_error_str() << endl;
+	}
+	rdSock.shutdown();
+}
+
+// --------------------------------------------------------------------------
+
+int main(int argc, char* argv[])
+{
+	cout << "Sample multi-threaded TCP echo client for 'sockpp' "
+		<< sockpp::SOCKPP_VERSION << '\n' << endl;
+
+	string host = (argc > 1) ? argv[1] : "localhost";
+	in_port_t port = (argc > 2) ? atoi(argv[2]) : 12345;
+
+	sockpp::socket_initializer sockInit;
+
+	// Implicitly creates an inet_address from {host,port}
+	// and then tries the connection.
+
+	sockpp::tcp_connector conn({host, port});
+	if (!conn) {
+		cerr << "Error connecting to server at "
+			<< sockpp::inet_address(host, port)
+			<< "\n\t" << conn.last_error_str() << endl;
+		return 1;
+	}
+
+	cout << "Created a connection from " << conn.address() << endl;
+
+	// We create a read thread and send it a clone (dup) of the
+	// connector socket. 
+
+	std::thread rdThr(read_thr, std::move(conn.clone()));
+
+	// The write loop get user input and writes it to the socket.
+
+	string s, sret;
+	while (getline(cin, s) && !s.empty()) {
+		if (conn.write(s) != (int) s.length()) {
+			if (conn.last_error() == EPIPE) {
+				cerr << "It appears that the socket was closed." << endl;
+			}
+			else {
+				cerr << "Error writing to the TCP stream ["
+					<< conn.last_error() << "]: "
+					<< conn.last_error_str() << endl;
+			}
+			break;
+		}
+	}
+	int ret = !conn ? 1 : 0;
+
+	// Shutting down the socket will cause the read thread to exit.
+	// We wait for it to exit before we leave the app.
+
+	conn.shutdown(SHUT_WR);
+	rdThr.join();
+
+	return ret;
+}
diff --git b/examples/tcp/tcpechosvr.cpp a/examples/tcp/tcpechosvr.cpp
new file mode 100644
index 0000000..1b7861f
--- /dev/null
+++ a/examples/tcp/tcpechosvr.cpp
@@ -0,0 +1,111 @@
+// tcpechosvr.cpp
+//
+// A multi-threaded TCP echo server for sockpp library.
+// This is a simple thread-per-connection TCP server.
+//
+// USAGE:
+//  	tcpechosvr [port]
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2014-2017 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include <iostream>
+#include <thread>
+#include "sockpp/tcp_acceptor.h"
+#include "sockpp/version.h"
+
+using namespace std;
+
+// --------------------------------------------------------------------------
+// The thread function. This is run in a separate thread for each socket.
+// Ownership of the socket object is transferred to the thread, so when this
+// function exits, the socket is automatically closed.
+
+void run_echo(sockpp::tcp_socket sock)
+{
+	ssize_t n;
+	char buf[512];
+
+	while ((n = sock.read(buf, sizeof(buf))) > 0)
+		sock.write_n(buf, n);
+
+	cout << "Connection closed from " << sock.peer_address() << endl;
+}
+
+// --------------------------------------------------------------------------
+// The main thread runs the TCP port acceptor. Each time a connection is
+// made, a new thread is spawned to handle it, leaving this main thread to
+// immediately wait for the next connection.
+
+int main(int argc, char* argv[])
+{
+	cout << "Sample TCP echo server for 'sockpp' "
+		<< sockpp::SOCKPP_VERSION << '\n' << endl;
+
+	in_port_t port = (argc > 1) ? atoi(argv[1]) : 12345;
+
+	sockpp::socket_initializer sockInit;
+
+	sockpp::tcp_acceptor acc(port);
+
+	if (!acc) {
+		cerr << "Error creating the acceptor: " << acc.last_error_str() << endl;
+		return 1;
+	}
+    //cout << "Acceptor bound to address: " << acc.address() << endl;
+	cout << "Awaiting connections on port " << port << "..." << endl;
+
+	while (true) {
+		sockpp::inet_address peer;
+
+		// Accept a new client connection
+		sockpp::tcp_socket sock = acc.accept(&peer);
+		cout << "Received a connection request from " << peer << endl;
+
+		if (!sock) {
+			cerr << "Error accepting incoming connection: " 
+				<< acc.last_error_str() << endl;
+		}
+		else {
+			// Create a thread and transfer the new stream to it.
+			thread thr(run_echo, std::move(sock));
+			thr.detach();
+		}
+	}
+
+	return 0;
+}
+
+
+
diff --git b/examples/tcp/tcpechotest.cpp a/examples/tcp/tcpechotest.cpp
new file mode 100644
index 0000000..9c2117f
--- /dev/null
+++ a/examples/tcp/tcpechotest.cpp
@@ -0,0 +1,123 @@
+// tcpechotest.cpp
+//
+// TCP echo timing client.
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2020 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include <iostream>
+#include <string>
+#include <chrono>
+#include <random>
+#include "sockpp/tcp_connector.h"
+#include "sockpp/version.h"
+
+using namespace std;
+using namespace std::chrono;
+
+const size_t DFLT_N = 100000;
+const size_t DFLT_SZ = 512;
+
+using fpsec = std::chrono::duration<double, std::chrono::seconds::period>;
+
+// --------------------------------------------------------------------------
+
+int main(int argc, char* argv[])
+{
+	cout << "Unix-domain echo timing test client for 'sockpp' "
+		<< sockpp::SOCKPP_VERSION << '\n' << endl;
+
+	string host = (argc > 1) ? argv[1] : "localhost";
+	in_port_t port = (argc > 2) ? atoi(argv[2]) : 12345;
+
+	size_t n  = (argc > 3) ? size_t(atoll(argv[3])) : DFLT_N;
+	size_t sz = (argc > 4) ? size_t(atoll(argv[4])) : DFLT_SZ;
+
+	sockpp::socket_initializer sockInit;
+
+	auto t_start = high_resolution_clock::now();
+
+
+	sockpp::tcp_connector conn({host, port});
+	if (!conn) {
+		cerr << "Error connecting to server at "
+			<< sockpp::inet_address(host, port)
+			<< "\n\t" << conn.last_error_str() << endl;
+		return 1;
+	}
+
+	cout << "Created a connection from " << conn.address() << endl;
+	cout << "Created a connection to " << conn.peer_address() << endl;
+
+    // Set a timeout for the responses
+    if (!conn.read_timeout(seconds(2))) {
+        cerr << "Error setting timeout on TCP stream: "
+                << conn.last_error_str() << endl;
+    }
+	string s, sret;
+
+    random_device rd;
+    mt19937 reng(rd());
+    uniform_int_distribution<int> dist(0, 25);
+
+	for (size_t i=0; i<sz; ++i)
+		s.push_back('a' + static_cast<char>(dist(reng)));
+
+	auto t_start_tx = high_resolution_clock::now();
+
+	for (size_t i=0; i<n; ++i) {
+		if (conn.write(s) != (ssize_t) s.length()) {
+			cerr << "Error writing to the UNIX stream" << endl;
+			break;
+		}
+
+		sret.resize(s.length());
+		int n = conn.read_n(&sret[0], s.length());
+
+		if (n != (int) s.length()) {
+			cerr << "Error reading from UNIX stream" << endl;
+			break;
+		}
+	}
+
+	auto t_end = high_resolution_clock::now();
+	cout << "Total time: " << fpsec(t_end - t_start).count() << "s" << endl;
+
+	auto t_tx = fpsec(t_end - t_start_tx).count();
+	auto rate = size_t(n / t_tx);
+	cout << "Transfer time: " << t_tx << "s\n    "
+		<< rate << " msg/s" << endl;
+
+	return (!conn) ? 1 : 0;
+}
diff --git b/examples/udp/udp6echo.cpp a/examples/udp/udp6echo.cpp
new file mode 100644
index 0000000..224576a
--- /dev/null
+++ a/examples/udp/udp6echo.cpp
@@ -0,0 +1,89 @@
+// udp6echo.cpp
+//
+// Simple UDP v6 echo client
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include <iostream>
+#include <string>
+#include "sockpp/udp6_socket.h"
+#include "sockpp/version.h"
+
+using namespace std;
+
+// --------------------------------------------------------------------------
+
+int main(int argc, char* argv[])
+{
+	cout << "Sample IPv6 UDP echo client for 'sockpp' "
+		<< sockpp::SOCKPP_VERSION << '\n' << endl;
+
+	string host = (argc > 1) ? argv[1] : "localhost";
+	in_port_t port = (argc > 2) ? atoi(argv[2]) : 12345;
+
+	sockpp::socket_initializer sockInit;
+
+	sockpp::udp6_socket sock;
+
+	if (!sock.connect(sockpp::inet6_address(host, port))) {
+		cerr << "Error connecting to server at " << host << ":" << port 
+			<< "\n\t" << sock.last_error_str() << endl;
+		return 1;
+	}
+
+	cout << "Created UDP socket at: " << sock.address() << endl;
+
+	string s, sret;
+	while (getline(cin, s) && !s.empty()) {
+		if (sock.send(s) != ssize_t(s.length())) {
+			cerr << "Error writing to the UDP socket: "
+				<< sock.last_error_str() << endl;
+			break;
+		}
+
+		sret.resize(s.length());
+		ssize_t n = sock.recv(&sret[0], s.length());
+
+		if (n != ssize_t(s.length())) {
+			cerr << "Error reading from UDP socket: "
+				<< sock.last_error_str() << endl;
+			break;
+		}
+
+		cout << sret << endl;
+	}
+
+	return (!sock) ? 1 : 0;
+}
diff --git b/examples/udp/udpecho.cpp a/examples/udp/udpecho.cpp
new file mode 100644
index 0000000..e3fd3e7
--- /dev/null
+++ a/examples/udp/udpecho.cpp
@@ -0,0 +1,89 @@
+// udpecho.cpp
+//
+// Simple UDP v4 echo client
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include <iostream>
+#include <string>
+#include "sockpp/udp_socket.h"
+#include "sockpp/version.h"
+
+using namespace std;
+
+// --------------------------------------------------------------------------
+
+int main(int argc, char* argv[])
+{
+	cout << "Sample UDP echo client for 'sockpp' "
+		<< sockpp::SOCKPP_VERSION << '\n' << endl;
+
+	string host = (argc > 1) ? argv[1] : "localhost";
+	in_port_t port = (argc > 2) ? atoi(argv[2]) : 12345;
+
+	sockpp::socket_initializer sockInit;
+
+	sockpp::udp_socket sock;
+
+	if (!sock.connect(sockpp::inet_address(host, port))) {
+		cerr << "Error connecting to server at " << host << ":" << port 
+			<< "\n\t" << sock.last_error_str() << endl;
+		return 1;
+	}
+
+	cout << "Created UDP socket at: " << sock.address() << endl;
+
+	string s, sret;
+	while (getline(cin, s) && !s.empty()) {
+		if (sock.send(s) != ssize_t(s.length())) {
+			cerr << "Error writing to the UDP socket: "
+				<< sock.last_error_str() << endl;
+			break;
+		}
+
+		sret.resize(s.length());
+		ssize_t n = sock.recv(&sret[0], s.length());
+
+		if (n != ssize_t(s.length())) {
+			cerr << "Error reading from UDP socket: "
+				<< sock.last_error_str() << endl;
+			break;
+		}
+
+		cout << sret << endl;
+	}
+
+	return (!sock) ? 1 : 0;
+}
diff --git b/examples/udp/udpechosvr.cpp a/examples/udp/udpechosvr.cpp
new file mode 100644
index 0000000..b3cf3f6
--- /dev/null
+++ a/examples/udp/udpechosvr.cpp
@@ -0,0 +1,121 @@
+// udpechosvr.cpp
+//
+// A simple multi-threaded TCP/IP UDP echo server for sockpp library.
+//
+// This runs a UDP echo server for both IPv4 and IPv6, each in a separate
+// thread. They both use the same port number, either as provided by the user
+// on the command line, or defaulting to 12345.
+//
+// USAGE:
+//  	uspechosvr [port]
+//
+// You can test with a netcat client, like:
+// 		$ nc -u localhost 12345		# IPv4
+// 		$ nc -6u localhost 12345	# IPv6
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include <iostream>
+#include <thread>
+#include "sockpp/udp_socket.h"
+#include "sockpp/udp6_socket.h"
+#include "sockpp/version.h"
+
+using namespace std;
+
+// --------------------------------------------------------------------------
+// The thread function. This is run in a separate thread for each socket.
+// Ownership of the socket object is transferred to the thread, so when this
+// function exits, the socket is automatically closed.
+
+template <typename UDPSOCK>
+void run_echo(UDPSOCK sock)
+{
+	ssize_t n;
+	char buf[512];
+
+	// Each UDP socket type knows its address type as `addr_t`
+	typename UDPSOCK::addr_t srcAddr;
+
+	// Read some data, also getting the address of the sender,
+	// then just send it back.
+	while ((n = sock.recv_from(buf, sizeof(buf), &srcAddr)) > 0)
+		sock.send_to(buf, n, srcAddr);
+}
+
+// --------------------------------------------------------------------------
+// The main thread creates the two UDP sockets (one each for IPv4 and IPv6),
+// and then starts them running the echo function each in a separate thread.
+
+int main(int argc, char* argv[])
+{
+	cout << "Sample UDP echo server for 'sockpp' "
+		<< sockpp::SOCKPP_VERSION << '\n' << endl;
+
+	in_port_t port = (argc > 1) ? atoi(argv[1]) : 12345;
+
+	sockpp::socket_initializer sockInit;
+
+	sockpp::udp_socket	udpsock;
+	if (!udpsock) {
+		cerr << "Error creating the UDP v4 socket: " << udpsock.last_error_str() << endl;
+		return 1;
+	}
+
+	if (!udpsock.bind(sockpp::inet_address("localhost", port))) {
+		cerr << "Error binding the UDP v4 socket: " << udpsock.last_error_str() << endl;
+		return 1;
+	}
+
+	sockpp::udp6_socket	udp6sock;
+	if (!udp6sock) {
+		cerr << "Error creating the UDP v6 socket: " << udp6sock.last_error_str() << endl;
+		return 1;
+	}
+
+	if (!udp6sock.bind(sockpp::inet6_address("localhost", port))) {
+		cerr << "Error binding the UDP v6 socket: " << udp6sock.last_error_str() << endl;
+		return 1;
+	}
+
+	// Spin up a thread to run the IPv4 socket.
+	thread thr(run_echo<sockpp::udp_socket>, std::move(udpsock));
+	thr.detach();
+
+	// Run the IPv6 socket in this thread. (Call doesn't return)
+	run_echo(std::move(udp6sock));
+	return 0;
+}
+
diff --git b/examples/unix/undgramecho.cpp a/examples/unix/undgramecho.cpp
new file mode 100644
index 0000000..9fc4a17
--- /dev/null
+++ a/examples/unix/undgramecho.cpp
@@ -0,0 +1,97 @@
+// udpecho.cpp
+//
+// Simple Unix-domain UDP echo client
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include <iostream>
+#include <string>
+#include "sockpp/unix_dgram_socket.h"
+
+using namespace std;
+
+// --------------------------------------------------------------------------
+
+int main(int argc, char* argv[])
+{
+	sockpp::socket_initializer sockInit;
+
+	string	cliAddr { "/tmp/undgramecho.sock" },
+			svrAddr { "/tmp/undgramechosvr.sock" };
+
+	sockpp::unix_dgram_socket sock;
+
+	// A Unix-domain UDP client needs to bind to its own address
+	// before it can send or receive packets
+
+	if (!sock.bind(sockpp::unix_address(cliAddr))) {
+		cerr << "Error connecting to client address at '" << cliAddr << "'"
+			<< "\n\t" << sock.last_error_str() << endl;
+		return 1;
+	}
+
+	// "Connect" to the server address. This is a convenience to set the
+	// default 'send_to' address, as there is no real connection.
+
+	if (!sock.connect(sockpp::unix_address(svrAddr))) {
+		cerr << "Error connecting to server at '" << svrAddr << "'"
+			<< "\n\t" << sock.last_error_str() << endl;
+		return 1;
+	}
+
+	cout << "Created UDP socket at: " << sock.address() << endl;
+
+	string s, sret;
+	while (getline(cin, s) && !s.empty()) {
+		if (sock.send(s) != ssize_t(s.length())) {
+			cerr << "Error writing to the UDP socket: "
+				<< sock.last_error_str() << endl;
+			break;
+		}
+
+		sret.resize(s.length());
+		ssize_t n = sock.recv(&sret[0], s.length());
+
+		if (n != ssize_t(s.length())) {
+			cerr << "Error reading from UDP socket: "
+				<< sock.last_error_str() << endl;
+			break;
+		}
+
+		cout << sret << endl;
+	}
+
+	return (!sock) ? 1 : 0;
+}
diff --git b/examples/unix/undgramechosvr.cpp a/examples/unix/undgramechosvr.cpp
new file mode 100644
index 0000000..fed3643
--- /dev/null
+++ a/examples/unix/undgramechosvr.cpp
@@ -0,0 +1,93 @@
+// undgramechosvr.cpp
+//
+// A simple multi-threaded TCP/IP UDP echo server for sockpp library.
+//
+// This runs a UDP echo server for both IPv4 and IPv6, each in a separate
+// thread. They both use the same port number, either as provided by the user
+// on the command line, or defaulting to 12345.
+//
+// USAGE:
+//  	undgramechosvr [port]
+//
+// You can test with a netcat client, like:
+// 		$ nc -u localhost 12345		# IPv4
+// 		$ nc -6u localhost 12345	# IPv6
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include <iostream>
+#include "sockpp/unix_dgram_socket.h"
+#include "sockpp/version.h"
+
+using namespace std;
+
+// --------------------------------------------------------------------------
+// The main thread creates the UDP socket, and then starts them running the
+// echo service in a loop.
+
+int main(int argc, char* argv[])
+{
+	cout << "Sample Unix-domain datagram echo server for 'sockpp' "
+		<< sockpp::SOCKPP_VERSION << '\n' << endl;
+
+	sockpp::socket_initializer sockInit;
+
+	sockpp::unix_dgram_socket sock;
+	if (!sock) {
+		cerr << "Error creating the socket: " << sock.last_error_str() << endl;
+		return 1;
+	}
+
+	if (!sock.bind(sockpp::unix_address("/tmp/undgramechosvr.sock"))) {
+		cerr << "Error binding the socket: " << sock.last_error_str() << endl;
+		return 1;
+	}
+
+	// Run the socket in this thread.
+	ssize_t n;
+	char buf[512];
+
+	sockpp::unix_address srcAddr;
+
+	cout << "Awaiting packets on: '" << sock.address() << "'" << endl;
+
+	// Read some data, also getting the address of the sender,
+	// then just send it back.
+	while ((n = sock.recv_from(buf, sizeof(buf), &srcAddr)) > 0)
+		sock.send_to(buf, n, srcAddr);
+
+	return 0;
+}
+
diff --git b/examples/unix/unecho.cpp a/examples/unix/unecho.cpp
new file mode 100644
index 0000000..5d1661c
--- /dev/null
+++ a/examples/unix/unecho.cpp
@@ -0,0 +1,87 @@
+// unecho.cpp
+//
+// Simple Unix-domain echo client
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2014-2017 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include <iostream>
+#include <string>
+#include "sockpp/unix_connector.h"
+#include "sockpp/version.h"
+
+using namespace std;
+
+// --------------------------------------------------------------------------
+
+int main(int argc, char* argv[])
+{
+	cout << "Sample Unix-domain echo client for 'sockpp' "
+		<< sockpp::SOCKPP_VERSION << '\n' << endl;
+
+	string path = (argc > 1) ? argv[1] : "/tmp/unechosvr.sock";
+
+	sockpp::socket_initializer sockInit;
+
+	sockpp::unix_connector conn;
+
+    bool ok = conn.connect(sockpp::unix_address(path));
+	if (!ok) {
+		cerr << "Error connecting to UNIX socket at " << path
+			<< "\n\t" << conn.last_error_str() << endl;
+		return 1;
+	}
+
+	cout << "Created a connection to '" << conn.peer_address() << "'" << endl;
+
+	string s, sret;
+	while (getline(cin, s) && !s.empty()) {
+		if (conn.write(s) != (int) s.length()) {
+			cerr << "Error writing to the UNIX stream" << endl;
+			break;
+		}
+
+		sret.resize(s.length());
+		int n = conn.read_n(&sret[0], s.length());
+
+		if (n != (int) s.length()) {
+			cerr << "Error reading from UNIX stream" << endl;
+			break;
+		}
+
+		cout << sret << endl;
+	}
+
+	return (!conn) ? 1 : 0;
+}
diff --git b/examples/unix/unechosvr.cpp a/examples/unix/unechosvr.cpp
new file mode 100644
index 0000000..ba5448b
--- /dev/null
+++ a/examples/unix/unechosvr.cpp
@@ -0,0 +1,111 @@
+// unechosvr.cpp
+//
+// A multi-threaded UNIX-domain echo server for sockpp library.
+// This is a simple thread-per-connection UNIX server.
+//
+// USAGE:
+//  	unechosvr [path]
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2014-2017 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include <iostream>
+#include <thread>
+#include "sockpp/unix_acceptor.h"
+#include "sockpp/version.h"
+
+using namespace std;
+
+// --------------------------------------------------------------------------
+// The thread function. This is run in a separate thread for each socket.
+// Ownership of the socket object is transferred to the thread, so when this
+// function exits, the socket is automatically closed.
+
+void run_echo(sockpp::unix_socket sock)
+{
+	int n;
+	char buf[512];
+
+	while ((n = sock.read(buf, sizeof(buf))) > 0)
+		sock.write_n(buf, n);
+
+	cout << "Connection closed" << endl;
+}
+
+// --------------------------------------------------------------------------
+// The main thread runs the UNIX acceptor.
+// Each time a connection is made, a new thread is spawned to handle it,
+// leaving this main thread to immediately wait for the next connection.
+
+int main(int argc, char* argv[])
+{
+	cout << "Sample Unix-domain echo server for 'sockpp' "
+		<< sockpp::SOCKPP_VERSION << '\n' << endl;
+
+	string path = "/tmp/unechosvr.sock";
+
+	if (argc > 1) {
+		path = argv[1];
+	}
+
+	sockpp::socket_initializer sockInit;
+	sockpp::unix_acceptor acc;
+
+	bool ok = acc.open(sockpp::unix_address(path));
+
+	if (!ok) {
+		cerr << "Error creating the acceptor: " << acc.last_error_str() << endl;
+		return 1;
+	}
+    cout << "Acceptor bound to address: '" << acc.address() << "'..." << endl;
+
+	while (true) {
+		// Accept a new client connection
+		auto sock = acc.accept();
+		cout << "Received a connection" << endl;
+
+		if (!sock) {
+			cerr << "Error accepting incoming connection: " 
+				<< acc.last_error_str() << endl;
+		}
+		else {
+			// Create a thread and transfer the new stream to it.
+			thread thr(run_echo, std::move(sock));
+			thr.detach();
+		}
+	}
+
+	return 0;
+}
+
diff --git b/examples/unix/unechotest.cpp a/examples/unix/unechotest.cpp
new file mode 100644
index 0000000..a0c1d98
--- /dev/null
+++ a/examples/unix/unechotest.cpp
@@ -0,0 +1,114 @@
+// unechotest.cpp
+//
+// Unix-domain echo timing test client
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2020 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include <iostream>
+#include <string>
+#include <chrono>
+#include <random>
+#include "sockpp/unix_connector.h"
+#include "sockpp/version.h"
+
+using namespace std;
+using namespace std::chrono;
+
+const size_t DFLT_N = 100000;
+const size_t DFLT_SZ = 512;
+
+using fpsec = std::chrono::duration<double, std::chrono::seconds::period>;
+
+// --------------------------------------------------------------------------
+
+int main(int argc, char* argv[])
+{
+	cout << "Unix-domain echo timing test client for 'sockpp' "
+		<< sockpp::SOCKPP_VERSION << '\n' << endl;
+
+	string path = (argc > 1) ? argv[1] : "/tmp/unechosvr.sock";
+	size_t n  = (argc > 2) ? size_t(atoll(argv[2])) : DFLT_N;
+	size_t sz = (argc > 3) ? size_t(atoll(argv[3])) : DFLT_SZ;
+
+	sockpp::socket_initializer sockInit;
+
+	auto t_start = high_resolution_clock::now();
+	sockpp::unix_connector conn;
+
+    bool ok = conn.connect(sockpp::unix_address(path));
+	if (!ok) {
+		cerr << "Error connecting to UNIX socket at " << path
+			<< "\n\t" << conn.last_error_str() << endl;
+		return 1;
+	}
+
+	cout << "Created a connection to '" << conn.peer_address() << "'" << endl;
+
+	string s, sret;
+
+    random_device rd;
+    mt19937 reng(rd());
+    uniform_int_distribution<char> dist(0, 25);
+
+	for (size_t i=0; i<sz; ++i)
+		s.push_back('a' + dist(reng));
+
+	auto t_start_tx = high_resolution_clock::now();
+
+	for (size_t i=0; i<n; ++i) {
+		if (conn.write(s) != (ssize_t) s.length()) {
+			cerr << "Error writing to the UNIX stream" << endl;
+			break;
+		}
+
+		sret.resize(s.length());
+		int n = conn.read_n(&sret[0], s.length());
+
+		if (n != (int) s.length()) {
+			cerr << "Error reading from UNIX stream" << endl;
+			break;
+		}
+	}
+
+	auto t_end = high_resolution_clock::now();
+	cout << "Total time: " << fpsec(t_end - t_start).count() << "s" << endl;
+
+	auto t_tx = fpsec(t_end - t_start_tx).count();
+	auto rate = size_t(n / t_tx);
+	cout << "Transfer time: " << t_tx << "s\n    "
+		<< rate << " msg/s" << endl;
+
+	return (!conn) ? 1 : 0;
+}
diff --git b/include/socket-cpp/acceptor.h a/include/socket-cpp/acceptor.h
new file mode 100644
index 0000000..30e6753
--- /dev/null
+++ a/include/socket-cpp/acceptor.h
@@ -0,0 +1,230 @@
+#pragma once
+
+#include "socket-cpp/inet_address.h"
+#include "socket-cpp/stream_socket.h"
+
+namespace osdev {
+namespace components {
+namespace socket-cpp {
+
+/**
+ * Class for creating a streaming server.
+ * Objects of this class bind and listen on streaming ports for incoming
+ * connections. Normally, a server thread creates one of these and blocks on
+ * the call to accept incoming connections. The call to accept creates and
+ * returns a @ref stream_socket which can then be used for the actual
+ * communications.
+ */
+class acceptor : public socket
+{
+	/** The base class */
+	using base = socket;
+
+	// Non-copyable
+	acceptor(const acceptor&) = delete;
+	acceptor& operator=(const acceptor&) = delete;
+
+protected:
+	/** The default listener queue size. */
+	static const int DFLT_QUE_SIZE = 4;
+
+	/**
+	 * Creates an underlying acceptor socket.
+	 * The acceptor uses a stream socket type, but for our purposes is not
+	 * classified (derived from) a streaming socket, since it doesn't
+	 * support read and write to the socket.
+	 * @param domain The communications domain (address family).
+	 * @return An OS handle to a stream socket.
+	 */
+	static socket_t create_handle(int domain) {
+		return stream_socket::create_handle(domain);
+	}
+
+public:
+	/**
+	 * Creates an unconnected acceptor.
+	 */
+	acceptor() {}
+	/**
+	 * Creates an acceptor from an existing OS socket
+	 * handle and claims ownership of the handle.
+	 * @param handle A socket handle from the operating system.
+	 */
+	explicit acceptor(socket_t handle) : base(handle) {}
+	/**
+	 * Creates an acceptor socket and starts it listening to the specified
+	 * address.
+	 * @param addr The address to which this server should be bound.
+	 * @param queSize The listener queue size.
+	 */
+	acceptor(const sock_address& addr, int queSize=DFLT_QUE_SIZE) {
+		open(addr, queSize);
+	}
+	/**
+	 * Move constructor.
+	 * Creates an acceptor by moving the other acceptor to this one.
+	 * @param acc Another acceptor
+	 */
+	acceptor(acceptor&& acc) : base(std::move(acc)) {}
+	/**
+	 * Creates an unbound acceptor socket with an open OS socket handle.
+	 * An application would need to manually bind and listen to this
+	 * acceptor to get incoming connections.
+	 * @param domain The communications domain (address family).
+	 * @return An open, but unbound acceptor socket.
+	 */
+	static acceptor create(int domain);
+	/**
+	 * Move assignment.
+	 * @param rhs The other socket to move into this one.
+	 * @return A reference to this object.
+	 */
+	acceptor& operator=(acceptor&& rhs) {
+		base::operator=(std::move(rhs));
+		return *this;
+	}
+	/**
+	 * Sets the socket listening on the address to which it is bound.
+	 * @param queSize The listener queue size.
+	 * @return @em true on success, @em false on error
+	 */
+	bool listen(int queSize=DFLT_QUE_SIZE) {
+		return check_ret_bool(::listen(handle(), queSize));
+	};
+	/**
+	 * Opens the acceptor socket, binds it to the specified address, and starts
+	 * listening.
+	 * @param addr The address to which this server should be bound.
+	 * @param queSize The listener queue size.
+	 * @param reuseSock Whether the SO_REUSEPORT (or SO_REUSEADDR on Win32)
+	 *  				socket option should be used before binding and
+	 *  				listening.
+	 * @return @em true on success, @em false on error
+	 */
+	bool open(const sock_address& addr, int queSize=DFLT_QUE_SIZE, bool reuseSock=true);
+	/**
+	 * Accepts an incoming TCP connection and gets the address of the client.
+	 * @param clientAddr Pointer to the variable that will get the
+	 *  				 address of a client when it connects.
+	 * @return A socket to the remote client.
+	 */
+	stream_socket accept(sock_address* clientAddr=nullptr);
+};
+
+
+/**
+ * Base template class for streaming servers of specific address families.
+ * This is a base for creating socket acceptor classes for an individual
+ * family. In most cases, all that is needed is a type definition specifying
+ * which addresses type should be used to receive incoming connections,
+ * like:
+ *     using tcp_acceptor = acceptor_tmpl<tcp_socket>;
+ */
+template <typename STREAM_SOCK, typename ADDR=typename STREAM_SOCK::addr_t>
+class acceptor_tmpl : public acceptor
+{
+	/** The base class */
+	using base = acceptor;
+
+	// Non-copyable
+	acceptor_tmpl(const acceptor_tmpl&) =delete;
+	acceptor_tmpl& operator=(const acceptor_tmpl&) =delete;
+
+public:
+	/** The type of streaming socket from the acceptor. */
+	using stream_sock_t = STREAM_SOCK;
+	/** The type of address for the acceptor and streams. */
+	using addr_t = ADDR;
+
+	/**
+	 * Creates an unconnected acceptor.
+	 */
+	acceptor_tmpl() {}
+	/**
+	 * Creates a acceptor and starts it listening on the specified address.
+	 * @param addr The TCP address on which to listen.
+	 * @param queSize The listener queue size.
+	 */
+	acceptor_tmpl(const addr_t& addr, int queSize=DFLT_QUE_SIZE) {
+		open(addr, queSize);
+	}
+	/**
+	 * Creates a acceptor and starts it listening on the specified port.
+	 * The acceptor binds to the specified port for any address on the local
+	 * host.
+	 * @param port The TCP port on which to listen.
+	 * @param queSize The listener queue size.
+	 */
+	acceptor_tmpl(in_port_t port, int queSize=DFLT_QUE_SIZE) {
+		open(port, queSize);
+	}
+	/**
+	 * Move constructor.
+	 * Creates an acceptor by moving the other acceptor to this one.
+	 * @param acc Another acceptor
+	 */
+	acceptor_tmpl(acceptor_tmpl&& acc) : base(std::move(acc)) {}
+	/**
+	 * Creates an unbound acceptor socket with an open OS socket handle.
+	 * An application would need to manually bind and listen to this
+	 * acceptor to get incoming connections.
+	 * @return An open, but unbound acceptor socket.
+	 */
+	static acceptor_tmpl create() {
+		return base::create(addr_t::ADDRESS_FAMILY);
+	}
+	/**
+	 * Move assignment.
+	 * @param rhs The other socket to move into this one.
+	 * @return A reference to this object.
+	 */
+	acceptor_tmpl& operator=(acceptor_tmpl&& rhs) {
+		base::operator=(std::move(rhs));
+		return *this;
+	}
+	/**
+	 * Gets the local address to which we are bound.
+	 * @return The local address to which we are bound.
+	 */
+	addr_t address() const { return addr_t(base::address()); }
+	/**
+	 * Binds the socket to the specified address.
+	 * @param addr The address to which we get bound.
+	 * @return @em true on success, @em false on error
+	 */
+	bool bind(const addr_t& addr) { return base::bind(addr); }
+	/**
+	 * Opens the acceptor socket, binds it to the specified address, and starts
+	 * listening.
+	 * @param addr The address to which this server should be bound.
+	 * @param queSize The listener queue size.
+	 * @return @em true on success, @em false on error
+	 */
+	bool open(const addr_t& addr, int queSize=DFLT_QUE_SIZE) {
+		return base::open(addr, queSize);
+	}
+	/**
+	 * Opens the acceptor socket, binds the socket to all adapters and starts it
+	 * listening.
+	 * @param port The TCP port on which to listen.
+	 * @param queSize The listener queue size.
+	 * @return @em true on success, @em false on error
+	 */
+	bool open(in_port_t port, int queSize=DFLT_QUE_SIZE) {
+		return open(addr_t(port), queSize);
+	}
+	/**
+	 * Accepts an incoming connection and gets the address of the client.
+	 * @param clientAddr Pointer to the variable that will get the
+	 *  				 address of a client when it connects.
+	 * @return A tcp_socket to the remote client.
+	 */
+	stream_sock_t accept(addr_t* clientAddr=nullptr) {
+		return stream_sock_t(base::accept(clientAddr));
+	}
+};
+
+}   // End namespace socket-cpp
+}   // End namespace components
+}   // End namespace osdev
+
diff --git b/include/socket-cpp/can_address.h a/include/socket-cpp/can_address.h
new file mode 100644
index 0000000..18e7375
--- /dev/null
+++ a/include/socket-cpp/can_address.h
@@ -0,0 +1,151 @@
+#pragma once
+
+#include "socket-cpp/platform.h"
+#include "socket-cpp/sock_address.h"
+#include <iostream>
+#include <string>
+#include <cstring>
+#include <sys/un.h>
+
+#include <linux/can.h>
+
+namespace osdev {
+namespace components {
+namespace socket-cpp {
+
+/**
+ * Class that represents a Linux SocketCAN address.
+ * This inherits from the CAN form of a socket address, @em sockaddr_can.
+ */
+class can_address : public sock_address
+{
+	/** The underlying C struct for SocketCAN addresses  */
+	sockaddr_can addr_;
+
+	/** The size of the underlying address struct, in bytes */
+	static constexpr size_t SZ = sizeof(sockaddr_can);
+
+public:
+    /** The address family for this type of address */
+	static constexpr sa_family_t ADDRESS_FAMILY = AF_CAN;
+
+	/** Iface to use to indicate binding to all interfaces */
+	static const unsigned ALL_IFACE = 0;
+
+	/**
+	 * Constructs an empty address.
+	 * The address is initialized to all zeroes.
+	 */
+	can_address() : addr_() {}
+	/**
+	 * Creates an address for binding to a specific CAN interface
+	 * @param ifindex The interface index to use. This must, obviously, be
+	 *  			  an index to a CAN interface.
+	 */
+	explicit can_address(unsigned ifindex);
+	/**
+	 * Constructs an address for the specified CAN interface.
+	 * The interface might be "can0", "can1", "vcan0", etc.
+	 * @param iface The name of the CAN interface
+	 */
+	can_address(const std::string& iface);
+	/**
+	 * Constructs the address by copying the specified structure.
+     * @param addr The generic address
+     * @throws std::invalid_argument if the address is not a SocketCAN
+     *            address (i.e. family is not AF_CAN)
+	 */
+	explicit can_address(const sockaddr& addr);
+	/**
+	 * Constructs the address by copying the specified structure.
+	 * @param addr The other address
+	 */
+	can_address(const sock_address& addr) {
+		std::memcpy(&addr_, addr.sockaddr_ptr(), SZ);
+	}
+	/**
+	 * Constructs the address by copying the specified structure.
+     * @param addr The other address
+     * @throws std::invalid_argument if the address is not properly
+     *            initialized as a SocketCAN address (i.e. family is not
+     *            AF_CAN)
+	 */
+	can_address(const sockaddr_can& addr) : addr_(addr) {}
+	/**
+	 * Constructs the address by copying the specified address.
+	 * @param addr The other address
+	 */
+	can_address(const can_address& addr) : addr_(addr.addr_) {}
+	/**
+	 * Checks if the address is set to some value.
+	 * This doesn't attempt to determine if the address is valid, simply
+	 * that it's not all zero.
+	 * @return @em true if the address has been set, @em false otherwise.
+	 */
+	bool is_set() const { return family() != AF_UNSPEC; }
+	/**
+	 * Gets the name of the CAN interface to which this address refers.
+	 * @return The name of the CAN interface to which this address refers.
+	 */
+	std::string iface() const;
+	/**
+	 * Gets the size of the address structure.
+	 * Note: In this implementation, this should return sizeof(this) but
+	 * more convenient in some places, and the implementation might change
+	 * in the future, so it might be more compatible with future revisions
+	 * to use this call.
+	 * @return The size of the address structure.
+	 */
+	socklen_t size() const override { return socklen_t(SZ); }
+
+    // TODO: Do we need a:
+    //   create(iface)
+    // to mimic the inet_address behavior?
+
+    /**
+	 * Gets a pointer to this object cast to a const @em sockaddr.
+	 * @return A pointer to this object cast to a const @em sockaddr.
+	 */
+	const sockaddr* sockaddr_ptr() const override {
+		return reinterpret_cast<const sockaddr*>(&addr_);
+	}
+	/**
+	 * Gets a pointer to this object cast to a @em sockaddr.
+	 * @return A pointer to this object cast to a @em sockaddr.
+	 */
+	sockaddr* sockaddr_ptr() override {
+		return reinterpret_cast<sockaddr*>(&addr_);
+	}
+	/**
+	 * Gets a const pointer to this object cast to a @em sockaddr_can.
+	 * @return const sockaddr_can pointer to this object.
+	 */
+	const sockaddr_can* sockaddr_can_ptr() const { return &addr_; }
+	/**
+	 * Gets a pointer to this object cast to a @em sockaddr_can.
+	 * @return sockaddr_can pointer to this object.
+	 */
+	sockaddr_can* sockaddr_can_ptr() { return &addr_; }
+	/**
+	 * Gets a printable string for the address.
+	 * @return A string representation of the address in the form
+	 *  	   "unix:<path>"
+	 */
+	std::string to_string() const {
+		return std::string("can:") + iface();
+	}
+};
+
+// --------------------------------------------------------------------------
+
+/**
+ * Stream inserter for the address.
+ * @param os The output stream
+ * @param addr The address
+ * @return A reference to the output stream.
+ */
+std::ostream& operator<<(std::ostream& os, const can_address& addr);
+
+}   // End namespace socket-cpp
+}   // End namespace components
+}   // End namespace osdev
diff --git b/include/socket-cpp/can_frame.h a/include/socket-cpp/can_frame.h
new file mode 100644
index 0000000..eb480b1
--- /dev/null
+++ a/include/socket-cpp/can_frame.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include "socket-cpp/platform.h"
+#include <string>
+#include <cstring>
+//#include <sys/un.h>
+#include <linux/can.h>
+
+namespace osdev {
+namespace components {
+namespace socket-cpp {
+
+/**
+ * Class that represents a Linux SocketCAN frame.
+ * This inherits from the Linux CAN frame struct, just providing easier
+   construction.
+ */
+class can_frame : public ::can_frame
+{
+	using base = ::can_frame;
+
+	/** The size of the underlying address struct, in bytes */
+	static constexpr size_t SZ = sizeof(::can_frame);
+
+public:
+	/**
+	 * Constructs an empty frame.
+	 * The frame is initialized to all zeroes.
+	 */
+	can_frame() : base{} {}
+	/**
+	 * Constructs a frame with the specified ID and data.
+	 * @param canID The CAN identifier for the frame
+	 * @param data The data field for the frame
+	 */
+	can_frame(canid_t canID, const std::string& data)
+		: can_frame{ canID, data.data(), data.length() } {}
+	/**
+	 * Constructs a frame with the specified ID and data.
+	 * @param canID The CAN identifier for the frame
+	 * @param data The data field for the frame
+	 * @param n The number of bytes in the data field
+	 */
+	can_frame(canid_t canID, const void* data, size_t n) : base{} {
+		this->can_id = canID;
+		this->can_dlc = n;
+		::memcpy(&this->data, data, n);
+	}
+};
+
+}   // End namespace socket-cpp
+}   // End namespace components
+}   // End namespace osdev
diff --git b/include/socket-cpp/can_socket.h a/include/socket-cpp/can_socket.h
new file mode 100644
index 0000000..937c72b
--- /dev/null
+++ a/include/socket-cpp/can_socket.h
@@ -0,0 +1,156 @@
+#pragma once
+
+#include "socket-cpp/datagram_socket.h"
+#include "socket-cpp/can_address.h"
+#include "socket-cpp/can_frame.h"
+
+namespace osdev {
+namespace components {
+namespace socket-cpp {
+
+/**
+ * Socket type for Linux SocketCAN.
+ *
+ * Note that technically these are RAW sockets, not DGRAM. We can/should
+ * organize the underlying hierarch to properly indicate this, but for
+ * practical usge, it doesn't matter too MUCH.
+ * The BCM CAN sockets are DGRAM sockets, but those aren't implemented yet.
+ * It wouldn't take too much to add them, though.
+ */
+class can_socket : public datagram_socket
+{
+	/** The base class */
+	using base = datagram_socket;
+
+	// Non-copyable
+	can_socket(const can_socket&) =delete;
+	can_socket& operator=(const can_socket&) =delete;
+
+	/**
+	 * We can't connect to a raw CAN socket;
+	 * we can only bind the address/iface.
+	 */
+	bool connect(const sock_address&) =delete;
+
+protected:
+	static socket_t create_handle(int type, int protocol) {
+		return socket_t(::socket(PROTOCOL_FAMILY, type, protocol));
+	}
+
+public:
+	/**
+	 *  The SocketCAN protocol family.
+	 *  Note that AF_CAN == PF_CAN, which is used in many of the CAN
+	 *  examples.
+	 */
+	static const int PROTOCOL_FAMILY = AF_CAN;
+
+	/** The socket 'type' for communications semantics. */
+	static constexpr int COMM_TYPE = SOCK_RAW;
+
+	/**
+	 * Creates an uninitialized CAN socket.
+	 */
+	can_socket() {}
+	/**
+     * Creates a CAN socket from an existing OS socket handle and
+     * claims ownership of the handle.
+	 * @param handle A socket handle from the operating system.
+	 */
+	explicit can_socket(socket_t handle) : base(handle) {}
+	/**
+	 * Creates a CAN socket and binds it to the address.
+	 * @param addr The address to bind.
+	 */
+	explicit can_socket(const can_address& addr);
+	/**
+	 * Move constructor.
+	 * @param other The other socket to move to this one
+	 */
+	can_socket(can_socket&& other) : base(std::move(other)) {}
+	/**
+	 * Move assignment.
+	 * @param rhs The other socket to move into this one.
+	 * @return A reference to this object.
+	 */
+	can_socket& operator=(can_socket&& rhs) {
+		base::operator=(std::move(rhs));
+		return *this;
+	}
+
+	/**
+	 * Gets the system time of the last frame read from the socket.
+	 * @return The system time of the last frame read from the socket with
+	 *  	   microsecond precision.
+	 */
+	std::chrono::system_clock::time_point last_frame_time();
+	/**
+	 * Gets a floating point timestamp of the last frame read from the
+	 * socket.
+	 * This is the number of seconds since the Unix epoch (time_t), with
+	 * floating-point, microsecond precision.
+	 * @return A floating-point timestamp with microsecond precision.
+	 */
+	double last_frame_timestamp();
+
+	// ----- I/O -----
+
+	/**
+	 * Sends a frame to the CAN interfacce at the specified address.
+	 * @param frame The CAN frame to send.
+	 * @param flags The flags. See send(2).
+	 * @param addr The remote destination of the data.
+	 * @return the number of bytes sent on success or, @em -1 on failure.
+	 */
+	ssize_t send_to(const can_frame& frame, int flags, const can_address& addr) {
+		return check_ret(
+			::sendto(handle(), &frame, sizeof(can_frame), flags,
+					 addr.sockaddr_ptr(), addr.size())
+	    );
+	}
+
+	/**
+	 * Sends a frame to the CAN interface at the specified address.
+	 * @param frame The CAN frame to send.
+	 * @param addr The remote destination of the data.
+	 * @return the number of bytes sent on success or, @em -1 on failure.
+	 */
+	ssize_t send_to(const can_frame& frame, const can_address& addr) {
+		return check_ret(
+			::sendto(handle(), &frame, sizeof(can_frame), 0,
+					 addr.sockaddr_ptr(), addr.size())
+	    );
+	}
+	/**
+	 * Sends a frame to the CAN bus.
+	 * The socket should be bound before calling this.
+	 * @param frame The CAN frame to send.
+	 * @param flags The option bit flags. See send(2).
+	 * @return @em zero on success, @em -1 on failure.
+	 */
+	ssize_t send(const can_frame& frame, int flags=0) {
+		return check_ret(::send(handle(), &frame, sizeof(can_frame), flags));
+	}
+	/**
+	 * Receives a message from the CAN interface at the specified address.
+	 * @param frame CAN frame to get the incoming data.
+	 * @param flags The option bit flags. See send(2).
+	 * @param srcAddr Receives the address of the peer that sent the
+	 *  			   message
+	 * @return The number of bytes read or @em -1 on error.
+	 */
+	ssize_t recv_from(can_frame* frame, int flags, can_address* srcAddr=nullptr);
+	/**
+	 * Receives a message on the socket.
+	 * @param frame CAN frame to get the incoming data.
+	 * @param flags The option bit flags. See send(2).
+	 * @return The number of bytes read or @em -1 on error.
+	 */
+	ssize_t recv(can_frame* frame, int flags=0) {
+		return check_ret(::recv(handle(), frame, sizeof(can_frame), flags));
+	}
+};
+
+}   // End namespace socket-cpp
+}   // End namespace components
+}   // End namespace osdev
diff --git b/include/socket-cpp/connector.h a/include/socket-cpp/connector.h
new file mode 100644
index 0000000..d6a382a
--- /dev/null
+++ a/include/socket-cpp/connector.h
@@ -0,0 +1,147 @@
+#pragma once
+
+#include "socket-cpp/stream_socket.h"
+#include "socket-cpp/sock_address.h"
+
+namespace osdev {
+namespace components {
+namespace socket-cpp {
+
+/**
+ * Class to create a client stream connection.
+ * This is a base class for creating active, streaming sockets that initiate
+ * connections to a server. It can be used to derive classes that implement
+ * TCP on IPv4 or IPv6.
+ */
+class connector : public stream_socket
+{
+    /** The base class */
+	using base = stream_socket;
+
+	// Non-copyable
+	connector(const connector&) =delete;
+	connector& operator=(const connector&) =delete;
+
+public:
+	/**
+	 * Creates an unconnected connector.
+	 */
+	connector() {}
+	/**
+	 * Creates the connector and attempts to connect to the specified
+	 * address.
+	 * @param addr The remote server address.
+	 */
+	connector(const sock_address& addr) { connect(addr); }
+	/**
+	 * Move constructor.
+	 * Creates a connector by moving the other connector to this one.
+	 * @param conn Another connector.
+	 */
+	connector(connector&& conn) : base(std::move(conn)) {}
+	/**
+	 * Move assignment.
+	 * @param rhs The other connector to move into this one.
+	 * @return A reference to this object.
+	 */
+	connector& operator=(connector&& rhs) {
+		base::operator=(std::move(rhs));
+		return *this;
+	}
+	/**
+	 * Determines if the socket connected to a remote host.
+	 * Note that this is not a reliable determination if the socket is
+	 * currently connected, but rather that an initial connection was
+	 * established.
+	 * @return @em true If the socket connected to a remote host,
+	 *  	   @em false if not.
+	 */
+	bool is_connected() const { return is_open(); }
+	/**
+     * Attempts to connect to the specified server.
+     * If the socket is currently connected, this will close the current
+     * connection and open the new one.
+	 * @param addr The remote server address.
+	 * @return @em true on success, @em false on error
+	 */
+	bool connect(const sock_address& addr);
+};
+
+/**
+ * Class to create a client TCP connection.
+ */
+template <typename STREAM_SOCK, typename ADDR=typename STREAM_SOCK::addr_t>
+class connector_tmpl : public connector
+{
+    /** The base class */
+	using base = connector;
+
+	// Non-copyable
+	connector_tmpl(const connector_tmpl&) =delete;
+	connector_tmpl& operator=(const connector_tmpl&) =delete;
+
+public:
+	/** The type of streaming socket from the acceptor. */
+	using stream_sock_t = STREAM_SOCK;
+	/** The type of address for the connector. */
+	using addr_t = ADDR;
+
+	/**
+	 * Creates an unconnected connector.
+	 */
+	connector_tmpl() {}
+	/**
+	 * Creates the connector and attempts to connect to the specified
+	 * address.
+	 * @param addr The remote server address.
+	 */
+	connector_tmpl(const addr_t& addr) : base(addr) {}
+	/**
+	 * Move constructor.
+	 * Creates a connector by moving the other connector to this one.
+	 * @param conn Another connector.
+	 */
+	connector_tmpl(connector_tmpl&& rhs) : base(std::move(rhs)) {}
+	/**
+	 * Move assignment.
+	 * @param rhs The other connector to move into this one.
+	 * @return A reference to this object.
+	 */
+	connector_tmpl& operator=(connector_tmpl&& rhs) {
+		base::operator=(std::move(rhs));
+		return *this;
+	}
+	/**
+	 * Gets the local address to which the socket is bound.
+	 * @return The local address to which the socket is bound.
+	 * @throw sys_error on error
+	 */
+	addr_t address() const { return addr_t(base::address()); }
+	/**
+	 * Gets the address of the remote peer, if this socket is connected.
+	 * @return The address of the remote peer, if this socket is connected.
+	 * @throw sys_error on error
+	 */
+	addr_t peer_address() const { return addr_t(base::peer_address()); }
+	/**
+	 * Binds the socket to the specified address.
+	 * This call is optional for a client connector, though it is rarely
+	 * used.
+	 * @param addr The address to which we get bound.
+	 * @return @em true on success, @em false on error
+	 */
+	bool bind(const addr_t& addr) { return base::bind(addr); }
+	/**
+	 * Attempts to connects to the specified server.
+	 * If the socket is currently connected, this will close the current
+	 * connection and open the new one.
+	 * @param addr The remote server address.
+	 * @return @em true on success, @em false on error
+	 */
+	bool connect(const addr_t& addr) { return base::connect(addr); }
+};
+
+}   // End namespace socket-cpp
+}   // End namespace components
+}   // End namespace osdev
+
diff --git b/include/socket-cpp/datagram_socket.h a/include/socket-cpp/datagram_socket.h
new file mode 100644
index 0000000..f57704b
--- /dev/null
+++ a/include/socket-cpp/datagram_socket.h
@@ -0,0 +1,350 @@
+#pragma once
+
+#include "socket-cpp/socket.h"
+
+namespace osdev {
+namespace components {
+namespace socket-cpp {
+
+/**
+ * Base class for datagram sockets.
+ *
+ * Datagram sockets are normally connectionless, where each packet is
+ * individually routed and delivered.
+ */
+class datagram_socket : public socket
+{
+	/** The base class   */
+	using base = socket;
+
+	// Non-copyable
+	datagram_socket(const datagram_socket&) =delete;
+	datagram_socket& operator=(const datagram_socket&) =delete;
+
+protected:
+	static socket_t create_handle(int domain, int protocol=0) {
+		return socket_t(::socket(domain, COMM_TYPE, protocol));
+	}
+
+	static socket_t create_handle(int domain, int type, int protocol) {
+		return socket_t(::socket(domain, type, protocol));
+	}
+
+public:
+	/** The socket 'type' for communications semantics. */
+	static constexpr int COMM_TYPE = SOCK_DGRAM;
+
+	/**
+	 * Creates an uninitialized datagram socket.
+	 */
+	datagram_socket() {}
+	/**
+     * Creates a datagram socket from an existing OS socket handle and
+     * claims ownership of the handle.
+	 * @param handle A socket handle from the operating system.
+	 */
+	explicit datagram_socket(socket_t handle) : base(handle) {}
+	/**
+	 * Creates a UDP socket and binds it to the address.
+	 * @param addr The address to bind.
+	 */
+	explicit datagram_socket(const sock_address& addr);
+	/**
+	 * Move constructor.
+	 * @param other The other socket to move to this one
+	 */
+	datagram_socket(datagram_socket&& other)
+		: base(std::move(other)) {}
+	/**
+	 * Move assignment.
+	 * @param rhs The other socket to move into this one.
+	 * @return A reference to this object.
+	 */
+	datagram_socket& operator=(datagram_socket&& rhs) {
+		base::operator=(std::move(rhs));
+		return *this;
+	}
+	/**
+	 * Connects the socket to the remote address.
+	 * In the case of datagram sockets, this does not create an actual
+	 * connection, but rather specifies the address to which datagrams are
+	 * sent by default and the only address from which packets are
+	 * received.
+	 * @param addr The address on which to "connect".
+	 * @return @em true on success, @em false on failure
+	 */
+	bool connect(const sock_address& addr) {
+		return check_ret_bool(::connect(handle(), addr.sockaddr_ptr(),
+										addr.size()));
+	}
+
+	// ----- I/O -----
+
+	/**
+	 * Sends a message to the socket at the specified address.
+	 * @param buf The data to send.
+	 * @param n The number of bytes in the data buffer.
+	 * @param flags The flags. See send(2).
+	 * @param addr The remote destination of the data.
+	 * @return the number of bytes sent on success or, @em -1 on failure.
+	 */
+	ssize_t send_to(const void* buf, size_t n, int flags, const sock_address& addr) {
+        #if defined(_WIN32)
+            return check_ret(::sendto(handle(), reinterpret_cast<const char*>(buf), int(n),
+                                      flags, addr.sockaddr_ptr(), addr.size()));
+        #else
+            return check_ret(::sendto(handle(), buf, n, flags,
+                                      addr.sockaddr_ptr(), addr.size()));
+        #endif
+	}
+	/**
+	 * Sends a string to the socket at the specified address.
+	 * @param s The string to send.
+	 * @param flags The flags. See send(2).
+	 * @param addr The remote destination of the data.
+	 * @return the number of bytes sent on success or, @em -1 on failure.
+	 */
+	ssize_t send_to(const std::string& s, int flags, const sock_address& addr) {
+		return send_to(s.data(), s.length(), flags, addr);
+	}
+	/**
+	 * Sends a message to another socket.
+	 * @param buf The data to send.
+	 * @param n The number of bytes in the data buffer.
+	 * @param addr The remote destination of the data.
+	 * @return the number of bytes sent on success or, @em -1 on failure.
+	 */
+	ssize_t send_to(const void* buf, size_t n, const sock_address& addr) {
+		return send_to(buf, n, 0, addr);
+	}
+	/**
+	 * Sends a string to another socket.
+	 * @param s The string to send.
+	 * @param addr The remote destination of the data.
+	 * @return the number of bytes sent on success or, @em -1 on failure.
+	 */
+	ssize_t send_to(const std::string& s, const sock_address& addr) {
+		return send_to(s.data(), s.length(), 0, addr);
+	}
+	/**
+	 * Sends a message to the socket at the default address.
+	 * The socket should be connected before calling this.
+	 * @param buf The date to send.
+	 * @param n The number of bytes in the data buffer.
+	 * @param flags The option bit flags. See send(2).
+	 * @return @em zero on success, @em -1 on failure.
+	 */
+	ssize_t send(const void* buf, size_t n, int flags=0) {
+        #if defined(_WIN32)
+            return check_ret(::send(handle(), reinterpret_cast<const char*>(buf),
+                                    int(n), flags));
+        #else
+            return check_ret(::send(handle(), buf, n, flags));
+        #endif
+	}
+	/**
+	 * Sends a string to the socket at the default address.
+	 * The socket should be connected before calling this
+	 * @param s The string to send.
+	 * @param flags The option bit flags. See send(2).
+	 * @return @em zero on success, @em -1 on failure.
+	 */
+	ssize_t send(const std::string& s, int flags=0) {
+		return send(s.data(), s.length(), flags);
+	}
+	/**
+	 * Receives a message on the socket.
+	 * @param buf Buffer to get the incoming data.
+	 * @param n The number of bytes to read.
+	 * @param flags The option bit flags. See send(2).
+	 * @param srcAddr Receives the address of the peer that sent the
+	 *  			   message
+	 * @return The number of bytes read or @em -1 on error.
+	 */
+	ssize_t recv_from(void* buf, size_t n, int flags, sock_address* srcAddr=nullptr);
+	/**
+	 * Receives a message on the socket.
+	 * @param buf Buffer to get the incoming data.
+	 * @param n The number of bytes to read.
+	 * @param srcAddr Receives the address of the peer that sent the
+	 *  			   message
+	 * @return The number of bytes read or @em -1 on error.
+	 */
+	ssize_t recv_from(void* buf, size_t n, sock_address* srcAddr=nullptr) {
+		return recv_from(buf, n, 0, srcAddr);
+	}
+	/**
+	 * Receives a message on the socket.
+	 * @param buf Buffer to get the incoming data.
+	 * @param n The number of bytes to read.
+	 * @param flags The option bit flags. See send(2).
+	 * @return The number of bytes read or @em -1 on error.
+	 */
+	ssize_t recv(void* buf, size_t n, int flags=0) {
+        #if defined(_WIN32)
+            return check_ret(::recv(handle(), reinterpret_cast<char*>(buf),
+                                    int(n), flags));
+        #else
+            return check_ret(::recv(handle(), buf, n, flags));
+        #endif
+	}
+};
+
+/**
+ * Base class for datagram sockets.
+ *
+ * Datagram sockets are normally connectionless, where each packet is
+ * individually routed and delivered.
+ */
+template <typename ADDR>
+class datagram_socket_tmpl : public datagram_socket
+{
+    /** The base class */
+    using base = datagram_socket;
+
+public:
+    /** The address family for this type of address */
+	static constexpr sa_family_t ADDRESS_FAMILY = ADDR::ADDRESS_FAMILY;
+	/** The type of address for the socket. */
+	using addr_t = ADDR;
+
+	/**
+	 * Creates an unbound datagram socket.
+	 * This can be used as a client or later bound as a server socket.
+	 */
+	datagram_socket_tmpl() : base(create_handle(ADDRESS_FAMILY)) {}
+	/**
+     * Creates a datagram socket from an existing OS socket handle and
+     * claims ownership of the handle.
+	 * @param handle A socket handle from the operating system.
+	 */
+	datagram_socket_tmpl(socket_t handle) : base(handle) {}
+	/**
+	 * Creates a datagram socket and binds it to the address.
+	 * @param addr The address to bind.
+	 */
+	datagram_socket_tmpl(const ADDR& addr) : base(addr) {}
+	/**
+	 * Move constructor.
+	 * @param other The other socket to move to this one
+	 */
+	datagram_socket_tmpl(datagram_socket_tmpl&& other)
+		: base(std::move(other)) {}
+	/**
+	 * Move assignment.
+	 * @param rhs The other socket to move into this one.
+	 * @return A reference to this object.
+	 */
+	datagram_socket_tmpl& operator=(datagram_socket_tmpl&& rhs) {
+		base::operator=(std::move(rhs));
+		return *this;
+	}
+
+	/**
+	 * Creates a pair of connected stream sockets.
+	 *
+	 * Whether this will work at all is highly system and domain dependent.
+	 * Currently it is only known to work for Unix-domain sockets on *nix
+	 * systems.
+	 *
+	 * @param protocol The protocol to be used with the socket. (Normally 0)
+	 *
+	 * @return A std::tuple of stream sockets. On error both sockets will be
+	 *  	   in an error state with the last error set.
+	 */
+	static std::tuple<datagram_socket_tmpl, datagram_socket_tmpl> pair(int protocol=0) {
+		auto pr = base::pair(addr_t::ADDRESS_FAMILY, COMM_TYPE, protocol);
+		return std::make_tuple<datagram_socket_tmpl, datagram_socket_tmpl>(
+			datagram_socket_tmpl{std::get<0>(pr).release()},
+			datagram_socket_tmpl{std::get<1>(pr).release()});
+	}
+	/**
+	 * Binds the socket to the local address.
+	 * Datagram sockets can bind to a local address/adapter to filter which
+	 * incoming packets to receive.
+	 * @param addr The address on which to bind.
+	 * @return @em true on success, @em false on failure
+	 */
+	bool bind(const ADDR& addr) { return base::bind(addr); }
+	/**
+	 * Connects the socket to the remote address.
+	 * In the case of datagram sockets, this does not create an actual
+	 * connection, but rather specifies the address to which datagrams are
+	 * sent by default and the only address from which packets are
+	 * received.
+	 * @param addr The address on which to "connect".
+	 * @return @em true on success, @em false on failure
+	 */
+	bool connect(const ADDR& addr) { return base::connect(addr); }
+
+	// ----- I/O -----
+
+	/**
+	 * Sends a message to the socket at the specified address.
+	 * @param buf The data to send.
+	 * @param n The number of bytes in the data buffer.
+	 * @param flags The option bit flags. See send(2).
+	 * @param addr The remote destination of the data.
+	 * @return the number of bytes sent on success or, @em -1 on failure.
+	 */
+	ssize_t send_to(const void* buf, size_t n, int flags, const ADDR& addr) {
+		return base::send_to(buf, n, flags, addr);
+	}
+	/**
+	 * Sends a string to the socket at the specified address.
+	 * @param s The string to send.
+	 * @param flags The flags. See send(2).
+	 * @param addr The remote destination of the data.
+	 * @return the number of bytes sent on success or, @em -1 on failure.
+	 */
+	ssize_t send_to(const std::string& s, int flags, const ADDR& addr) {
+		return base::send_to(s, flags, addr);
+	}
+	/**
+	 * Sends a message to another socket.
+	 * @param buf The data to send.
+	 * @param n The number of bytes in the data buffer.
+	 * @param addr The remote destination of the data.
+	 * @return the number of bytes sent on success or, @em -1 on failure.
+	 */
+	ssize_t send_to(const void* buf, size_t n, const ADDR& addr) {
+		return base::send_to(buf, n, 0, addr);
+	}
+	/**
+	 * Sends a string to another socket.
+	 * @param s The string to send.
+	 * @param addr The remote destination of the data.
+	 * @return the number of bytes sent on success or, @em -1 on failure.
+	 */
+	ssize_t send_to(const std::string& s, const ADDR& addr) {
+		return base::send_to(s, addr);
+	}
+	/**
+	 * Receives a message on the socket.
+	 * @param buf Buffer to get the incoming data.
+	 * @param n The number of bytes to read.
+	 * @param flags The option bit flags. See send(2).
+	 * @param srcAddr Receives the address of the peer that sent
+	 *  			the message
+	 * @return The number of bytes read or @em -1 on error.
+	 */
+	ssize_t recv_from(void* buf, size_t n, int flags, ADDR* srcAddr) {
+		return base::recv_from(buf, n, flags, srcAddr);
+	}
+	/**
+	 * Receives a message on the socket.
+	 * @param buf Buffer to get the incoming data.
+	 * @param n The number of bytes to read.
+	 * @param srcAddr Receives the address of the peer that sent
+	 *  			the message
+	 * @return The number of bytes read or @em -1 on error.
+	 */
+	ssize_t recv_from(void* buf, size_t n, ADDR* srcAddr=nullptr) {
+		return base::recv_from(buf, n, srcAddr);
+	}
+};
+
+}   // End namespace socket-cpp
+}   // End namespace components
+}   // End namespace osdev
+
diff --git b/include/socket-cpp/exception.h a/include/socket-cpp/exception.h
new file mode 100644
index 0000000..8e24c18
--- /dev/null
+++ a/include/socket-cpp/exception.h
@@ -0,0 +1,80 @@
+#pragma once
+
+#include <cerrno>
+#include <stdexcept>
+#include <string>
+
+namespace osdev {
+namespace components {
+namespace socket-cpp {
+
+/**
+ * System error.
+ * These are errors that are resulted from system socket calls. The error
+ * codes are platform 'errno' values (or similar), and the messages are
+ * typically derived from the system.
+ */
+class sys_error : public std::runtime_error
+{
+	/** The system error number (errno) */
+	int errno_;
+
+public:
+	/**
+	 * Creates an error using the current system 'errno' value.
+	 */
+	sys_error() : sys_error(errno) {}
+	/**
+	 * Constructs an error with the specified system errno.
+	 * @param err The error number. This is the system errno value.
+	 */
+	explicit sys_error(int err);
+	/**
+	 * Get the error number.
+	 * @return The system error number.
+	 */
+	int error() const { return errno_; }
+    /**
+     * Gets a string describing the specified error.
+     * This is typically the returned message from the system strerror().
+     * @param err The system error number.
+     * @return A string describing the specified error.
+     */
+    static std::string error_str(int err);
+};
+
+/**
+ * Errors from getaddrinfo.
+ * These are errors relating to DNS lookup, returned from the getaddrinfo system call.
+ * Their codes are declared in <netdb.h>.
+ */
+class getaddrinfo_error : public std::runtime_error
+{
+	/** The error number, as returned by getaddrinfo. */
+	int error_;
+    /** The hostname being resolved. */
+    std::string hostname_;
+
+public:
+	/**
+	 * Constructs an error with the specified getaddrinfo error code.
+	 * @param err The error number, as returned by getaddrinfo.
+     * @param hostname The DNS name being resolved that triggered the error.
+	 */
+	getaddrinfo_error(int err, const std::string &hostname);
+	/**
+	 * Get the error number.
+	 * @return The error number returned by getaddrinfo.
+	 */
+	int error() const { return error_; }
+    /**
+     * Get the hostname that triggered the error.
+     * @return The hostname that getaddrinfo failed to resolve.
+     */
+    const std::string &hostname() const { return hostname_; }
+};
+
+}   // End namespace socket-cpp
+}   // End namespace components
+}   // End namespace osdev
+
diff --git b/include/socket-cpp/inet6_address.h a/include/socket-cpp/inet6_address.h
new file mode 100644
index 0000000..c80fae5
--- /dev/null
+++ a/include/socket-cpp/inet6_address.h
@@ -0,0 +1,200 @@
+#pragma once
+
+#include "socket-cpp/platform.h"
+#include "socket-cpp/sock_address.h"
+#include <iostream>
+#include <string>
+#include <cstring>
+
+namespace osdev {
+namespace components {
+namespace socket-cpp {
+
+/**
+ * Class that represents an internet (IPv6) address. This inherits from the
+ * IP-specific form of a socket address, @em sockaddr_in.
+ */
+class inet6_address : public sock_address
+{
+	/** The underlying C IPv6 struct  */
+	sockaddr_in6 addr_;
+
+	/** The size of the underlying address struct, in bytes */
+	static constexpr size_t SZ = sizeof(sockaddr_in6);
+
+public:
+    /** The address family for this type of address */
+	static constexpr sa_family_t ADDRESS_FAMILY = AF_INET6;
+
+	/**
+	 * Constructs an empty address.
+	 * The address is initialized to all zeroes.
+	 */
+	inet6_address() : addr_() {}
+	/**
+	 * Constructs an address for any iface using the specified port.
+	 * This is a convenient way for a server to specify an address that will
+	 * bind to all interfaces.
+	 * @param port The port number in native/host byte order.
+	 */
+	explicit inet6_address(in_port_t port) {
+		const in6_addr ANY IN6ADDR_ANY_INIT;
+		create(ANY, port);
+	}
+	/**
+	 * Constructs an address using the name of the host and the specified
+	 * port. This attempts to resolve the host name to an address.
+	 *
+	 * @param saddr The name of the host.
+	 * @param port The port number in native/host byte order.
+	 */
+	inet6_address(const std::string& saddr, in_port_t port) {
+		create(saddr, port);
+	}
+	/**
+     * Constructs the address by copying the specified structure.
+     * TODO: Do we actually need a conversion from something that's
+     * cast to a sockaddr, but is really a sockaddr_in6?
+	 * @param addr The other address
+	 */
+	inet6_address(const sockaddr_storage& addr) {
+		std::memcpy(&addr_, &addr, SZ);
+	}
+	/**
+	 * Constructs the address by copying the specified structure.
+	 * @param addr The other address
+	 */
+	inet6_address(const sock_address& addr) {
+		std::memcpy(&addr_, addr.sockaddr_ptr(), SZ);
+	}
+	/**
+	 * Constructs the address by copying the specified structure.
+	 * @param addr The other address
+	 */
+	inet6_address(const sockaddr_in6& addr) {
+		std::memcpy(&addr_, &addr, SZ);
+	}
+	/**
+	 * Constructs the address by copying the specified address.
+	 * @param addr The other address
+	 */
+	inet6_address(const inet6_address& addr) : addr_(addr.addr_) {}
+    /**
+     * Creates an address on the loopback (localhost) interface.
+     * @param port The port number (in native/host byte order).
+     * @return The full address on the loopback interface.
+     */
+    static inet6_address loopback(in_port_t port) {
+		const in6_addr LOOPBACK IN6ADDR_LOOPBACK_INIT;
+        inet6_address addr;
+        addr.create(LOOPBACK, port);
+        return addr;
+	}
+	/**
+	 * Checks if the address is set to some value.
+	 * This doesn't attempt to determine if the address is valid, simply
+	 * that it's not all zero.
+	 * @return bool
+	 */
+	bool is_set() const;
+	/**
+	 * Attempts to resolve the host name into a 32-bit internet address.
+	 * @param saddr The string host name.
+	 * @return The internet address in network byte order.
+	 */
+	static in6_addr resolve_name(const std::string& saddr);
+	/**
+	 * Creates the socket address using the specified host address and port
+	 * number.
+	 * @param addr The host address (16-byte IPv6 address).
+	 * @param port The host port number.
+	 */
+    void create(const in6_addr& addr, in_port_t port);
+	/**
+	 * Creates the socket address using the specified host name and port
+	 * number.
+	 * @param saddr The string host name.
+	 * @param port The port number in native/host byte order.
+	 */
+	void create(const std::string& saddr, in_port_t port);
+	/**
+     * Gets 128-bit IPv6 address.
+     * The address is usually stored in network byte order.
+	 * @return The IPv6 address.
+	 */
+	in6_addr address() const { return addr_.sin6_addr; }
+	/**
+     * Gets a byte of the 128-bit IPv6 Address.
+     * Note that the address is normally stored in network byte
+     * order.
+	 * @param i The byte to read (0-7)
+{
+	return addr_ != sockaddr_in6{};
+	 * @return The specified byte in the 128-bit IPv6 Address
+	 */
+	uint8_t operator[](int i) const {
+		return addr_.sin6_addr.s6_addr[i];
+	}
+	/**
+	 * Gets the port number.
+	 * @return The port number in native/host byte order.
+	 */
+	in_port_t port() const { return ntohs(addr_.sin6_port); }
+	/**
+	 * Gets the size of this structure.
+	 * This is equivalent to sizeof(this) but more convenient in some
+	 * places.
+	 * @return The size of this structure.
+	 */
+	socklen_t size() const override { return socklen_t(SZ); }
+	/**
+	 * Gets a pointer to this object cast to a @em sockaddr.
+	 * @return A pointer to this object cast to a @em sockaddr.
+	 */
+	const sockaddr* sockaddr_ptr() const override {
+		return reinterpret_cast<const sockaddr*>(&addr_);
+	}
+	/**
+	 * Gets a pointer to this object cast to a @em sockaddr.
+	 * @return A pointer to this object cast to a @em sockaddr.
+	 */
+	sockaddr* sockaddr_ptr() override {
+		return reinterpret_cast<sockaddr*>(&addr_);
+	}
+	/**
+     * Gets a const pointer to this object cast to a @em
+     * sockaddr_in6.
+	 * @return const sockaddr_in6 pointer to this object.
+	 */
+	const sockaddr_in6* sockaddr_in6_ptr() const { return &addr_; }
+	/**
+	 * Gets a pointer to this object cast to a @em sockaddr_in6.
+	 * @return sockaddr_in6 pointer to this object.
+	 */
+	sockaddr_in6* sockaddr_in6_ptr() { return &addr_; }
+	/**
+	 * Gets a printable string for the address.
+     * This gets the address in the printable form "[addr]:port"
+     * using inet_ntop(). It does not attempt to find the host name
+     * using a lookup.
+	 * @return A string representation of the address in the form 
+	 *  	   '[address]:port'
+	 */
+	std::string to_string() const;
+};
+
+// --------------------------------------------------------------------------
+
+/**
+ * Stream inserter for the address. 
+ * This uses the simple dot notation of the address as returned from 
+ * inet_ntop(). It does not attempt a host lookup.
+ * @param os The output stream
+ * @param addr The address
+ * @return A reference to the output stream.
+ */
+std::ostream& operator<<(std::ostream& os, const inet6_address& addr);
+
+}   // End namespace socket-cpp
+}   // End namespace components
+}   // End namespace osdev
diff --git b/include/socket-cpp/inet_address.h a/include/socket-cpp/inet_address.h
new file mode 100644
index 0000000..409c21a
--- /dev/null
+++ a/include/socket-cpp/inet_address.h
@@ -0,0 +1,192 @@
+#pragma once
+
+#include "socket-cpp/sock_address.h"
+#include <iostream>
+#include <string>
+#include <cstring>
+#include <algorithm>
+
+namespace osdev {
+namespace components {
+namespace socket-cpp {
+
+/**
+ * Class that represents an internet (IPv4) address.
+ * This inherits from the IP-specific form of a socket address,
+ * @em sockaddr_in.
+ */
+class inet_address : public sock_address
+{
+	/** The underlying C struct for IPv4 addresses */
+	sockaddr_in addr_;
+
+	/** The size of the underlying address struct, in bytes */
+	static constexpr size_t SZ = sizeof(sockaddr_in);
+
+public:
+    /** The address family for this type of address */
+	static constexpr sa_family_t ADDRESS_FAMILY = AF_INET;
+
+	/**
+	 * Constructs an empty address.
+	 * The address is initialized to all zeroes.
+	 */
+	inet_address() : addr_() {}
+	/**
+     * Constructs an address for any iface using the specified port.
+     * This is a convenient way for a server to specify an address that will
+     * bind to all interfaces.
+	 * @param port The port number in native/host byte order.
+	 */
+	explicit inet_address(in_port_t port) {
+		create(in_addr_t(INADDR_ANY), port);
+	}
+	/**
+	 * Constructs an address for the specified host using the specified
+	 * port.
+	 * @param addr The 32-bit host address in native/host byte order.
+	 * @param port The port number in native/host byte order.
+	 */
+	inet_address(uint32_t addr, in_port_t port) { create(addr, port); }
+	/**
+	 * Constructs an address using the name of the host and the specified
+	 * port. This attempts to resolve the host name to an address.
+	 *
+	 * @param saddr The name of the host.
+	 * @param port The port number in native/host byte order.
+	 */
+	inet_address(const std::string& saddr, in_port_t port) {
+		create(saddr, port);
+	}
+	/**
+	 * Constructs the address by copying the specified structure.
+	 * @param addr The other address
+	 */
+	inet_address(const sockaddr& addr) {
+		std::memcpy(&addr_, &addr, SZ);
+	}
+	/**
+	 * Constructs the address by copying the specified structure.
+	 * @param addr The other address
+	 */
+	inet_address(const sock_address& addr) {
+		std::memcpy(&addr_, addr.sockaddr_ptr(), SZ);
+	}
+	/**
+	 * Constructs the address by copying the specified structure.
+	 * @param addr The other address
+	 */
+	inet_address(const sockaddr_in& addr) : addr_(addr) {}
+	/**
+	 * Constructs the address by copying the specified address.
+	 * @param addr The other address
+	 */
+	inet_address(const inet_address& addr) : addr_(addr.addr_) {}
+	/**
+	 * Checks if the address is set to some value.
+	 * This doesn't attempt to determine if the address is valid, simply
+	 * that it's not all zero.
+	 * @return bool
+	 */
+	bool is_set() const;
+	/**
+	 * Attempts to resolve the host name into a 32-bit internet address.
+	 * @param saddr The string host name.
+	 * @return The internet address in network byte order.
+     * @throw sys_error, getaddrinfo_error
+	 */
+	static in_addr_t resolve_name(const std::string& saddr);
+	/**
+	 * Creates the socket address using the specified host address and port
+	 * number.
+	 * @param addr The host address.
+	 * @param port The host port number.
+	 */
+	void create(in_addr_t addr, in_port_t port);
+	/**
+	 * Creates the socket address using the specified host name and port
+	 * number.
+	 * @param saddr The string host name.
+	 * @param port The port number in native/host byte order.
+     * @throw sys_error, getaddrinfo_error
+	 */
+	void create(const std::string& saddr, in_port_t port);
+	/**
+	 * Gets the 32-bit internet address.
+	 * @return The internet address in the local host's byte order.
+	 */
+	in_addr_t address() const { return ntohl(addr_.sin_addr.s_addr); }
+	/**
+	 * Gets a byte of the 32-bit Internet Address
+	 * @param i The byte to read (0-3)
+	 * @return The specified byte in the 32-bit Internet Address
+	 */
+	uint8_t operator[](int i) const {
+		in_addr_t addr = address();
+		return ((const uint8_t*)&addr)[i];
+	}
+	/**
+	 * Gets the port number.
+	 * @return The port number in native/host byte order.
+	 */
+	in_port_t port() const { return ntohs(addr_.sin_port); }
+	/**
+	 * Gets the size of this structure.
+	 * This is equivalent to sizeof(this) but more convenient in some
+	 * places.
+	 * @return The size of this structure.
+	 */
+	socklen_t size() const override { return socklen_t(SZ); }
+	/**
+	 * Gets a pointer to this object cast to a @em sockaddr.
+	 * @return A pointer to this object cast to a @em sockaddr.
+	 */
+	const sockaddr* sockaddr_ptr() const override {
+		return reinterpret_cast<const sockaddr*>(&addr_);
+	}
+	/**
+	 * Gets a pointer to this object cast to a @em sockaddr.
+	 * @return A pointer to this object cast to a @em sockaddr.
+	 */
+	sockaddr* sockaddr_ptr() override {
+		return reinterpret_cast<sockaddr*>(&addr_);
+	}
+	/**
+	 * Gets a const pointer to this object cast to a @em sockaddr_in.
+	 * @return const sockaddr_in pointer to this object.
+	 */
+	const sockaddr_in* sockaddr_in_ptr() const {
+		return static_cast<const sockaddr_in*>(&addr_);
+	}
+	/**
+	 * Gets a pointer to this object cast to a @em sockaddr_in.
+	 * @return sockaddr_in pointer to this object.
+	 */
+	sockaddr_in* sockaddr_in_ptr() {
+		return static_cast<sockaddr_in*>(&addr_);
+	}
+	/**
+	 * Gets a printable string for the address.
+	 * This gets the simple dot notation of the address as returned from 
+	 * inet_ntop(). It does not attempt a host lookup.
+	 * @return A string representation of the address in the form 
+	 *  	   'address:port'
+	 */
+	std::string to_string() const;
+};
+
+// --------------------------------------------------------------------------
+
+/**
+ * Stream inserter for the address. 
+ * This uses the simple dot notation of the address as returned from 
+ * inet_ntop(). It does not attempt a host lookup.
+ * @param os The output stream
+ * @param addr The address
+ * @return A reference to the output stream.
+ */
+std::ostream& operator<<(std::ostream& os, const inet_address& addr);
+
+}   // End namespace socket-cpp
+}   // End namespace components
+}   // End namespace osdev
diff --git b/include/socket-cpp/platform.h a/include/socket-cpp/platform.h
new file mode 100644
index 0000000..3641795
--- /dev/null
+++ a/include/socket-cpp/platform.h
@@ -0,0 +1,72 @@
+#pragma once
+
+namespace osdev {
+namespace components {
+namespace socket-cpp {
+
+#include <cstdint>
+
+#if defined(_WIN32)
+	//#pragma warning(4 : 4996)	// Deprecated functions (CRT & all)
+	//#pragma warning(4 : 4250)	// Inheritance via dominance
+
+	#if !defined(WIN32_LEAN_AND_MEAN)
+		#define WIN32_LEAN_AND_MEAN
+	#endif
+
+	#if !defined(_CRT_SECURE_NO_DEPRECATE)
+		#define _CRT_SECURE_NO_DEPRECATE
+	#endif
+
+	//#include <cstddef>
+	//#include <windows.h>
+	#include <winsock2.h>
+	#include <ws2tcpip.h>
+
+	#define SOCKPP_SOCKET_T_DEFINED
+	using socket_t = SOCKET;
+
+	using socklen_t = int;
+	using in_port_t = uint16_t;
+	using in_addr_t = uint32_t;
+
+	using sa_family_t = u_short;
+
+	#ifndef _SSIZE_T_DEFINED 
+		#define _SSIZE_T_DEFINED 
+		#undef ssize_t
+        using ssize_t = SSIZE_T;
+	#endif // _SSIZE_T_DEFINED
+
+    #ifndef _SUSECONDS_T
+        #define _SUSECONDS_T
+        typedef long suseconds_t;	// signed # of microseconds in timeval
+    #endif	// _SUSECONDS_T
+ 
+    #define SHUT_RD SD_RECEIVE
+    #define SHUT_WR SD_SEND
+    #define SHUT_RDWR SD_BOTH
+
+    struct iovec
+    {
+        void* iov_base;
+		size_t iov_len;
+    };
+
+#else
+	#include <unistd.h>
+	#include <sys/socket.h>
+	#include <sys/uio.h>
+	#include <arpa/inet.h>
+	#ifdef __FreeBSD__
+		#include <netinet/in.h>
+	#endif
+	#include <netdb.h>
+	#include <signal.h>
+	#include <cerrno>
+#endif
+
+}   // End namespace socket-cpp
+}   // End namespace components
+}   // End namespace osdev
+
diff --git b/include/socket-cpp/sock_address.h a/include/socket-cpp/sock_address.h
new file mode 100644
index 0000000..2a1c0d6
--- /dev/null
+++ a/include/socket-cpp/sock_address.h
@@ -0,0 +1,153 @@
+#pragma once
+
+#include "socket-cpp/platform.h"
+#include <cstring>
+#include <stdexcept>
+
+namespace osdev {
+namespace components {
+namespace socket-cpp {
+
+/**
+ * Generic socket address.
+ * Abstract base class for socket addresses. The underlying C socket
+ * functions typically take or return an address as a `sockaddr` pointer and
+ * length. So derived classes that wrap the
+ */
+class sock_address
+{
+public:
+	/**
+	 * Virtual destructor.
+	 */
+	virtual ~sock_address() {}
+	/**
+	 * Gets the size of this structure.
+	 * This is equivalent to sizeof(this) but more convenient in some
+	 * places.
+	 * @return The size of this structure.
+	 */
+	virtual socklen_t size() const =0;
+	/**
+	 * Gets a pointer to this object cast to a @em sockaddr.
+	 * @return A pointer to this object cast to a @em sockaddr.
+	 */
+	virtual sockaddr* sockaddr_ptr() =0;
+	/**
+	 * Gets a pointer to this object cast to a @em sockaddr.
+	 * @return A pointer to this object cast to a @em sockaddr.
+	 */
+	virtual const sockaddr* sockaddr_ptr() const =0;
+	/**
+	 * Gets the network family of the address.
+	 * @return The network family of the address (AF_INET, etc). If the
+	 *  	   address is not known, returns AF_UNSPEC.
+	 */
+	virtual sa_family_t family() const {
+		auto p = sockaddr_ptr();
+		return p ? p->sa_family : AF_UNSPEC;
+	}
+};
+
+/**
+ * Generic socket address.
+ *
+ * This is a wrapper around `sockaddr_storage` which can hold any family
+ * address. This should have enough memory to contain any address struct for
+ * the system on which it is compiled.
+ */
+class sock_address_any : public sock_address
+{
+    /** Storage for any kind of socket address */
+    sockaddr_storage addr_;
+	/** Length of the address (in bytes) */
+	socklen_t sz_;
+
+	/** The maximum size of an address, in bytes */
+	static constexpr size_t MAX_SZ = sizeof(sockaddr_storage);
+
+public:
+	/**
+	 * Constructs an empty address.
+	 * The address is initialized to all zeroes.
+	 */
+	sock_address_any() : addr_{}, sz_{MAX_SZ} {}
+	/**
+	 * Constructs an address.
+	 * @param addr Pointer to a buffer holding the address.
+	 * @param n The number of valid bytes in the address
+	 * @throws std::length_error if `n` is greater than the maximum size of
+	 *  		  an address.
+	 */
+	sock_address_any(const sockaddr* addr, socklen_t n) {
+		if (n > MAX_SZ)
+			throw std::length_error("Address length out of range");
+        std::memcpy(&addr_, addr, sz_ = n);
+    }
+	/**
+	 * Constructs an address.
+	 * @param addr The buffer holding the address.
+	 * @param n The number of valid bytes in the address
+	 * @throws std::length_error if `n` is greater than the maximum size of
+	 *  		  an address.
+	 */
+	sock_address_any(const sockaddr_storage& addr, socklen_t n) {
+		if (n > MAX_SZ)
+			throw std::length_error("Address length out of range");
+        std::memcpy(&addr_, &addr, sz_ = n);
+    }
+	/**
+	 * Copies another address to this one.
+	 * @param addr The other address to copy into this one.
+	 */
+	sock_address_any(const sock_address& addr)
+		: sock_address_any(addr.sockaddr_ptr(), addr.size()) {}
+	/**
+	 * Gets the size of the address.
+	 * @return The size of the address. This is the number of bytes that are a
+	 *  	   valid part of the address.
+	 */
+	socklen_t size() const override { return sz_; }
+	/**
+	 * Gets a pointer to this object cast to a @em sockaddr.
+	 * @return A pointer to this object cast to a @em sockaddr.
+	 */
+	const sockaddr* sockaddr_ptr() const override {
+		return reinterpret_cast<const sockaddr*>(&addr_);
+	}
+	/**
+	 * Gets a pointer to this object cast to a @em sockaddr.
+	 * @return A pointer to this object cast to a @em sockaddr.
+	 */
+	sockaddr* sockaddr_ptr() override {
+		return reinterpret_cast<sockaddr*>(&addr_);
+	}
+};
+
+/**
+ * Determines if the two objects refer to the same address.
+ * @param lhs A socket address
+ * @param rhs A socket address
+ * @return @em true if `lhs` and `rhs` refer to the same address, @em false
+ *  	   otherwise.
+ */
+inline bool operator==(const sock_address& lhs, const sock_address& rhs) {
+    return lhs.size() == rhs.size() &&
+        std::memcmp(lhs.sockaddr_ptr(), rhs.sockaddr_ptr(), lhs.size()) == 0;
+}
+
+/**
+ * Determines if the two objects refer to the different address.
+ * @param lhs A socket address
+ * @param rhs A socket address
+ * @return @em true if `lhs` and `rhs` refer to different address, @em false
+ *  	   if they refer to the same address.
+ */
+inline bool operator!=(const sock_address& lhs, const sock_address& rhs) {
+	return !operator==(lhs, rhs);
+}
+
+}   // End namespace socket-cpp
+}   // End namespace components
+}   // End namespace osdev
+
diff --git b/include/socket-cpp/socket.h a/include/socket-cpp/socket.h
new file mode 100644
index 0000000..f1a2a82
--- /dev/null
+++ a/include/socket-cpp/socket.h
@@ -0,0 +1,515 @@
+/**
+ * @file socket.h
+ *
+ * Classes for TCP & UDP socket.
+ *
+ * @author Frank Pagliughi
+ * @author SoRo Systems, Inc.
+ * @author www.sorosys.com
+ *
+ * @date December 2014
+ */
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2014-2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#ifndef __sockpp_socket_h
+#define __sockpp_socket_h
+
+#include "sockpp/sock_address.h"
+#include <chrono>
+#include <string>
+#include <tuple>
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+#if !defined(SOCKPP_SOCKET_T_DEFINED)
+	typedef int socket_t;				///< The OS socket handle
+	const socket_t INVALID_SOCKET = -1;	///< Invalid socket descriptor
+	#define SOCKPP_SOCKET_T_DEFINED
+#endif
+
+/**
+ * Converts a number of microseconds to a relative timeval.
+ * @param dur A chrono duration of microseconds.
+ * @return A timeval
+ */
+timeval to_timeval(const std::chrono::microseconds& dur);
+
+/**
+ * Converts a chrono duration to a relative timeval.
+ * @param dur A chrono duration.
+ * @return A timeval.
+ */
+template<class Rep, class Period>
+timeval to_timeval(const std::chrono::duration<Rep,Period>& dur) {
+	return to_timeval(std::chrono::duration_cast<std::chrono::microseconds>(dur));
+}
+
+/**
+ * Converts a relative timeval to a chrono duration.
+ * @param tv A timeval.
+ * @return A chrono duration.
+ */
+inline std::chrono::microseconds to_duration(const timeval& tv)
+{
+    auto dur = std::chrono::seconds{tv.tv_sec}
+				+ std::chrono::microseconds{tv.tv_usec};
+    return std::chrono::duration_cast<std::chrono::microseconds>(dur);
+}
+
+/**
+ * Converts an absolute timeval to a chrono time_point.
+ * @param tv A timeval.
+ * @return A chrono time_point.
+ */
+inline std::chrono::system_clock::time_point to_timepoint(const timeval& tv)
+{
+	return std::chrono::system_clock::time_point {
+        std::chrono::duration_cast<std::chrono::system_clock::duration>(to_duration(tv))
+	};
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Base class for socket objects.
+ *
+ * This class wraps an OS socket handle and maintains strict ownership
+ * semantics. If a socket object has a valid, open socket, then it owns the
+ * handle and will close it when the object is destroyed.
+ *
+ * Objects of this class are not copyable, but they are moveable.
+ */
+class socket
+{
+	/** The OS integer socket handle */
+	socket_t handle_;
+	/** Cache of the last error (errno) */
+	mutable int lastErr_;
+	/**
+	 * The OS-specific function to close a socket handle/
+	 * @param h The OS socket handle.
+	 * @return @em true if the sock is closed, @em false on error.
+	 */
+	bool close(socket_t h);
+
+	// Non-copyable.
+	socket(const socket&) =delete;
+	socket& operator=(const socket&) =delete;
+
+protected:
+	/**
+	 * Closes the socket without checking for errors or updating the last
+	 * error.
+	 * This is used in internal open/connect type functions that fail after
+	 * creating the socket, but want to preserve the previous failure
+	 * condition.
+	 * Assumes that the socket handle is valid.
+	 * @return Always returns @em false.
+	 */
+	bool close_on_err() {
+		close(release());
+		return false;
+	}
+	/**
+	 * OS-specific means to retrieve the last error from an operation.
+	 * This should be called after a failed system call to set the
+	 * lastErr_ member variable. Normally this would be called from
+	 * @ref check_ret.
+	 */
+	static int get_last_error();
+	/**
+	 * Cache the last system error code into this object.
+	 * This should be called after a failed system call to store the error
+	 * value.
+	 */
+	void set_last_error() {
+		lastErr_ = get_last_error();
+	}
+	/**
+	 * Checks the value and if less than zero, sets last error.
+     * @tparam T A signed integer type of any size
+	 * @param ret The return value from a library or system call.
+	 * @return Returns the value sent to it, `ret`.
+	 */
+    template <typename T>
+	T check_ret(T ret) const{
+		lastErr_ = (ret < 0) ? get_last_error() : 0;
+		return ret;
+	}
+	/**
+     * Checks the value and if less than zero, sets last error. 
+     * @tparam T A signed integer type of any size
+	 * @param ret The return value from a library or system call.
+	 * @return @em true if the value is a typical system success value (>=0)
+	 *  	   or @em false is is an error (<0)
+	 */
+    template <typename T>
+	bool check_ret_bool(T ret) const{
+		lastErr_ = (ret < 0) ? get_last_error() : 0;
+		return ret >= 0;
+	}
+	/**
+	 * Checks the value and if it is not a valid socket, sets last error.
+	 * This is specifically required for Windows which uses an unsigned type
+	 * for its SOCKET.
+	 * @param ret The return value from a library or system call that returns
+	 *  		  a socket, such as socket() or accept().
+	 * @return Returns the value sent to it, `ret`.
+	 */
+	socket_t check_socket(socket_t ret) const {
+		lastErr_ = (ret == INVALID_SOCKET) ? get_last_error() : 0;
+		return ret;
+	}
+    /**
+     * Checks the value and if it is INVALID_SOCKET, sets last error. 
+	 * This is specifically required for Windows which uses an unsigned type
+	 * for its SOCKET.
+	 * @param ret The return value from a library or system call that returns
+	 *  		  a socket such as socket() or accept().
+	 * @return @em true if the value is a valid socket (not INVALID_SOCKET)
+	 *  	   or @em false is is an error (INVALID_SOCKET)
+	 */
+	bool check_socket_bool(socket_t ret) const{
+		lastErr_ = (ret == INVALID_SOCKET) ? get_last_error() : 0;
+		return ret != INVALID_SOCKET;
+	}
+
+public:
+	/**
+	 * Creates an unconnected (invalid) socket
+	 */
+	socket() : handle_(INVALID_SOCKET), lastErr_(0) {}
+	/**
+	 * Creates a socket from an existing OS socket handle.
+	 * The object takes ownership of the handle and will close it when
+	 * destroyed.
+	 * @param h An OS socket handle.
+	 */
+	explicit socket(socket_t h) : handle_(h), lastErr_(0) {}
+	/**
+	 * Move constructor.
+	 * This takes ownership of the underlying handle in sock.
+	 * @param sock An rvalue reference to a socket object.
+	 */
+	socket(socket&& sock) noexcept
+			: handle_(sock.handle_), lastErr_(sock.lastErr_) {
+		sock.handle_ = INVALID_SOCKET;
+	}
+	/**
+	 * Destructor closes the socket.
+	 */
+	virtual ~socket() { close(); }
+	/**
+	 * Initializes the socket (sockpp) library.
+	 * This is only required for Win32. On platforms that use a standard
+	 * socket implementation this is an empty call.
+	 */
+	static void initialize();
+	/**
+	 * Shuts down the socket library.
+	 * This is only required for Win32. On platforms that use a standard
+	 * socket implementation this is an empty call.
+	 */
+	static void destroy();
+	/**
+	 * Creates a socket with the specified communications characterics.
+	 * Not that this is not normally how a socket is creates in the sockpp
+	 * library. Applications would typically create a connector (client) or
+	 * acceptor (server) socket which would take care of the details.
+	 *
+	 * This is included for completeness or for creating different types of
+	 * sockets than are supported by the library.
+	 *
+	 * @param domain The communications domain for the sockets (i.e. the
+	 *  			 address family)
+	 * @param type The communication semantics for the sockets (SOCK_STREAM,
+	 *  		   SOCK_DGRAM, etc).
+	 * @param protocol The particular protocol to be used with the sockets
+	 *
+	 * @return A socket with the requested communications characteristics.
+	 */
+	static socket create(int domain, int type, int protocol=0);
+	/**
+	 * Determines if the socket is open (valid).
+	 * @return @em true if the socket is open, @em false otherwise.
+	 */
+	bool is_open() const { return handle_ != INVALID_SOCKET; }
+	/**
+	 * Determines if the socket is closed or in an error state.
+	 * @return @em true if the socket is closed or in an error state, @em
+	 *  	   false otherwise.
+	 */
+	bool operator!() const {
+		return handle_ == INVALID_SOCKET || lastErr_ != 0;
+	}
+	/**
+	 * Determines if the socket is open and in an error-free state.
+	 * @return @em true if the socket is open and in an error-free state,
+	 *  	   @em false otherwise.
+	 */
+	explicit operator bool() const {
+		return handle_ != INVALID_SOCKET && lastErr_ == 0;
+	}
+	/**
+	 * Get the underlying OS socket handle.
+	 * @return The underlying OS socket handle.
+	 */
+	socket_t handle() const { return handle_; }
+	/**
+	 * Gets the network family of the address to which the socket is bound.
+	 * @return The network family of the address (AF_INET, etc) to which the
+	 *  	   socket is bound. If the socket is not bound, or the address
+	 *  	   is not known, returns AF_UNSPEC.
+	 */
+	virtual sa_family_t family() const {
+		return address().family();
+	}
+	/**
+	 * Creates a new socket that refers to this one.
+	 * This creates a new object with an independent lifetime, but refers
+	 * back to this same socket. On most systems, this duplicates the file
+	 * handle using the dup() call.
+	 * A typical use of this is to have separate threads for reading and
+	 * writing the socket. One thread would get the original socket and the
+	 * other would get the cloned one.
+	 * @return A new socket object that refers to the same socket as this
+	 *  	   one.
+	 */
+	socket clone() const;
+	/**
+	 * Creates a pair of connected sockets.
+	 *
+	 * Whether this will work at all is highly system and domain dependent.
+	 * Currently it is only known to work for Unix-domain sockets on *nix
+	 * systems.
+	 *
+	 * Note that applications would normally call this from a derived socket
+	 * class which would return the properly type-cast sockets to match the
+	 * `domain` and `type`.
+	 *
+	 * @param domain The communications domain for the sockets (i.e. the
+	 *  			 address family)
+	 * @param type The communication semantics for the sockets (SOCK_STREAM,
+	 *  		   SOCK_DGRAM, etc).
+	 * @param protocol The particular protocol to be used with the sockets
+	 *
+	 * @return A std::tuple of sockets. On error both sockets will be in an
+	 *  	   error state with the
+	 */
+	static std::tuple<socket, socket> pair(int domain, int type, int protocol=0);
+	/**
+	 * Clears the error flag for the object.
+	 * @param val The value to set the flag, normally zero.
+	 */
+	void clear(int val=0) { lastErr_ = val; }
+	/**
+	 * Releases ownership of the underlying socket object.
+	 * @return The OS socket handle.
+	 */
+	socket_t release() {
+		socket_t h = handle_;
+		handle_ = INVALID_SOCKET;
+		return h;
+	}
+	/**
+	 * Replaces the underlying managed socket object.
+	 * @param h The new socket handle to manage.
+	 */
+	void reset(socket_t h=INVALID_SOCKET);
+	/**
+	 * Move assignment.
+	 * This assigns ownership of the socket from the other object to this
+	 * one.
+	 * @return A reference to this object.
+	 */
+	socket& operator=(socket&& sock) noexcept {
+		// Give our handle to the other to close.
+		std::swap(handle_, sock.handle_);
+		lastErr_ = sock.lastErr_;
+		return *this;
+	}
+	/**
+	 * Binds the socket to the specified address.
+	 * @param addr The address to which we get bound.
+	 * @return @em true on success, @em false on error
+	 */
+	bool bind(const sock_address& addr);
+	/**
+	 * Gets the local address to which the socket is bound.
+	 * @return The local address to which the socket is bound.
+	 */
+	sock_address_any address() const;
+	/**
+	 * Gets the address of the remote peer, if this socket is connected.
+	 * @return The address of the remote peer, if this socket is connected.
+	 */
+	sock_address_any peer_address() const;
+    /**
+     * Gets the value of a socket option.
+     *
+     * This is a thin wrapper for the system `getsockopt`.
+     *
+     * @param level The protocol level at which the option resides, such as
+     *              SOL_SOCKET.
+     * @param optname The option passed directly to the protocol module.
+     * @param optval The buffer for the value to retrieve
+     * @param optlen Initially contains the lenth of the buffer, and on return
+     *               contains the length of the value retrieved.
+     *
+     * @return bool @em true if the value was retrieved, @em false if an error
+     *         occurred.
+     */
+    bool get_option(int level, int optname, void* optval, socklen_t* optlen) const;
+    /**
+     * Gets the value of a socket option.
+     *
+     * @param level The protocol level at which the option resides, such as
+     *              SOL_SOCKET.
+     * @param optname The option passed directly to the protocol module.
+     * @param val The value to retrieve
+     * @return bool @em true if the value was retrieved, @em false if an error
+     *         occurred.
+     */
+	template <typename T>
+    bool get_option(int level, int optname, T* val) const {
+		socklen_t len = sizeof(T);
+		return get_option(level, optname, (void*) val, &len);
+	}
+    /**
+     * Sets the value of a socket option.
+     *
+     * This is a thin wrapper for the system `setsockopt`.
+     *
+     * @param level The protocol level at which the option resides, such as
+     *              SOL_SOCKET.
+     * @param optname The option passed directly to the protocol module.
+     * @param optval The buffer with the value to set.
+     * @param optlen Contains the lenth of the value buffer.
+     *
+     * @return bool @em true if the value was set, @em false if an error
+     *         occurred.
+     */
+    bool set_option(int level, int optname, const void* optval, socklen_t optlen);
+    /**
+     * Sets the value of a socket option.
+     *
+     * @param level The protocol level at which the option resides, such as
+     *              SOL_SOCKET.
+     * @param optname The option passed directly to the protocol module.
+     * @param val The value to set.
+     *
+     * @return bool @em true if the value was set, @em false if an error
+     *         occurred.
+     */
+	template <typename T>
+    bool set_option(int level, int optname, const T& val) {
+		return set_option(level, optname, (void*) &val, sizeof(T));
+	}
+	/**
+	 * Places the socket into or out of non-blocking mode.
+	 * When in non-blocking mode, a call that is not immediately ready to
+	 * complete (read, write, accept, etc) will return immediately with the
+	 * error EWOULDBLOCK.
+	 * @param on Whether to turn non-blocking mode on or off.
+	 * @return @em true on success, @em false on failure.
+	 */
+	bool set_non_blocking(bool on=true);
+    /**
+     * Gets a string describing the specified error.
+     * This is typically the returned message from the system strerror().
+     * @param errNum The error number.
+     * @return A string describing the specified error.
+     */
+    static std::string error_str(int errNum);
+	/**
+	 * Gets the code for the last errror.
+	 * This is typically the code from the underlying OS operation.
+	 * @return The code for the last errror.
+	 */
+	int last_error() const { return lastErr_; }
+	/**
+	 * Gets a string describing the last errror.
+	 * This is typically the returned message from the system strerror().
+	 * @return A string describing the last errror.
+	 */
+	std::string last_error_str() const {
+        return error_str(lastErr_);
+    }
+	/**
+	 * Shuts down all or part of the full-duplex connection.
+	 * @param how Which part of the connection should be shut:
+	 *  	@li SHUT_RD   (0) Further reads disallowed.
+	 *  	@li SHUT_WR   (1) Further writes disallowed
+	 *  	@li SHUT_RDWR (2) Further reads and writes disallowed.
+	 * @return @em true on success, @em false on error.
+	 */
+	bool shutdown(int how=SHUT_RDWR);
+	/**
+	 * Closes the socket.
+	 * After closing the socket, the handle is @em invalid, and can not be
+	 * used again until reassigned.
+	 * @return @em true if the sock is closed, @em false on error.
+	 */
+	bool close();
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * RAII class to initialize and then shut down the library.
+ * A single object of this class can be declared before any other classes in
+ * the library are used. The lifetime of the object should span the use of
+ * the other classes in the library, so declaring an object at the top of
+ * main() is usually the best choice.
+ * This is only required on some platforms, particularly Windows, but is
+ * harmless on other platforms. On some, such as POSIX, the initializer sets
+ * optional parameters for the library, and the destructor does nothing.
+ */
+class socket_initializer
+{
+public:
+	socket_initializer() { socket::initialize(); }
+	~socket_initializer() { socket::destroy(); }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+// end namespace sockpp
+}
+
+#endif		// __sockpp_socket_h
+
diff --git b/include/socket-cpp/stream_socket.h a/include/socket-cpp/stream_socket.h
new file mode 100644
index 0000000..e0722d2
--- /dev/null
+++ a/include/socket-cpp/stream_socket.h
@@ -0,0 +1,332 @@
+/**
+ * @file stream_socket.h
+ *
+ * Classes for stream sockets.
+ *
+ * @author Frank Pagliughi
+ * @author SoRo Systems, Inc.
+ * @author www.sorosys.com
+ *
+ * @date December 2014
+ */
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2014-2017 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#ifndef __sockpp_stream_socket_h
+#define __sockpp_stream_socket_h
+
+#include "sockpp/socket.h"
+#include <vector>
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Base class for streaming sockets, such as TCP and Unix Domain.
+ * This is the streaming connection between two peers. It looks like a
+ * readable/writeable device.
+ */
+class stream_socket : public socket
+{
+	/** The base class   */
+	using base = socket;
+
+protected:
+	/** Acceptor can create stream sockets. */
+	friend class acceptor;
+
+	/**
+	 * Creates a streaming socket.
+	 * @return An OS handle to a stream socket.
+	 */
+	static socket_t create_handle(int domain) {
+		return (socket_t) ::socket(domain, COMM_TYPE, 0);
+	}
+
+public:
+	/** The socket 'type' for communications semantics. */
+	static constexpr int COMM_TYPE = SOCK_STREAM;
+	/**
+	 * Creates an unconnected streaming socket.
+	 */
+	stream_socket() {}
+	/**
+     * Creates a streaming socket from an existing OS socket handle and
+     * claims ownership of the handle.
+	 * @param handle A socket handle from the operating system.
+	 */
+	explicit stream_socket(socket_t handle) : base(handle) {}
+	/**
+	 * Creates a stream socket by copying the socket handle from the 
+	 * specified socket object and transfers ownership of the socket. 
+	 */
+	stream_socket(stream_socket&& sock) : base(std::move(sock)) {}
+
+
+	/**
+	 * Creates a socket with the specified communications characterics.
+	 * Not that this is not normally how a socket is creates in the sockpp
+	 * library. Applications would typically create a connector (client) or
+	 * acceptor (server) socket which would take care of the details.
+	 *
+	 * This is included for completeness or for creating different types of
+	 * sockets than are supported by the library.
+	 *
+	 * @param domain The communications domain for the sockets (i.e. the
+	 *  			 address family)
+	 * @param protocol The particular protocol to be used with the sockets
+	 *
+	 * @return A stream socket with the requested communications
+	 *  	   characteristics.
+	 */
+	static stream_socket create(int domain, int protocol=0);
+
+	/**
+	 * Move assignment.
+	 * @param rhs The other socket to move into this one.
+	 * @return A reference to this object.
+	 */
+	stream_socket& operator=(stream_socket&& rhs) {
+		base::operator=(std::move(rhs));
+		return *this;
+	}
+	/**
+	 * Creates a new stream_socket that refers to this one.
+	 * This creates a new object with an independent lifetime, but refers
+	 * back to this same socket. On most systems, this duplicates the file
+	 * handle using the dup() call.
+	 * A typical use of this is to have separate threads for reading and
+	 * writing the socket. One thread would get the original socket and the
+	 * other would get the cloned one.
+	 * @return A new stream socket object that refers to the same socket as
+	 *  	   this one.
+	 */
+	stream_socket clone() const {
+		auto h = base::clone().release();
+		return stream_socket(h);
+	}
+	/**
+	 * Reads from the port
+	 * @param buf Buffer to get the incoming data.
+	 * @param n The number of bytes to try to read.
+	 * @return The number of bytes read on success, or @em -1 on error.
+	 */
+	virtual ssize_t read(void *buf, size_t n);
+	/**
+	 * Best effort attempts to read the specified number of bytes.
+	 * This will make repeated read attempts until all the bytes are read in
+	 * or until an error occurs.
+	 * @param buf Buffer to get the incoming data.
+	 * @param n The number of bytes to try to read.
+	 * @return The number of bytes read on success, or @em -1 on error. If
+	 *  	   successful, the number of bytes read should always be 'n'.
+	 */
+	virtual ssize_t read_n(void *buf, size_t n);
+    /**
+     * Reads discontiguous memory ranges from the socket.
+     * @param ranges The vector of memory ranges to fill
+     * @return The number of bytes read, or @em -1 on error.
+     */
+	ssize_t read(const std::vector<iovec>& ranges);
+	/**
+	 * Set a timeout for read operations.
+	 * Sets the timeout that the device uses for read operations. Not all
+	 * devices support timeouts, so the caller should prepare for failure.
+	 * @param to The amount of time to wait for the operation to complete.
+	 * @return @em true on success, @em false on failure.
+	 */
+	virtual bool read_timeout(const std::chrono::microseconds& to);
+	/**
+	 * Set a timeout for read operations.
+	 * Sets the timout that the device uses for read operations. Not all
+	 * devices support timouts, so the caller should prepare for failure.
+	 * @param to The amount of time to wait for the operation to complete.
+	 * @return @em true on success, @em false on failure.
+	 */
+	template<class Rep, class Period>
+	bool read_timeout(const std::chrono::duration<Rep,Period>& to) {
+		return read_timeout(std::chrono::duration_cast<std::chrono::microseconds>(to));
+	}
+	/**
+	 * Writes the buffer to the socket.
+	 * @param buf The buffer to write
+	 * @param n The number of bytes in the buffer.
+	 * @return The number of bytes written, or @em -1 on error.
+	 */
+	virtual ssize_t write(const void *buf, size_t n);
+	/**
+	 * Best effort attempt to write the whole buffer to the socket.
+	 * @param buf The buffer to write
+	 * @param n The number of bytes in the buffer.
+	 * @return The number of bytes written, or @em -1 on error. If
+	 *  	   successful, the number of bytes written should always be 'n'.
+	 */
+	virtual ssize_t write_n(const void *buf, size_t n);
+	/**
+	 * Best effort attempt to write a string to the socket.
+	 * @param s The string to write.
+	 * @return The number of bytes written, or @em -1 on error. On success,
+	 *  	   the number of bytes written should always be the length of
+	 *  	   the string.
+	 */
+	virtual ssize_t write(const std::string& s) {
+		return write_n(s.data(), s.size());
+	}
+    /**
+     * Writes discontiguous memory ranges to the socket.
+     * @param ranges The vector of memory ranges to write
+     * @return The number of bytes written, or @em -1 on error.
+     */
+    virtual ssize_t write(const std::vector<iovec> &ranges);
+	/**
+	 * Set a timeout for write operations.
+	 * Sets the timout that the device uses for write operations. Not all
+	 * devices support timouts, so the caller should prepare for failure.
+	 * @param to The amount of time to wait for the operation to complete.
+	 * @return @em true on success, @em false on failure.
+	 */
+	virtual bool write_timeout(const std::chrono::microseconds& to);
+	/**
+	 * Set a timeout for write operations.
+	 * Sets the timout that the device uses for write operations. Not all
+	 * devices support timouts, so the caller should prepare for failure.
+	 * @param to The amount of time to wait for the operation to complete.
+	 * @return @em true on success, @em false on failure.
+	 */
+	template<class Rep, class Period>
+	bool write_timeout(const std::chrono::duration<Rep,Period>& to) {
+		return write_timeout(std::chrono::duration_cast<std::chrono::microseconds>(to));
+	}
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Template for creating specific stream types (IPv4, IPv6, etc).
+ * This just overrides methods that take a generic address and replace them
+ * with the address type for a specific family. This doesn't add any
+ * runtime functionality, but has compile-time checks that address types
+ * aren't accidentally being mixed for an object.
+ */
+template <typename ADDR>
+class stream_socket_tmpl : public stream_socket
+{
+	/** The base class */
+	using base = stream_socket;
+
+public:
+    /** The address family for this type of address */
+	static constexpr sa_family_t ADDRESS_FAMILY = ADDR::ADDRESS_FAMILY;
+	/** The type of network address used with this socket. */
+	using addr_t = ADDR;
+	/**
+	 * Creates an unconnected streaming socket.
+	 */
+	stream_socket_tmpl() {}
+	/**
+     * Creates a streaming socket from an existing OS socket handle and
+     * claims ownership of the handle.
+	 * @param handle A socket handle from the operating system.
+	 */
+	explicit stream_socket_tmpl(socket_t handle) : base(handle) {}
+	/**
+	 * Move constructor.
+	 * Creates a stream socket by moving the other socket to this one.
+	 * @param sock Another stream socket.
+	 */
+	stream_socket_tmpl(stream_socket&& sock)
+			: base(std::move(sock)) {}
+	/**
+	 * Creates a stream socket by copying the socket handle from the
+	 * specified socket object and transfers ownership of the socket.
+	 */
+	stream_socket_tmpl(stream_socket_tmpl&& sock)
+			: base(std::move(sock)) {}
+	/**
+	 * Move assignment.
+	 * @param rhs The other socket to move into this one.
+	 * @return A reference to this object.
+	 */
+	stream_socket_tmpl& operator=(stream_socket_tmpl&& rhs) {
+		base::operator=(std::move(rhs));
+		return *this;
+	}
+	/**
+	 * Cretates a stream socket.
+	 * @param protocol The particular protocol to be used with the sockets
+	 * @return A stream socket
+	 */
+	stream_socket_tmpl create(int protocol=0) {
+		return stream_socket_tmpl(std::move(base::create(ADDRESS_FAMILY, protocol)));
+	}
+	/**
+	 * Creates a pair of connected stream sockets.
+	 *
+	 * Whether this will work at all is highly system and domain dependent.
+	 * Currently it is only known to work for Unix-domain sockets on *nix
+	 * systems.
+	 *
+	 * @param protocol The protocol to be used with the socket. (Normally 0)
+	 *
+	 * @return A std::tuple of stream sockets. On error both sockets will be
+	 *  	   in an error state with the last error set.
+	 */
+	static std::tuple<stream_socket_tmpl, stream_socket_tmpl> pair(int protocol=0) {
+		auto pr = base::pair(ADDRESS_FAMILY, COMM_TYPE, protocol);
+		return std::make_tuple<stream_socket_tmpl, stream_socket_tmpl>(
+			stream_socket_tmpl{std::get<0>(pr).release()},
+			stream_socket_tmpl{std::get<1>(pr).release()});
+	}
+	/**
+	 * Gets the local address to which the socket is bound.
+	 * @return The local address to which the socket is bound.
+	 * @throw sys_error on error
+	 */
+	addr_t address() const { return addr_t(socket::address()); }
+	/**
+	 * Gets the address of the remote peer, if this socket is connected.
+	 * @return The address of the remote peer, if this socket is connected.
+	 * @throw sys_error on error
+	 */
+	addr_t peer_address() const { return addr_t(socket::peer_address()); }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+// end namespace sockpp
+}
+
+#endif		// __sockpp_socket_h
+
diff --git b/include/socket-cpp/tcp6_acceptor.h a/include/socket-cpp/tcp6_acceptor.h
new file mode 100644
index 0000000..230d83a
--- /dev/null
+++ a/include/socket-cpp/tcp6_acceptor.h
@@ -0,0 +1,69 @@
+/// @file tcp6_acceptor.h
+///
+/// Class for a TCP v6 server to accept incoming connections.
+///
+/// @author	Frank Pagliughi
+///	@author	SoRo Systems, Inc.
+///	@author	www.sorosys.com
+///
+/// @date	May 2019
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#ifndef __sockpp_tcp6_acceptor_h
+#define __sockpp_tcp6_acceptor_h
+
+#include "sockpp/acceptor.h"
+#include "sockpp/tcp6_socket.h"
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+/// Class for creating a TCP v6 server.
+/// Objects of this class bind and listen on TCP ports for incoming
+/// connections. Normally, a server thread creates one of these and blocks
+/// on the call to accept incoming connections. The call to accept creates
+/// and returns a @ref tcp6_socket which can then be used for the actual
+/// communications.
+
+using tcp6_acceptor = acceptor_tmpl<tcp6_socket>;
+
+/////////////////////////////////////////////////////////////////////////////
+// end namespace sockpp
+};
+
+#endif		// __sockpp_tcp_acceptor_h
+
diff --git b/include/socket-cpp/tcp6_connector.h a/include/socket-cpp/tcp6_connector.h
new file mode 100644
index 0000000..3c37980
--- /dev/null
+++ a/include/socket-cpp/tcp6_connector.h
@@ -0,0 +1,66 @@
+/**
+ * @file tcp6_connector.h
+ *
+ * Class for creating client-side TCP connections
+ *
+ * @author	Frank Pagliughi
+ * @author	SoRo Systems, Inc.
+ * @author  www.sorosys.com
+ *
+ * @date	May 2019
+ */
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2014-2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+
+#ifndef __sockpp_tcp6_connector_h
+#define __sockpp_tcp6_connector_h
+
+#include "sockpp/connector.h"
+#include "sockpp/tcp6_socket.h"
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+/** IPv6 active, connector (client) socket. */
+using tcp6_connector = connector_tmpl<tcp6_socket>;
+
+/////////////////////////////////////////////////////////////////////////////
+// end namespace sockpp
+}
+
+#endif		// __sockpp_tcp6_connector_h
+
diff --git b/include/socket-cpp/tcp6_socket.h a/include/socket-cpp/tcp6_socket.h
new file mode 100644
index 0000000..04ab99b
--- /dev/null
+++ a/include/socket-cpp/tcp6_socket.h
@@ -0,0 +1,65 @@
+/**
+ * @file tcp6_socket.h
+ *
+ * Class (typedef) for IPv6 TCP socket.
+ *
+ * @author Frank Pagliughi
+ * @author SoRo Systems, Inc.
+ * @author www.sorosys.com
+ *
+ * @date August 2019
+ */
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#ifndef __sockpp_tcp6_socket_h
+#define __sockpp_tcp6_socket_h
+
+#include "sockpp/stream_socket.h"
+#include "sockpp/inet6_address.h"
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+/** IPv6 streaming TCP socket */
+using tcp6_socket = stream_socket_tmpl<inet6_address>;
+
+/////////////////////////////////////////////////////////////////////////////
+// end namespace sockpp
+}
+
+#endif		// __sockpp_tcp6_socket_h
+
diff --git b/include/socket-cpp/tcp_acceptor.h a/include/socket-cpp/tcp_acceptor.h
new file mode 100644
index 0000000..cfd78e4
--- /dev/null
+++ a/include/socket-cpp/tcp_acceptor.h
@@ -0,0 +1,69 @@
+/// @file tcp_acceptor.h
+///
+/// Class for a TCP server to accept incoming connections.
+///
+/// @author	Frank Pagliughi
+///	@author	SoRo Systems, Inc.
+///	@author	www.sorosys.com
+///
+/// @date	December 2014
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2014-2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#ifndef __sockpp_tcp_acceptor_h
+#define __sockpp_tcp_acceptor_h
+
+#include "sockpp/acceptor.h"
+#include "sockpp/tcp_socket.h"
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+/// Class for creating a TCP server.
+/// Objects of this class bind and listen on TCP ports for incoming
+/// connections. Normally, a server thread creates one of these and blocks
+/// on the call to accept incoming connections. The call to accept creates
+/// and returns a @ref tcp_socket which can then be used for the actual
+/// communications.
+
+using tcp_acceptor = acceptor_tmpl<tcp_socket>;
+
+/////////////////////////////////////////////////////////////////////////////
+// end namespace sockpp
+};
+
+#endif		// __sockpp_tcp_acceptor_h
+
diff --git b/include/socket-cpp/tcp_connector.h a/include/socket-cpp/tcp_connector.h
new file mode 100644
index 0000000..c5088b0
--- /dev/null
+++ a/include/socket-cpp/tcp_connector.h
@@ -0,0 +1,65 @@
+/**
+ * @file tcp_connector.h
+ *
+ * Class for creating client-side TCP connections
+ *
+ * @author	Frank Pagliughi
+ * @author	SoRo Systems, Inc.
+ * @author  www.sorosys.com
+ *
+ * @date	December 2014
+ */
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2014-2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+
+#ifndef __sockpp_tcp_connector_h
+#define __sockpp_tcp_connector_h
+
+#include "sockpp/connector.h"
+#include "sockpp/tcp_socket.h"
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+/** IPv4 active, connector (client) socket. */
+using tcp_connector = connector_tmpl<tcp_socket>;
+
+/////////////////////////////////////////////////////////////////////////////
+// end namespace sockpp
+};
+
+#endif		// __sockpp_tcp_connector_h
diff --git b/include/socket-cpp/tcp_socket.h a/include/socket-cpp/tcp_socket.h
new file mode 100644
index 0000000..6f1cf64
--- /dev/null
+++ a/include/socket-cpp/tcp_socket.h
@@ -0,0 +1,65 @@
+/**
+ * @file tcp_socket.h
+ *
+ * Class (typedef) for IPv4 TCP socket.
+ *
+ * @author Frank Pagliughi
+ * @author SoRo Systems, Inc.
+ * @author www.sorosys.com
+ *
+ * @date August 2019
+ */
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#ifndef __sockpp_tcp_socket_h
+#define __sockpp_tcp_socket_h
+
+#include "sockpp/stream_socket.h"
+#include "sockpp/inet_address.h"
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+/** IPv4 streaming TCP socket */
+using tcp_socket = stream_socket_tmpl<inet_address>;
+
+/////////////////////////////////////////////////////////////////////////////
+// end namespace sockpp
+}
+
+#endif		// __sockpp_tcp_socket_h
+
diff --git b/include/socket-cpp/udp6_socket.h a/include/socket-cpp/udp6_socket.h
new file mode 100644
index 0000000..5c294c5
--- /dev/null
+++ a/include/socket-cpp/udp6_socket.h
@@ -0,0 +1,65 @@
+/**
+ * @file udp6_socket.h
+ *
+ * Class (typedef) for UDP v6 socket.
+ *
+ * @author Frank Pagliughi
+ * @author SoRo Systems, Inc.
+ * @author www.sorosys.com
+ *
+ * @date August 2019
+ */
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#ifndef __sockpp_udp6_socket_h
+#define __sockpp_udp6_socket_h
+
+#include "sockpp/datagram_socket.h"
+#include "sockpp/inet6_address.h"
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+/** UDP datagram socket type for IPv6 */
+using udp6_socket = datagram_socket_tmpl<inet6_address>;
+
+/////////////////////////////////////////////////////////////////////////////
+// end namespace sockpp
+}
+
+#endif		// __sockpp_udp6_socket_h
+
diff --git b/include/socket-cpp/udp_socket.h a/include/socket-cpp/udp_socket.h
new file mode 100644
index 0000000..c63f299
--- /dev/null
+++ a/include/socket-cpp/udp_socket.h
@@ -0,0 +1,65 @@
+/**
+ * @file udp_socket.h
+ *
+ * Class (typedef) for UDP v4 socket.
+ *
+ * @author Frank Pagliughi
+ * @author SoRo Systems, Inc.
+ * @author www.sorosys.com
+ *
+ * @date August 2019
+ */
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#ifndef __sockpp_udp_socket_h
+#define __sockpp_udp_socket_h
+
+#include "sockpp/datagram_socket.h"
+#include "sockpp/inet_address.h"
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+/** UDP datagram socket type for IPv4 */
+using udp_socket = datagram_socket_tmpl<inet_address>;
+
+/////////////////////////////////////////////////////////////////////////////
+// end namespace sockpp
+}
+
+#endif		// __sockpp_udp_socket_h
+
diff --git b/include/socket-cpp/unix_acceptor.h a/include/socket-cpp/unix_acceptor.h
new file mode 100644
index 0000000..5bcc067
--- /dev/null
+++ a/include/socket-cpp/unix_acceptor.h
@@ -0,0 +1,115 @@
+/// @file unix_acceptor.h
+///
+/// Class for a TCP server to accept incoming connections.
+///
+/// @author	Frank Pagliughi
+///	@author	SoRo Systems, Inc.
+///	@author	www.sorosys.com
+///
+/// @date	December 2014
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2014-2017 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#ifndef __sockpp_unix_acceptor_h
+#define __sockpp_unix_acceptor_h
+
+#include "sockpp/acceptor.h"
+#include "sockpp/unix_stream_socket.h"
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+/// Class for creating a Unix-domain server.
+/// Objects of this class bind and listen on Unix-domain ports for
+/// connections. Normally, a server thread creates one of these and blocks
+/// on the call to accept incoming connections. The call to accept creates
+/// and returns a @ref unix_stream_socket which can then be used for the
+/// actual communications.
+
+class unix_acceptor : public acceptor
+{
+    /** The base class */
+    using base = acceptor;
+
+	// Non-copyable
+	unix_acceptor(const unix_acceptor&) =delete;
+	unix_acceptor& operator=(const unix_acceptor&) =delete;
+
+public:
+	/**
+	 * Creates an unconnected acceptor.
+	 */
+	unix_acceptor() {}
+	/**
+	 * Creates a acceptor and starts it listening on the specified address.
+	 * @param addr The TCP address on which to listen.
+	 * @param queSize The listener queue size.
+	 */
+	unix_acceptor(const unix_address& addr, int queSize=DFLT_QUE_SIZE) {
+		open(addr, queSize);
+	}
+	/**
+	 * Gets the local address to which we are bound.
+	 * @return The local address to which we are bound.
+	 */
+	unix_address address() const { return unix_address(base::address()); }
+    /**
+     * Base open call also work.
+     */
+    using base::open;
+	/**
+	 * Opens the acceptor socket and binds it to the specified address.
+	 * @param addr The address to which this server should be bound.
+	 * @param queSize The listener queue size.
+	 * @return @em true on success, @em false on error
+	 */
+	bool open(const unix_address& addr, int queSize=DFLT_QUE_SIZE) {
+		return base::open(addr, queSize);
+	}
+	/**
+     * Accepts an incoming UNIX connection and gets the address of the
+     * client.
+	 * @return A unix_socket to the client.
+	 */
+	unix_socket accept() { return unix_socket(base::accept()); }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+// end namespace sockpp
+};
+
+#endif		// __sockpp_unix_acceptor_h
+
diff --git b/include/socket-cpp/unix_address.h a/include/socket-cpp/unix_address.h
new file mode 100644
index 0000000..30e8de7
--- /dev/null
+++ a/include/socket-cpp/unix_address.h
@@ -0,0 +1,192 @@
+/**
+ * @file unix_address.h
+ *
+ * Class for a UNIX-domain socket address.
+ *
+ * @author Frank Pagliughi
+ * @author SoRo Systems, Inc.
+ * @author www.sorosys.com
+ *
+ * @date February 2014
+ */
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2014-2017 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#ifndef __sockpp_unix_addr_h
+#define __sockpp_unix_addr_h
+
+#include "sockpp/platform.h"
+#include "sockpp/sock_address.h"
+#include <iostream>
+#include <string>
+#include <cstring>
+#include <sys/un.h>
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Class that represents a UNIX domain address.
+ * This inherits from the UNIX form of a socket address, @em sockaddr_un.
+ */
+class unix_address : public sock_address
+{
+	/** The underlying C struct for unix-domain addresses  */
+	sockaddr_un addr_;
+
+	/** The size of the underlying address struct, in bytes */
+	static constexpr size_t SZ = sizeof(sockaddr_un);
+
+public:
+    /** The address family for this type of address */
+	static constexpr sa_family_t ADDRESS_FAMILY = AF_UNIX;
+
+    // TODO: This only applies to Linux
+	static constexpr size_t MAX_PATH_NAME = 108;
+
+	/**
+	 * Constructs an empty address.
+	 * The address is initialized to all zeroes.
+	 */
+	unix_address() : addr_() {}
+	/**
+	 * Constructs an address for the specified path.
+	 * @param path The
+	 */
+	unix_address(const std::string& path);
+	/**
+	 * Constructs the address by copying the specified structure.
+     * @param addr The generic address
+     * @throws std::invalid_argument if the address is not a UNIX-domain
+     *            address (i.e. family is not AF_UNIX)
+	 */
+	explicit unix_address(const sockaddr& addr);
+	/**
+	 * Constructs the address by copying the specified structure.
+	 * @param addr The other address
+	 */
+	unix_address(const sock_address& addr) {
+		std::memcpy(&addr_, addr.sockaddr_ptr(), SZ);
+	}
+	/**
+	 * Constructs the address by copying the specified structure.
+     * @param addr The other address
+     * @throws std::invalid_argument if the address is not properly
+     *            initialized as a UNIX-domain address (i.e. family is not
+     *            AF_UNIX)
+	 */
+	unix_address(const sockaddr_un& addr) : addr_(addr) {}
+	/**
+	 * Constructs the address by copying the specified address.
+	 * @param addr The other address
+	 */
+	unix_address(const unix_address& addr) : addr_(addr.addr_) {}
+	/**
+	 * Checks if the address is set to some value.
+	 * This doesn't attempt to determine if the address is valid, simply
+	 * that it's not all zero.
+	 * @return @em true if the address has been set, @em false otherwise.
+	 */
+	bool is_set() const { return addr_.sun_path[0] != '\0'; }
+	/**
+	 * Gets the path to which this address refers.
+	 * @return The path to which this address refers.
+	 */
+	std::string path() const { return std::string(addr_.sun_path); }
+	/**
+	 * Gets the size of the address structure.
+	 * Note: In this implementation, this should return sizeof(this) but
+	 * more convenient in some places, and the implementation might change
+	 * in the future, so it might be more compatible with future revisions
+	 * to use this call.
+	 * @return The size of the address structure.
+	 */
+	socklen_t size() const override { return socklen_t(SZ); }
+
+    // TODO: Do we need a:
+    //   create(path)
+    // to mimic the inet_address behavior?
+
+    /**
+	 * Gets a pointer to this object cast to a const @em sockaddr.
+	 * @return A pointer to this object cast to a const @em sockaddr.
+	 */
+	const sockaddr* sockaddr_ptr() const override {
+		return reinterpret_cast<const sockaddr*>(&addr_);
+	}
+	/**
+	 * Gets a pointer to this object cast to a @em sockaddr.
+	 * @return A pointer to this object cast to a @em sockaddr.
+	 */
+	sockaddr* sockaddr_ptr() override {
+		return reinterpret_cast<sockaddr*>(&addr_);
+	}
+	/**
+	 * Gets a const pointer to this object cast to a @em sockaddr_un.
+	 * @return const sockaddr_un pointer to this object.
+	 */
+	const sockaddr_un* sockaddr_un_ptr() const { return &addr_; }
+	/**
+	 * Gets a pointer to this object cast to a @em sockaddr_un.
+	 * @return sockaddr_un pointer to this object.
+	 */
+	sockaddr_un* sockaddr_un_ptr() { return &addr_; }
+	/**
+	 * Gets a printable string for the address.
+	 * @return A string representation of the address in the form
+	 *  	   "unix:<path>"
+	 */
+	std::string to_string() const {
+		return std::string("unix:") + std::string(addr_.sun_path);
+	}
+};
+
+// --------------------------------------------------------------------------
+
+/**
+ * Stream inserter for the address.
+ * @param os The output stream
+ * @param addr The address
+ * @return A reference to the output stream.
+ */
+std::ostream& operator<<(std::ostream& os, const unix_address& addr);
+
+/////////////////////////////////////////////////////////////////////////////
+// end namespace sockpp
+}
+
+#endif		// __sockpp_unix_addr_h
+
diff --git b/include/socket-cpp/unix_connector.h a/include/socket-cpp/unix_connector.h
new file mode 100644
index 0000000..8777d6f
--- /dev/null
+++ a/include/socket-cpp/unix_connector.h
@@ -0,0 +1,65 @@
+/**
+ * @file unix_connector.h
+ *
+ * Class for creating client-side UNIX-domain socket connections.
+ *
+ * @author	Frank Pagliughi
+ * @author	SoRo Systems, Inc.
+ * @author  www.sorosys.com
+ *
+ * @date	December 2018
+ */
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2014-2017 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#ifndef __sockpp_unix_connector_h
+#define __sockpp_unix_connector_h
+
+#include "sockpp/connector.h"
+#include "sockpp/unix_stream_socket.h"
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+/** Unix-domain active connector socket. */
+using unix_connector = connector_tmpl<unix_socket, unix_address>;
+
+/////////////////////////////////////////////////////////////////////////////
+// end namespace sockpp
+};
+
+#endif		// __sockpp_unix_connector_h
+
diff --git b/include/socket-cpp/unix_dgram_socket.h a/include/socket-cpp/unix_dgram_socket.h
new file mode 100644
index 0000000..da171a2
--- /dev/null
+++ a/include/socket-cpp/unix_dgram_socket.h
@@ -0,0 +1,68 @@
+/**
+ * @file unix_dgram_socket.h
+ *
+ * Class (typedef) for Unix-domain UDP socket.
+ *
+ * @author Frank Pagliughi
+ * @author SoRo Systems, Inc.
+ * @author www.sorosys.com
+ *
+ * @date August 2019
+ */
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#ifndef __sockpp_unix_dgram_socket_h
+#define __sockpp_unix_dgram_socket_h
+
+#include "sockpp/datagram_socket.h"
+#include "sockpp/unix_address.h"
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+/** Unix-domain datagram socket */
+using unix_datagram_socket = datagram_socket_tmpl<unix_address>;
+
+/** Unix-domain datagram socket (same as `unix_datagram_socket`) */
+using unix_dgram_socket = unix_datagram_socket;
+
+/////////////////////////////////////////////////////////////////////////////
+// end namespace sockpp
+}
+
+#endif		// __sockpp_unix_dgram_socket_h
+
diff --git b/include/socket-cpp/unix_stream_socket.h a/include/socket-cpp/unix_stream_socket.h
new file mode 100644
index 0000000..fa3863c
--- /dev/null
+++ a/include/socket-cpp/unix_stream_socket.h
@@ -0,0 +1,68 @@
+/**
+ * @file unix_stream_socket.h
+ *
+ * Class (typedef) for Unix-domain streaming socket.
+ *
+ * @author Frank Pagliughi
+ * @author SoRo Systems, Inc.
+ * @author www.sorosys.com
+ *
+ * @date August 2019
+ */
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#ifndef __sockpp_unix_stream_socket_h
+#define __sockpp_unix_stream_socket_h
+
+#include "sockpp/stream_socket.h"
+#include "sockpp/unix_address.h"
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+/** Streaming Unix-domain socket */
+using unix_stream_socket = stream_socket_tmpl<unix_address>;
+
+/** Streaming Unix-domain socket (same as a `unix_stream_socket` */
+using unix_socket = unix_stream_socket;
+
+/////////////////////////////////////////////////////////////////////////////
+// end namespace sockpp
+}
+
+#endif		// __sockpp_unix_stream_socket_h
+
diff --git b/scripts/setup_submodules a/scripts/setup_submodules
new file mode 100755
index 0000000..b8c7a6e
--- /dev/null
+++ a/scripts/setup_submodules
@@ -0,0 +1,161 @@
+#!/bin/bash
+
+# ===============================================
+# == Setting some environment variables
+# ===============================================
+GIT_URL_SUBS="http://gitlab.osdev.nl/open_source"
+FUNC_RESULT="-1"
+
+# Name          : print_usage_exit()
+# Description   : Print the way this script is intended to be used and exit.
+# Parameters    : None.
+# Returns       : err_code 1 to the Operating System
+# --------------------------------------------------------------------------------------
+function print_usage_exit() 
+{
+    echo "Usage $0 -i|--install|-u|--update"
+    echo "	-i or --install  Install the submodules mentioned in the submodules.list" 
+    echo "	-u or --update   Update the submodules mentioned in the submodules.list"
+    echo " "
+    exit 1
+}
+
+# Name          : check_top_or_sub
+# Description   : Determine if we're running in a "single" lib-build or part of a
+#                 "meta"-repository ( submodule ). 
+# Parameters    : None
+# Returns       : Updates the value FUNC_RESULT.
+#                 -1 - We're neither a git-repo or submodule.
+#                  0 - We're a submodule
+#                  1 - We're a top-repo ( Single library )
+# --------------------------------------------------------------------------------------
+function check_top_or_sub() 
+{
+    # This function checks if we're the top-repository.
+    # In that case we need the submodules.. If we're already a submodule, 
+    # we simply exit this script with a message
+    if [ -e ./.git ]; then
+	    FUNC_RESULT="1"
+        return
+    elif [ -e ../.git ]; then
+        if [ -e ../.submodules ]; then
+            echo "Seems like we're already a submodule. Nothing to do here."
+            FUNC_RESULT="0"
+            return
+        fi
+    fi
+    FUNC_RESULT="-1"
+    return
+}
+
+# Name          : check_working_dir
+# Description   : If we're in the top of our repo, we can run this script further.
+# Parameters    : None.
+# Returns       : Updates the value FUNC_RESULT.
+#                 -1 - Not used.
+#                  0 - We're not on the top-level
+#                  1 - We're at the top-level. Good to go.
+# --------------------------------------------------------------------------------------
+function check_working_dir() 
+{
+    FUNC_RESULT="-1"
+    # Check if we're in the top-level directory of our repository.
+    if [ -f ./scripts/submodules.list ]; then
+        # We're good to go
+        FUNC_RESULT="1"
+        return
+    fi
+    FUNC_RESULT="0"
+    return
+}
+
+# Name          : read_submodules
+# Description   : Read the list of submodules needed for this project
+# Parameters    : None
+# Returns       : Updates the value FUNC_RESULT
+#                 0 - Module list was not found
+#                 1 - Module list was found and read.
+# --------------------------------------------------------------------------------------
+function read_submodules() 
+{
+    FUNC_RESULT="-1"
+    if [ -e ./scripts/submodules.list ]; then
+        source ./scripts/submodules.list
+        FUNC_RESULT="1"
+        return
+    fi
+
+    echo "Submodules list not found...."
+    FUNC_RESULT="0"
+    return
+}
+
+# Name          : add_submodules
+# Description   : Configure the repo to add the submodules.
+# Parameters    : None.
+# Returns       : None.
+# --------------------------------------------------------------------------------------
+function add_submodules() 
+{
+    echo -e "Adding SubModule(s)."
+    for SUB_MODULE in ${SUB_MODULES}
+    do
+        echo -e "< ${SUB_MODULE} >"
+        git submodule add -f ${GIT_URL_SUBS}/${SUB_MODULE}.git ${SUB_MODULE}
+        git config submodule.${SUB_MODULE}.url ${GIT_URL_SUBS}/${SUB_MODULE}.git
+    done
+}
+
+# Name          : get_submodules
+# Description   : Actually get the submodules from gitlab and add them.
+# Parameters    : None
+# Returns       : None
+# --------------------------------------------------------------------------------------
+function get_submodules() 
+{
+    git submodule update --init --recursive
+}
+
+# Name          : update_submodules
+# Description   : Update the submodules already added.
+# Parameters    : None 
+# Returns       : None
+# --------------------------------------------------------------------------------------
+function update_submodules() 
+{
+    git submodule update --recursive
+}
+
+# =============================================================================
+# ==        T H E   M A I N   E N T R Y   O F   T H I S   S C R I P T        ==
+# =============================================================================
+check_top_or_sub
+if [ "${FUNC_RESULT}" == "0" ]; then
+    echo "Seems like we're a submodule already or not part of a repository."
+    exit 0
+fi
+
+check_working_dir
+if [ "${FUNC_RESULT}" == "0" ]; then
+    echo "Go to the top of this repository and type : scripts/setup_submodules [-i|--install]"
+    exit 0
+fi
+
+read_submodules
+
+case "$1" in
+    -i*|--install*)
+        echo "Installing submodules for this repository ( ${PWD} )"
+        add_submodules
+        get_submodules
+        ;;
+    -u*|--update*)
+        echo "Update submodules : ${SUB_MODULES}"
+        update_submodules
+        ;;
+    *)
+        echo "No parameters found..."
+        print_usage_exit
+        ;;
+esac        
+
diff --git b/scripts/submodules.list a/scripts/submodules.list
new file mode 100644
index 0000000..109e69b
--- /dev/null
+++ a/scripts/submodules.list
@@ -0,0 +1,2 @@
+SUB_MODULES="versioning
+cmake"
diff --git b/src/acceptor.cpp a/src/acceptor.cpp
new file mode 100644
index 0000000..7d119d7
--- /dev/null
+++ a/src/acceptor.cpp
@@ -0,0 +1,74 @@
+#include <cstring>
+#include "sockpp/acceptor.h"
+
+using namespace std;
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+acceptor acceptor::create(int domain)
+{
+	acceptor acc(create_handle(domain));
+	if (!acc)
+		acc.clear(get_last_error());
+	return acc;
+}
+
+// --------------------------------------------------------------------------
+
+// This attempts to open the acceptor, bind to the requested address, and
+// start listening. On any error it will be sure to leave the underlying
+// socket in an unopened/invalid state.
+// If the acceptor appears to already be opened, this will quietly succeed
+// without doing anything.
+
+bool acceptor::open(const sock_address& addr,
+					int queSize /*=DFLT_QUE_SIZE*/,
+					bool reuseSock /*=true*/)
+{
+	// TODO: What to do if we are open but bound to a different address?
+	if (is_open())
+		return true;
+
+	sa_family_t domain = addr.family();
+	socket_t h = create_handle(domain);
+
+	if (!check_socket_bool(h))
+		return false;
+
+	reset(h);
+
+	#if defined(_WIN32)
+		const int REUSE = SO_REUSEADDR;
+	#else
+		const int REUSE = SO_REUSEPORT;
+	#endif
+	
+	if (reuseSock && (domain == AF_INET || domain == AF_INET6)) {
+		int reuse = 1;
+		if (!set_option(SOL_SOCKET, REUSE, reuse))
+			return close_on_err();
+	}
+
+	if (!bind(addr) || !listen(queSize))
+		return close_on_err();
+
+	return true;
+}
+
+// --------------------------------------------------------------------------
+
+stream_socket acceptor::accept(sock_address* clientAddr /*=nullptr*/)
+{
+	sockaddr* p = clientAddr ? clientAddr->sockaddr_ptr() : nullptr;
+	socklen_t len = clientAddr ? clientAddr->size() : 0;
+
+	socket_t s = check_socket(::accept(handle(), p, clientAddr ? &len : nullptr));
+	return stream_socket(s);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// end namespace sockpp
+}
+
diff --git b/src/connector.cpp a/src/connector.cpp
new file mode 100644
index 0000000..dbe57dd
--- /dev/null
+++ a/src/connector.cpp
@@ -0,0 +1,23 @@
+#include "sockpp/connector.h"
+
+namespace sockpp {
+
+bool connector::connect(const sock_address& addr)
+{
+    sa_family_t domain = addr.family();
+	socket_t h = create_handle(domain);
+
+	if (!check_ret_bool(h))
+		return false;
+
+	// This will close the old connection, if any.
+	reset(h);
+
+	if (!check_ret_bool(::connect(h, addr.sockaddr_ptr(), addr.size())))
+		return close_on_err();
+
+	return true;
+}
+
+}
+
diff --git b/src/datagram_socket.cpp a/src/datagram_socket.cpp
new file mode 100644
index 0000000..41cf832
--- /dev/null
+++ a/src/datagram_socket.cpp
@@ -0,0 +1,38 @@
+#include "sockpp/datagram_socket.h"
+#include "sockpp/exception.h"
+#include <algorithm>
+
+using namespace std::chrono;
+
+namespace sockpp {
+
+datagram_socket::datagram_socket(const sock_address& addr)
+{
+	auto domain = addr.family();
+	socket_t h = create_handle(domain);
+
+	if (check_socket_bool(h)) {
+		reset(h);
+		// TODO: If the bind fails, should we close the socket and fail completely?
+		bind(addr);
+	}
+}
+
+ssize_t datagram_socket::recv_from(void* buf, size_t n, int flags,
+								   sock_address* srcAddr /*=nullptr*/)
+{
+	sockaddr* p = srcAddr ? srcAddr->sockaddr_ptr() : nullptr;
+    socklen_t len = srcAddr ? srcAddr->size() : 0;
+
+	// TODO: Check returned length
+    #if defined(_WIN32)
+        return check_ret(::recvfrom(handle(), reinterpret_cast<char*>(buf),
+                                    int(n), flags, p, &len));
+    #else
+        return check_ret(::recvfrom(handle(), buf, n, flags, p, &len));
+    #endif
+}
+
+
+}
+
diff --git b/src/exception.cpp a/src/exception.cpp
new file mode 100644
index 0000000..7a5b37a
--- /dev/null
+++ a/src/exception.cpp
@@ -0,0 +1,50 @@
+#include "sockpp/exception.h"
+#include "sockpp/platform.h"
+#include <cstring>
+
+// Used to explicitly ignore the returned value of a function call.
+#define ignore_result(x) if (x) {}
+
+using namespace std;
+
+namespace sockpp {
+
+sys_error::sys_error(int err) : runtime_error(error_str(err)), errno_(err)
+{
+}
+
+std::string sys_error::error_str(int err)
+{
+	char buf[1024];
+	buf[0] = '\x0';
+
+	#if defined(_WIN32)
+		FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+			NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+			buf, sizeof(buf), NULL);
+    #else
+    	#ifdef _GNU_SOURCE
+			#if !defined(__GLIBC__)
+			// use the XSI standard behavior.
+				int e = strerror_r(err, buf, sizeof(buf));
+				auto s = strerror(e);
+				return s ? std::string(s) : std::string();
+			#else
+			// assume GNU exception
+				auto s = strerror_r(err, buf, sizeof(buf));
+				return s ? std::string(s) : std::string();
+			#endif
+        #else
+            ignore_result(strerror_r(err, buf, sizeof(buf)));
+        #endif
+    #endif
+	return std::string(buf);
+}
+
+getaddrinfo_error::getaddrinfo_error(int err, const string& hostname)
+    : runtime_error(gai_strerror(err)), error_(err), hostname_(hostname)
+{
+}
+
+}
+
diff --git b/src/inet6_address.cpp a/src/inet6_address.cpp
new file mode 100644
index 0000000..64c7ff8
--- /dev/null
+++ a/src/inet6_address.cpp
@@ -0,0 +1,80 @@
+#include "sockpp/inet6_address.h"
+#include "sockpp/exception.h"
+
+using namespace std;
+
+namespace sockpp {
+
+bool inet6_address::is_set() const
+{
+	static const auto EMPTY_ADDR = sockaddr_in6{};
+	return std::memcmp(&addr_, &EMPTY_ADDR, SZ) != 0;
+}
+
+in6_addr inet6_address::resolve_name(const string& saddr)
+{
+	#if !defined(_WIN32)
+		in6_addr ia;
+		if (::inet_pton(ADDRESS_FAMILY, saddr.c_str(), &ia) == 1)
+			return ia;
+	#endif
+
+    addrinfo *res, hints = addrinfo{};
+    hints.ai_family = ADDRESS_FAMILY;
+    hints.ai_socktype = SOCK_STREAM;
+
+    int gai_err = ::getaddrinfo(saddr.c_str(), NULL, &hints, &res);
+
+    #if !defined(_WIN32)
+        if (gai_err == EAI_SYSTEM)
+            throw sys_error();
+    #endif
+
+    if (gai_err != 0)
+        throw getaddrinfo_error(gai_err, saddr);
+
+
+    auto ipv6 = reinterpret_cast<sockaddr_in6*>(res->ai_addr);
+    auto addr = ipv6->sin6_addr;
+    freeaddrinfo(res);
+    return addr;
+}
+
+void inet6_address::create(const in6_addr& addr, in_port_t port)
+{
+	addr_ = sockaddr_in6{};
+    addr_.sin6_family = AF_INET6;
+    addr_.sin6_flowinfo = 0;
+    addr_.sin6_addr = addr;
+    addr_.sin6_port = htons(port);
+}
+
+void inet6_address::create(const string& saddr, in_port_t port)
+{
+	addr_ = sockaddr_in6{};
+	addr_.sin6_family = AF_INET6;
+    addr_.sin6_flowinfo = 0;
+	addr_.sin6_addr = resolve_name(saddr.c_str());
+	addr_.sin6_port = htons(port);
+}
+
+string inet6_address::to_string() const
+{
+    char buf[INET6_ADDRSTRLEN];
+    auto str = inet_ntop(AF_INET6, (void*) &(addr_.sin6_addr),
+						 buf, INET6_ADDRSTRLEN);
+    return std::string("[") + std::string(str ? str : "<unknown>")
+        + "]:" + std::to_string(unsigned(port()));
+}
+
+ostream& operator<<(ostream& os, const inet6_address& addr)
+{
+	char buf[INET6_ADDRSTRLEN];
+	auto str = inet_ntop(AF_INET6, (void*) &(addr.sockaddr_in6_ptr()->sin6_addr),
+						 buf, INET6_ADDRSTRLEN);
+	os << "[" << (str ? str : "<unknown>") << "]:" << unsigned(addr.port());
+	return os;
+}
+
+}
+
diff --git b/src/inet_address.cpp a/src/inet_address.cpp
new file mode 100644
index 0000000..d14382a
--- /dev/null
+++ a/src/inet_address.cpp
@@ -0,0 +1,75 @@
+#include "sockpp/inet_address.h"
+#include "sockpp/exception.h"
+
+using namespace std;
+
+namespace sockpp {
+
+bool inet_address::is_set() const
+{
+	static const auto EMPTY_ADDR = sockaddr_in{};
+	return std::memcmp(&addr_, &EMPTY_ADDR, SZ) != 0;
+}
+
+in_addr_t inet_address::resolve_name(const std::string& saddr)
+{
+	#if !defined(_WIN32)
+		in_addr ia;
+		if (::inet_pton(ADDRESS_FAMILY, saddr.c_str(), &ia) == 1)
+			return ia.s_addr;
+	#endif
+
+    addrinfo *res, hints = addrinfo{};
+    hints.ai_family = ADDRESS_FAMILY;
+    hints.ai_socktype = SOCK_STREAM;
+
+    int gai_err = ::getaddrinfo(saddr.c_str(), NULL, &hints, &res);
+
+    #if !defined(_WIN32)
+        if (gai_err == EAI_SYSTEM)
+            throw sys_error();
+    #endif
+
+    if (gai_err != 0)
+        throw getaddrinfo_error(gai_err, saddr);
+
+    auto ipv4 = reinterpret_cast<sockaddr_in*>(res->ai_addr);
+    auto addr = ipv4->sin_addr.s_addr;
+    freeaddrinfo(res);
+    return addr;
+}
+
+void inet_address::create(uint32_t addr, in_port_t port)
+{
+	addr_ = sockaddr_in{};
+	addr_.sin_family = AF_INET;
+	addr_.sin_addr.s_addr = htonl(addr);
+	addr_.sin_port = htons(port);
+}
+
+void inet_address::create(const std::string& saddr, in_port_t port)
+{
+	addr_ = sockaddr_in{};
+	addr_.sin_family = AF_INET;
+	addr_.sin_addr.s_addr = resolve_name(saddr.c_str());
+	addr_.sin_port = htons(port);
+}
+
+string inet_address::to_string() const
+{
+	char buf[INET_ADDRSTRLEN];
+	auto str = inet_ntop(AF_INET, (void*) &(addr_.sin_addr), buf, INET_ADDRSTRLEN);
+	return std::string(str ? str : "<unknown>")
+		+ ":" + std::to_string(unsigned(port()));
+}
+
+ostream& operator<<(ostream& os, const inet_address& addr)
+{
+    char buf[INET_ADDRSTRLEN];
+	auto str = inet_ntop(AF_INET, (void*) &(addr.sockaddr_in_ptr()->sin_addr),
+						 buf, INET_ADDRSTRLEN);
+	os << (str ? str : "<unknown>") << ":" << unsigned(addr.port());
+	return os;
+}
+
+}
diff --git b/src/linux/can_address.cpp a/src/linux/can_address.cpp
new file mode 100644
index 0000000..7d24064
--- /dev/null
+++ a/src/linux/can_address.cpp
@@ -0,0 +1,104 @@
+// can_address.cpp
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2014-2021 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include "sockpp/can_address.h"
+#include "sockpp/socket.h"
+#include <cstring>
+#include <stdexcept>
+#include <sys/ioctl.h>
+#include <net/if.h>
+
+using namespace std;
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+constexpr sa_family_t can_address::ADDRESS_FAMILY;
+
+// --------------------------------------------------------------------------
+
+can_address::can_address(unsigned ifindex) : addr_{}
+{
+	addr_.can_family = AF_CAN;
+	addr_.can_ifindex = ifindex;
+}
+
+can_address::can_address(const string& iface) : addr_{}
+{
+	unsigned idx = if_nametoindex(iface.c_str());
+
+	if (idx != 0) {
+		addr_.can_family = AF_CAN;
+		addr_.can_ifindex = idx;
+	}
+}
+
+can_address::can_address(const sockaddr& addr)
+{
+    auto domain = addr.sa_family;
+    if (domain != AF_CAN)
+        throw std::invalid_argument("Not a SocketCAN address");
+
+    std::memcpy(&addr_, &addr, sizeof(sockaddr));
+}
+
+string can_address::iface() const
+{
+	if (addr_.can_family == AF_UNSPEC)
+		return string("none");
+
+	if (addr_.can_ifindex == 0)
+		return string("any");
+
+	char buf[IF_NAMESIZE];
+	const char* iface = if_indextoname(addr_.can_ifindex, buf);
+
+	return string(iface ? iface : "unknown");
+}
+
+
+// --------------------------------------------------------------------------
+
+ostream& operator<<(ostream& os, const can_address& addr)
+{
+	os << "can:" << addr.iface();
+	return os;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// End namespace sockpp
+}
diff --git b/src/linux/can_socket.cpp a/src/linux/can_socket.cpp
new file mode 100644
index 0000000..dc7e699
--- /dev/null
+++ a/src/linux/can_socket.cpp
@@ -0,0 +1,92 @@
+// can_socket.cpp
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2021 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include "sockpp/can_socket.h"
+#include "sockpp/socket.h"
+#include <sys/ioctl.h>
+
+using namespace std;
+using namespace std::chrono;
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+can_socket::can_socket(const can_address& addr)
+{
+	socket_t h = create_handle(SOCK_RAW, CAN_RAW);
+
+	if (check_socket_bool(h)) {
+		reset(h);
+		bind(addr);
+	}
+}
+
+system_clock::time_point can_socket::last_frame_time()
+{
+	timeval tv {};
+
+	// TODO: Handle error
+	::ioctl(handle(), SIOCGSTAMP, &tv);
+	return to_timepoint(tv);
+}
+
+double can_socket::last_frame_timestamp()
+{
+	timeval tv {};
+
+	// TODO: Handle error
+	::ioctl(handle(), SIOCGSTAMP, &tv);
+	return double(tv.tv_sec) + 1.0e-6 * tv.tv_usec;
+}
+
+
+// --------------------------------------------------------------------------
+
+ssize_t can_socket::recv_from(can_frame *frame, int flags,
+							  can_address* srcAddr /*=nullptr*/)
+{
+	sockaddr* p = srcAddr ? srcAddr->sockaddr_ptr() : nullptr;
+    socklen_t len = srcAddr ? srcAddr->size() : 0;
+
+	// TODO: Check returned length
+	return check_ret(::recvfrom(handle(), frame, sizeof(can_frame),
+								flags, p, &len));
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// End namespace sockpp
+}
diff --git b/src/socket.cpp a/src/socket.cpp
new file mode 100644
index 0000000..e055aef
--- /dev/null
+++ a/src/socket.cpp
@@ -0,0 +1,231 @@
+#include "sockpp/socket.h"
+#include "sockpp/exception.h"
+#include <algorithm>
+#include <cstring>
+#include <fcntl.h>
+
+// Used to explicitly ignore the returned value of a function call.
+#define ignore_result(x) if (x) {}
+
+using namespace std::chrono;
+
+namespace sockpp {
+
+timeval to_timeval(const microseconds& dur)
+{
+	const seconds sec = duration_cast<seconds>(dur);
+
+	timeval tv;
+	#if defined(_WIN32)
+		tv.tv_sec  = long(sec.count());
+	#else
+		tv.tv_sec  = time_t(sec.count());
+	#endif
+	tv.tv_usec = suseconds_t(duration_cast<microseconds>(dur - sec).count());
+	return tv;
+}
+
+int socket::get_last_error()
+{
+	#if defined(_WIN32)
+		return ::WSAGetLastError();
+	#else
+		int err = errno;
+		return err;
+	#endif
+}
+
+bool socket::close(socket_t h)
+{
+	#if defined(_WIN32)
+		return ::closesocket(h) >= 0;
+	#else
+		return ::close(h) >= 0;
+	#endif
+}
+
+void socket::initialize()
+{
+	#if defined(_WIN32)
+		WSADATA wsadata;
+		::WSAStartup(MAKEWORD(2, 0), &wsadata);
+	#else
+		// Don't signal on socket write errors.
+		::signal(SIGPIPE, SIG_IGN);
+	#endif
+}
+
+void socket::destroy()
+{
+	#if defined(_WIN32)
+		::WSACleanup();
+	#endif
+}
+
+socket socket::create(int domain, int type, int protocol /*=0*/)
+{
+	socket sock(::socket(domain, type, protocol));
+	if (!sock)
+		sock.clear(get_last_error());
+	return sock;
+}
+
+socket socket::clone() const
+{
+	socket_t h = INVALID_SOCKET;
+	#if defined(_WIN32)
+		WSAPROTOCOL_INFO protInfo;
+		if (::WSADuplicateSocket(handle_, ::GetCurrentProcessId(), &protInfo) == 0)
+			h = ::WSASocket(AF_INET, SOCK_STREAM, 0, &protInfo, 0, WSA_FLAG_OVERLAPPED);
+		// TODO: Set lastErr_ on failure
+	#else
+		h = ::dup(handle_);
+	#endif
+
+	return socket(h); 
+}
+
+std::tuple<socket, socket> socket::pair(int domain, int type, int protocol /*=0*/)
+{
+	socket sock0, sock1;
+
+	#if !defined(_WIN32)
+		int sv[2];
+		int ret = ::socketpair(domain, type, protocol, sv);
+
+		if (ret == 0) {
+			sock0.reset(sv[0]);
+			sock1.reset(sv[1]);
+		}
+		else {
+			int err = get_last_error();
+			sock0.clear(err);
+			sock1.clear(err);
+		}
+	#else
+		sock0.clear(ENOTSUP);
+		sock1.clear(ENOTSUP);
+	#endif
+
+	// TODO: Should we set an "unsupported" error on Windows?
+
+	return std::make_tuple<socket, socket>(std::move(sock0), std::move(sock1));
+}
+
+void socket::reset(socket_t h /*=INVALID_SOCKET*/)
+{
+	socket_t oh = handle_;
+	handle_ = h;
+	if (oh != INVALID_SOCKET)
+		close(oh);
+	clear();
+}
+
+bool socket::bind(const sock_address& addr)
+{
+	return check_ret_bool(::bind(handle_, addr.sockaddr_ptr(), addr.size()));
+}
+
+sock_address_any socket::address() const
+{
+	auto addrStore = sockaddr_storage{};
+	socklen_t len = sizeof(sockaddr_storage);
+
+	if (!check_ret_bool(::getsockname(handle_,
+				reinterpret_cast<sockaddr*>(&addrStore), &len)))
+		return sock_address_any{};
+
+	return sock_address_any(addrStore, len);
+}
+
+sock_address_any socket::peer_address() const
+{
+	auto addrStore = sockaddr_storage{};
+	socklen_t len = sizeof(sockaddr_storage);
+
+	if (!check_ret_bool(::getpeername(handle_,
+				reinterpret_cast<sockaddr*>(&addrStore), &len)))
+		return sock_address_any{};
+
+	return sock_address_any(addrStore, len);
+}
+
+bool socket::get_option(int level, int optname, void* optval, socklen_t* optlen) const
+{
+	#if defined(_WIN32)
+        if (optval && optlen) {
+            int len = static_cast<int>(*optlen);
+            if (check_ret_bool(::getsockopt(handle_, level, optname,
+                                            static_cast<char*>(optval), &len))) {
+                *optlen = static_cast<socklen_t>(len);
+                return true;
+            }
+        }
+        return false;
+	#else
+		return check_ret_bool(::getsockopt(handle_, level, optname, optval, optlen));
+	#endif
+}
+
+bool socket::set_option(int level, int optname, const void* optval, socklen_t optlen)
+{
+	#if defined(_WIN32)
+		return check_ret_bool(::setsockopt(handle_, level, optname, 
+										   static_cast<const char*>(optval), 
+										   static_cast<int>(optlen)));
+	#else
+		return check_ret_bool(::setsockopt(handle_, level, optname, optval, optlen));
+	#endif
+}
+
+bool socket::set_non_blocking(bool on /*=true*/)
+{
+	#if defined(_WIN32)
+		unsigned long mode = on ? 1 : 0;
+		return check_ret_bool(::ioctlsocket(handle_, FIONBIO, &mode));
+	#else
+		/**
+		 * TODO: Consider a generic function:
+		 *   bool set_flag(int flag, bool on=true);
+		 * Used like:
+		 *   set_flag(O_NONBLOCK, on);
+		 */
+		int flags = ::fcntl(handle_, F_GETFL, 0);
+
+		if (flags == -1) {
+			set_last_error();
+			return false;
+		}
+		flags = on ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK);
+
+		if (::fcntl(handle_, F_SETFL, flags) == -1) {
+			set_last_error();
+			return false;
+		}
+		return true;
+	#endif
+}
+
+std::string socket::error_str(int err)
+{
+	return sys_error::error_str(err);
+}
+
+bool socket::shutdown(int how /*=SHUT_RDWR*/)
+{
+	return check_ret_bool(::shutdown(handle_, how));
+}
+
+bool socket::close()
+{
+	if (handle_ != INVALID_SOCKET) {
+		if (!close(release())){
+			set_last_error();
+			return false;
+		}
+	}
+	return true;
+}
+
+}
+
diff --git b/src/stream_socket.cpp a/src/stream_socket.cpp
new file mode 100644
index 0000000..6840afd
--- /dev/null
+++ a/src/stream_socket.cpp
@@ -0,0 +1,152 @@
+#include "socket-cpp/stream_socket.h"
+#include "socket-cpp/exception.h"
+#include <algorithm>
+#include <memory>
+
+using namespace std::chrono;
+
+namespace socket-cpp {
+
+stream_socket stream_socket::create(int domain, int protocol /*=0*/)
+{
+	stream_socket sock(::socket(domain, COMM_TYPE, protocol));
+	if (!sock)
+		sock.clear(get_last_error());
+	return sock;
+}
+
+ssize_t stream_socket::read(void *buf, size_t n)
+{
+	#if defined(_WIN32)
+		return check_ret(::recv(handle(), reinterpret_cast<char*>(buf),
+								int(n), 0));
+	#else
+		return check_ret(::recv(handle(), buf, n, 0));
+	#endif
+}
+
+ssize_t stream_socket::read_n(void *buf, size_t n)
+{
+	size_t	nr = 0;
+	ssize_t	nx = 0;
+
+	uint8_t *b = reinterpret_cast<uint8_t*>(buf);
+
+	while (nr < n) {
+		if ((nx = read(b+nr, n-nr)) < 0 && last_error() == EINTR)
+			continue;
+
+		if (nx <= 0)
+			break;
+
+		nr += nx;
+	}
+
+	return (nr == 0 && nx < 0) ? nx : ssize_t(nr);
+}
+
+
+ssize_t stream_socket::read(const std::vector<iovec>& ranges)
+{
+	if (ranges.empty())
+		return 0;
+
+	#if !defined(_WIN32)
+		return check_ret(::readv(handle(), ranges.data(), int(ranges.size())));
+	#else
+		std::vector<WSABUF> bufs;
+		for (const auto& iovec : ranges) {
+			bufs.push_back({
+				static_cast<ULONG>(iovec.iov_len),
+				static_cast<CHAR*>(iovec.iov_base)
+			});
+		}
+
+		DWORD flags = 0,
+			  nread = 0,
+			  nbuf = DWORD(bufs.size());
+
+		auto ret = check_ret(::WSARecv(handle(), bufs.data(), nbuf, &nread, &flags, nullptr, nullptr));
+		return ssize_t(ret == SOCKET_ERROR ? ret : nread);
+	#endif
+}
+
+bool stream_socket::read_timeout(const microseconds& to)
+{
+	auto tv = 
+		#if defined(_WIN32)
+			DWORD(duration_cast<milliseconds>(to).count());
+		#else
+			to_timeval(to);
+		#endif
+	return set_option(SOL_SOCKET, SO_RCVTIMEO, tv);
+}
+
+ssize_t stream_socket::write(const void *buf, size_t n)
+{
+	#if defined(_WIN32)
+		return check_ret(::send(handle(), reinterpret_cast<const char*>(buf),
+								int(n) , 0));
+	#else
+		return check_ret(::send(handle(), buf, n , 0));
+	#endif
+}
+
+ssize_t stream_socket::write_n(const void *buf, size_t n)
+{
+	size_t	nw = 0;
+	ssize_t	nx = 0;
+
+	const uint8_t *b = reinterpret_cast<const uint8_t*>(buf);
+
+	while (nw < n) {
+		if ((nx = write(b+nw, n-nw)) < 0 && last_error() == EINTR)
+			continue;
+
+		if (nx <= 0)
+			break;
+
+		nw += nx;
+	}
+
+	return (nw == 0 && nx < 0) ? nx : ssize_t(nw);
+}
+
+ssize_t stream_socket::write(const std::vector<iovec>& ranges)
+{
+	if (ranges.empty())
+		return 0;
+
+	#if !defined(_WIN32)
+		return check_ret(::writev(handle(), ranges.data(), int(ranges.size())));
+	#else
+		std::vector<WSABUF> bufs;
+		for (const auto& iovec : ranges) {
+			bufs.push_back({
+				static_cast<ULONG>(iovec.iov_len),
+				static_cast<CHAR*>(iovec.iov_base)
+			});
+		}
+
+		DWORD nwritten = 0,
+			nmsg = DWORD(bufs.size());
+
+		auto ret = check_ret(::WSASend(handle(), bufs.data(), nmsg, &nwritten, 0, nullptr, nullptr));
+		return ssize_t(ret == SOCKET_ERROR ? ret : nwritten);
+	#endif
+}
+
+bool stream_socket::write_timeout(const microseconds& to)
+{
+	auto tv = 
+		#if defined(_WIN32)
+			DWORD(duration_cast<milliseconds>(to).count());
+		#else
+			to_timeval(to);
+		#endif
+
+	return set_option(SOL_SOCKET, SO_SNDTIMEO, tv);
+}
+
+}
+
diff --git b/src/unix/unix_address.cpp a/src/unix/unix_address.cpp
new file mode 100644
index 0000000..a4088ed
--- /dev/null
+++ a/src/unix/unix_address.cpp
@@ -0,0 +1,79 @@
+// unix_address.cpp
+//
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2014-2017 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+
+#include "sockpp/unix_address.h"
+#include <cstring>
+#include <stdexcept>
+
+using namespace std;
+
+namespace sockpp {
+
+/////////////////////////////////////////////////////////////////////////////
+
+constexpr sa_family_t unix_address::ADDRESS_FAMILY;
+constexpr size_t unix_address::MAX_PATH_NAME;
+
+// --------------------------------------------------------------------------
+
+unix_address::unix_address(const string& path)
+{
+	addr_.sun_family = ADDRESS_FAMILY;
+	::strncpy(addr_.sun_path, path.c_str(), MAX_PATH_NAME);
+}
+
+unix_address::unix_address(const sockaddr& addr)
+{
+    auto domain = addr.sa_family;
+    if (domain != AF_UNIX)
+        throw std::invalid_argument("Not a UNIX-domain address");
+
+    // TODO: We should check the path, or at least see that it has
+    // proper NUL termination.
+    std::memcpy(&addr_, &addr, sizeof(sockaddr));
+}
+
+// --------------------------------------------------------------------------
+
+ostream& operator<<(ostream& os, const unix_address& addr)
+{
+	os << "unix:" << addr.path();
+	return os;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// End namespace sockpp
+}
diff --git b/tests/udptst.cpp a/tests/udptst.cpp
new file mode 100644
index 0000000..f440294
--- /dev/null
+++ a/tests/udptst.cpp
@@ -0,0 +1,75 @@
+// udptst.cpp
+
+#include <iostream>
+#include "sockpp/socket.h"
+
+using namespace std;
+
+// --------------------------------------------------------------------------
+
+int do_recv(sockpp::udp_socket& sock)
+{
+	char buf[6];
+	int n = sock.recv(buf, sizeof(buf));
+
+	if (n < 0) {
+		cerr << "Error sending packet: ["
+			<< sock.last_error() << "]" << endl;
+		return -1;
+	}
+
+	cout << "Received " << n << " bytes" << flush;
+	buf[n] = '\0';
+	cout << " '" << buf << "'" << endl;
+	return 0;
+}
+
+// --------------------------------------------------------------------------
+
+int main()
+{
+	in_port_t port = 12345;
+
+	cout << "Testing UDP sockets" << endl;
+	sockpp::udp_socket srvrSock;
+
+	if (!srvrSock) {
+		cerr << "Error creating server socket [" 
+			<< srvrSock.last_error() << "]" << endl;
+		return 1;
+	}
+
+	if (!srvrSock.bind(port)) {
+		cerr << "Error binding to port: " << port << " [" 
+			<< srvrSock.last_error() << "]" << endl;
+		return 1;
+	}
+
+	sockpp::udp_socket cliSock;
+
+	if (!cliSock) {
+		cerr << "Error creating server socket [" 
+			<< cliSock.last_error() << "]" << endl;
+		return 1;
+	}
+
+	sockpp::inet_address localAddr("localhost", port);
+
+	if (!cliSock.connect(localAddr)) {
+		cerr << "Error connecting to port: " << port << " [" 
+			<< cliSock.last_error() << "]" << endl;
+		return 1;
+	}
+
+	cliSock.send("Hello");
+	do_recv(srvrSock);
+
+	cliSock.close();
+
+	sockpp::udp_socket sock;
+	sock.sendto("bubba", localAddr);
+	do_recv(srvrSock);
+
+	return 0;
+}
+
diff --git b/tests/unit/test_acceptor.cpp a/tests/unit/test_acceptor.cpp
new file mode 100644
index 0000000..2ff5f38
--- /dev/null
+++ a/tests/unit/test_acceptor.cpp
@@ -0,0 +1,130 @@
+// test_acceptor.cpp
+//
+// Unit tests for the `acceptor` class(es).
+//
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+//
+
+#include "sockpp/acceptor.h"
+#include "sockpp/inet_address.h"
+#include "catch2/catch.hpp"
+#include <string>
+
+using namespace sockpp;
+
+TEST_CASE("acceptor default constructor", "[acceptor]") {
+	acceptor sock;
+	REQUIRE(!sock);
+	REQUIRE(!sock.is_open());
+}
+
+TEST_CASE("acceptor handle constructor", "[acceptor]") {
+	constexpr auto HANDLE = socket_t(3);
+
+	SECTION("valid handle") {
+		acceptor sock(HANDLE);
+		REQUIRE(sock);
+		REQUIRE(sock.is_open());
+	}
+
+	SECTION("invalid handle") {
+		acceptor sock(INVALID_SOCKET);
+		REQUIRE(!sock);
+		REQUIRE(!sock.is_open());
+		// TODO: Should this set an error?
+		REQUIRE(sock.last_error() == 0);
+	}
+}
+
+TEST_CASE("acceptor address constructor", "[acceptor]") {
+	SECTION("valid address") {
+		const auto ADDR = inet_address("localhost", 12345);
+
+		acceptor sock(ADDR);
+		REQUIRE(sock);
+		REQUIRE(sock.is_open());
+		REQUIRE(sock.last_error() == 0);
+		REQUIRE(sock.address() == ADDR);
+	}
+
+	SECTION("invalid address") {
+		const auto ADDR = sock_address_any{};
+
+		acceptor sock(ADDR);
+		REQUIRE(!sock);
+		REQUIRE(!sock.is_open());
+
+        // Windows returns a different error code than *nix
+        #if defined(_WIN32)
+            REQUIRE(sock.last_error() == WSAEINVAL);
+        #else
+            REQUIRE(sock.last_error() == EAFNOSUPPORT);
+        #endif
+	}
+}
+
+TEST_CASE("acceptor create", "[acceptor]") {
+	SECTION("valid domain") {
+		auto sock = acceptor::create(AF_INET);
+
+		REQUIRE(sock);
+		REQUIRE(sock.is_open());
+		REQUIRE(sock.last_error() == 0);
+
+        // Windows returns unknown family for unbound socket
+        // Windows returns a different error code than *nix
+        #if defined(_WIN32)
+            REQUIRE(sock.family() == AF_UNSPEC);
+        #else
+            REQUIRE(sock.family() == AF_INET);
+        #endif
+	}
+
+	SECTION("invalid domain") {
+		auto sock = acceptor::create(AF_UNSPEC);
+
+		REQUIRE(!sock);
+		REQUIRE(!sock.is_open());
+
+        // Windows returns a different error code than *nix
+        #if defined(_WIN32)
+            REQUIRE(sock.last_error() == WSAEINVAL);
+        #else
+            REQUIRE(sock.last_error() == EAFNOSUPPORT);
+        #endif
+	}
+}
+
diff --git b/tests/unit/test_connector.cpp a/tests/unit/test_connector.cpp
new file mode 100644
index 0000000..0ea0faa
--- /dev/null
+++ a/tests/unit/test_connector.cpp
@@ -0,0 +1,66 @@
+// test_connector.cpp
+//
+// Unit tests for the `connector` class(es).
+//
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+//
+
+#include "sockpp/connector.h"
+#include "sockpp/sock_address.h"
+#include "catch2/catch.hpp"
+#include <string>
+
+using namespace sockpp;
+
+// Test that connector errors properly when given an empty address.
+TEST_CASE("connector unspecified address", "[connector]") {
+	connector conn;
+	REQUIRE(!conn);
+
+	sock_address_any addr;
+
+	bool ok = conn.connect(addr);
+	REQUIRE(!ok);
+
+    // Windows returns a different error code than *nix
+    #if defined(_WIN32)
+        REQUIRE(conn.last_error() == WSAENOTSOCK);
+    #else
+        REQUIRE(conn.last_error() == EAFNOSUPPORT);
+    #endif
+}
+
+
diff --git b/tests/unit/test_datagram_socket.cpp a/tests/unit/test_datagram_socket.cpp
new file mode 100644
index 0000000..925872a
--- /dev/null
+++ a/tests/unit/test_datagram_socket.cpp
@@ -0,0 +1,98 @@
+// test_datagram_socket.cpp
+//
+// Unit tests for the `datagram_socket` class(es).
+//
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+//
+
+#include "sockpp/datagram_socket.h"
+#include "sockpp/inet_address.h"
+#include "catch2/catch.hpp"
+#include <string>
+
+using namespace sockpp;
+
+TEST_CASE("datagram_socket default constructor", "[datagram_socket]") {
+	datagram_socket sock;
+	REQUIRE(!sock);
+	REQUIRE(!sock.is_open());
+}
+
+TEST_CASE("datagram_socket handle constructor", "[datagram_socket]") {
+	constexpr auto HANDLE = socket_t(3);
+
+	SECTION("valid handle") {
+		datagram_socket sock(HANDLE);
+		REQUIRE(sock);
+		REQUIRE(sock.is_open());
+	}
+
+	SECTION("invalid handle") {
+		datagram_socket sock(INVALID_SOCKET);
+		REQUIRE(!sock);
+		REQUIRE(!sock.is_open());
+		// TODO: Should this set an error?
+		REQUIRE(sock.last_error() == 0);
+	}
+}
+
+TEST_CASE("datagram_socket address constructor", "[datagram_socket]") {
+	SECTION("valid address") {
+		const auto ADDR = inet_address("localhost", 12345);
+
+		datagram_socket sock(ADDR);
+		REQUIRE(sock);
+		REQUIRE(sock.is_open());
+		REQUIRE(sock.last_error() == 0);
+		REQUIRE(sock.address() == ADDR);
+	}
+
+	SECTION("invalid address") {
+		const auto ADDR = sock_address_any();
+
+		datagram_socket sock(ADDR);
+		REQUIRE(!sock);
+		REQUIRE(!sock.is_open());
+
+        // Windows returns a different error code than *nix
+        #if defined(_WIN32)
+            REQUIRE(sock.last_error() == WSAEINVAL);
+        #else
+            REQUIRE(sock.last_error() == EAFNOSUPPORT);
+        #endif
+	}
+}
+
diff --git b/tests/unit/test_inet_address.cpp a/tests/unit/test_inet_address.cpp
new file mode 100644
index 0000000..e56835d
--- /dev/null
+++ a/tests/unit/test_inet_address.cpp
@@ -0,0 +1,144 @@
+// test_inet_address.cpp
+//
+// Unit tests for the `inet_address` class.
+//
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2018 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+//
+
+#include "sockpp/inet_address.h"
+#include "catch2/catch.hpp"
+#include <string>
+
+using namespace sockpp;
+
+const uint32_t      ANY_ADDR        { INADDR_ANY };         // Any iface 0x00000000
+const uint32_t      LOCALHOST_ADDR  { INADDR_LOOPBACK };    // Localhost 0x7F000001
+const std::string   LOCALHOST_STR   { "localhost" };
+const in_port_t     PORT { 12345 };
+
+TEST_CASE("inet_address default constructor", "[address]") {
+    inet_address addr;
+
+    REQUIRE(!addr.is_set());
+    REQUIRE(0 == addr.address());
+    REQUIRE(0 == addr.port());
+    REQUIRE(sizeof(sockaddr_in) == addr.size());
+
+    SECTION("creating address from int32") {
+        addr.create(LOCALHOST_ADDR, PORT);
+
+        REQUIRE(addr.is_set());
+        REQUIRE(LOCALHOST_ADDR == addr.address());
+        REQUIRE(PORT == addr.port());
+
+        REQUIRE(uint8_t((LOCALHOST_ADDR >>  0) &0xFF) == addr[0]);
+        REQUIRE(uint8_t((LOCALHOST_ADDR >>  8) &0xFF) == addr[1]);
+        REQUIRE(uint8_t((LOCALHOST_ADDR >> 16) &0xFF) == addr[2]);
+        REQUIRE(uint8_t((LOCALHOST_ADDR >> 24) &0xFF) == addr[3]);
+
+        // Check the low-level struct
+        REQUIRE(AF_INET == addr.sockaddr_in_ptr()->sin_family);
+        REQUIRE(LOCALHOST_ADDR == ntohl(addr.sockaddr_in_ptr()->sin_addr.s_addr));
+        REQUIRE(PORT == ntohs(addr.sockaddr_in_ptr()->sin_port));
+    }
+
+    SECTION("creating address from name") {
+        addr.create(LOCALHOST_STR, PORT);
+
+        REQUIRE(addr.is_set());
+        REQUIRE(LOCALHOST_ADDR == addr.address());
+        REQUIRE(PORT == addr.port());
+
+        // Check the low-level struct
+        REQUIRE(AF_INET == addr.sockaddr_in_ptr()->sin_family);
+        REQUIRE(LOCALHOST_ADDR == ntohl(addr.sockaddr_in_ptr()->sin_addr.s_addr));
+        REQUIRE(PORT == ntohs(addr.sockaddr_in_ptr()->sin_port));
+    }
+}
+
+// When created using only a port number this should use the
+// "any" address to bind to all interfaces (typ for server)
+TEST_CASE("inet_address port-only constructor", "[address]") {
+    inet_address addr(PORT);
+
+    REQUIRE(addr.is_set());
+    REQUIRE(ANY_ADDR == addr.address());
+    REQUIRE(PORT == addr.port());
+
+    // Check the low-level struct
+    REQUIRE(AF_INET == addr.sockaddr_in_ptr()->sin_family);
+}
+
+TEST_CASE("inet_address int32_t constructor", "[address]") {
+    inet_address addr(LOCALHOST_ADDR, PORT);
+
+    REQUIRE(addr.is_set());
+    REQUIRE(LOCALHOST_ADDR == addr.address());
+    REQUIRE(PORT == addr.port());
+
+    REQUIRE(uint8_t((LOCALHOST_ADDR >>  0) &0xFF) == addr[0]);
+    REQUIRE(uint8_t((LOCALHOST_ADDR >>  8) &0xFF) == addr[1]);
+    REQUIRE(uint8_t((LOCALHOST_ADDR >> 16) &0xFF) == addr[2]);
+    REQUIRE(uint8_t((LOCALHOST_ADDR >> 24) &0xFF) == addr[3]);
+
+    // Check the low-level struct
+    REQUIRE(AF_INET == addr.sockaddr_in_ptr()->sin_family);
+    REQUIRE(LOCALHOST_ADDR == ntohl(addr.sockaddr_in_ptr()->sin_addr.s_addr));
+    REQUIRE(PORT == ntohs(addr.sockaddr_in_ptr()->sin_port));
+}
+
+TEST_CASE("inet_address name constructor", "[address]") {
+    inet_address addr(LOCALHOST_STR, PORT);
+
+    REQUIRE(addr.is_set());
+    REQUIRE(LOCALHOST_ADDR == addr.address());
+    REQUIRE(PORT == addr.port());
+
+    REQUIRE(uint8_t((LOCALHOST_ADDR >>  0) &0xFF) == addr[0]);
+    REQUIRE(uint8_t((LOCALHOST_ADDR >>  8) &0xFF) == addr[1]);
+    REQUIRE(uint8_t((LOCALHOST_ADDR >> 16) &0xFF) == addr[2]);
+    REQUIRE(uint8_t((LOCALHOST_ADDR >> 24) &0xFF) == addr[3]);
+
+    // Check the low-level struct
+    REQUIRE(AF_INET == addr.sockaddr_in_ptr()->sin_family);
+    REQUIRE(LOCALHOST_ADDR == ntohl(addr.sockaddr_in_ptr()->sin_addr.s_addr));
+    REQUIRE(PORT == ntohs(addr.sockaddr_in_ptr()->sin_port));
+}
+
+TEST_CASE("IPv4 resolve_address", "[address]") {
+	REQUIRE(inet_address::resolve_name("127.0.0.1") == htonl(LOCALHOST_ADDR));
+	REQUIRE(inet_address::resolve_name(LOCALHOST_STR) == htonl(LOCALHOST_ADDR));
+}
diff --git b/tests/unit/test_socket.cpp a/tests/unit/test_socket.cpp
new file mode 100644
index 0000000..8669e89
--- /dev/null
+++ a/tests/unit/test_socket.cpp
@@ -0,0 +1,332 @@
+// test_socket.cpp
+//
+// Unit tests for the base `socket` class.
+//
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+//
+
+#include "sockpp/socket.h"
+#include "sockpp/inet_address.h"
+#include "catch2/catch.hpp"
+#include <string>
+
+using namespace sockpp;
+using namespace std::chrono;
+
+/////////////////////////////////////////////////////////////////////////////
+// Aux functions
+
+TEST_CASE("test to_timeval", "aux") {
+	SECTION("concrete function") {
+		timeval tv = to_timeval(microseconds(500));
+		REQUIRE(tv.tv_sec == 0);
+		REQUIRE(tv.tv_usec == 500);
+
+		tv = to_timeval(microseconds(2500000));
+		REQUIRE(tv.tv_sec == 2);
+		REQUIRE(tv.tv_usec == 500000);
+	}
+
+	SECTION("template") {
+		timeval tv = to_timeval(milliseconds(1));
+		REQUIRE(tv.tv_sec == 0);
+		REQUIRE(tv.tv_usec == 1000);
+
+		tv = to_timeval(milliseconds(2500));
+		REQUIRE(tv.tv_sec == 2);
+		REQUIRE(tv.tv_usec == 500000);
+
+		tv = to_timeval(seconds(5));
+		REQUIRE(tv.tv_sec == 5);
+		REQUIRE(tv.tv_usec == 0);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// socket class
+
+constexpr in_port_t INET_TEST_PORT = 12346;
+
+TEST_CASE("socket constructors", "[socket]") {
+	SECTION("default constructor") {
+		sockpp::socket sock;
+
+		REQUIRE(!sock);
+		REQUIRE(!sock.is_open());
+		REQUIRE(sock.handle() == INVALID_SOCKET);
+		REQUIRE(sock.last_error() == 0);
+	}
+
+	SECTION("handle constructor") {
+		constexpr auto HANDLE = socket_t(3);
+		sockpp::socket sock(HANDLE);
+
+		REQUIRE(sock);
+		REQUIRE(sock.is_open());
+		REQUIRE(sock.handle() == HANDLE);
+		REQUIRE(sock.last_error() == 0);
+	}
+
+
+	SECTION("move constructor") {
+		constexpr auto HANDLE = socket_t(3);
+		sockpp::socket org_sock(HANDLE);
+
+		sockpp::socket sock(std::move(org_sock));
+
+		// Make sure the new socket got the handle
+		REQUIRE(sock);
+		REQUIRE(sock.handle() == HANDLE);
+		REQUIRE(sock.last_error() == 0);
+
+		// Make sure the handle was moved out of the org_sock
+		REQUIRE(!org_sock);
+		REQUIRE(org_sock.handle() == INVALID_SOCKET);
+	}
+}
+
+// Test the socket error behavior
+TEST_CASE("socket errors", "[socket]") {
+	SECTION("basic errors") {
+		sockpp::socket sock;
+
+		// Operations on an unopened socket should give an error
+		int reuse = 1;
+		socklen_t len = sizeof(int);
+		bool ok = sock.get_option(SOL_SOCKET, SO_REUSEADDR, &reuse, &len);
+
+		// Socket should be in error state
+		REQUIRE(!ok);
+		REQUIRE(!sock);
+
+		int err = sock.last_error();
+		REQUIRE(err != 0);
+
+		// last_error() is sticky, unlike `errno`
+		REQUIRE(sock.last_error() == err);
+
+		// We can clear the error
+		sock.clear();
+		REQUIRE(sock.last_error() == 0);
+
+		// Test arbitrary clear value
+		sock.clear(42);
+		REQUIRE(sock.last_error() == 42);
+		REQUIRE(!sock);
+	}
+
+	SECTION("clear error") {
+		auto sock = sockpp::socket::create(AF_INET, SOCK_STREAM);
+		REQUIRE(sock);
+
+		sock.clear(42);
+		REQUIRE(!sock);
+
+		sock.clear();
+		REQUIRE(sock);
+	}
+}
+
+TEST_CASE("socket handles", "[socket]") {
+
+	constexpr auto HANDLE = socket_t(3);
+
+	SECTION("test release") {
+		sockpp::socket sock(HANDLE);
+
+		REQUIRE(sock.handle() == HANDLE);
+		REQUIRE(sock.release() == HANDLE);
+
+		// Make sure the handle was moved out of the sock
+		REQUIRE(!sock);
+		REQUIRE(sock.handle() == INVALID_SOCKET);
+	}
+
+	SECTION("test reset") {
+		sockpp::socket sock(HANDLE);
+		REQUIRE(sock.handle() == HANDLE);
+
+		sock.reset();	// Default reset acts like release w/o return
+
+		// Make sure the handle was moved out of the sock
+		REQUIRE(!sock);
+		REQUIRE(sock.handle() == INVALID_SOCKET);
+
+		// Now reset with a "valid" handle
+		sock.reset(HANDLE);
+		REQUIRE(sock);
+		REQUIRE(sock.handle() == HANDLE);
+	}
+}
+
+TEST_CASE("socket family", "[socket]") {
+	SECTION("uninitialized socket") {
+		// Uninitialized socket should have unspecified family
+		sockpp::socket sock;
+		REQUIRE(sock.family() == AF_UNSPEC);
+	}
+
+	SECTION("unbound socket") {
+		// Unbound socket should have creation family
+		auto sock = socket::create(AF_INET, SOCK_STREAM);
+
+		// Windows and *nix behave differently
+		#if defined(_WIN32)
+			REQUIRE(sock.family() == AF_UNSPEC);
+		#else
+			REQUIRE(sock.family() == AF_INET);
+		#endif
+	}
+
+	SECTION("bound socket") {
+		// Bound socket should have same family as
+		// address to which it's bound
+		auto sock = socket::create(AF_INET, SOCK_STREAM);
+		inet_address addr(INET_TEST_PORT);
+
+		int reuse = 1;
+		REQUIRE(sock.set_option(SOL_SOCKET, SO_REUSEADDR, reuse));
+		REQUIRE(sock.bind(addr));
+		REQUIRE(sock.family() == addr.family());
+	}
+}
+
+TEST_CASE("socket address", "[socket]") {
+	SECTION("uninitialized socket") {
+		// Uninitialized socket should have empty address
+		sockpp::socket sock;
+		REQUIRE(sock.address() == sock_address_any{});
+	}
+
+	// The address has the specified family but all zeros
+	SECTION("unbound socket") {
+		auto sock = socket::create(AF_INET, SOCK_STREAM);
+		auto addr = inet_address(sock.address());
+
+		// Windows and *nix behave differently for family
+		#if defined(_WIN32)
+			REQUIRE(sock.family() == AF_UNSPEC);
+		#else
+			REQUIRE(sock.family() == AF_INET);
+		#endif
+
+		REQUIRE(addr.address() == 0);
+		REQUIRE(addr.port() == 0);
+	}
+
+	SECTION("bound socket") {
+		// Bound socket should have same family as
+		// address to which it's bound
+		auto sock = socket::create(AF_INET, SOCK_STREAM);
+		const inet_address ADDR(INET_TEST_PORT);
+
+		int reuse = 1;
+		REQUIRE(sock.set_option(SOL_SOCKET, SO_REUSEADDR, reuse));
+
+		REQUIRE(sock.bind(ADDR));
+		REQUIRE(sock.address() == ADDR);
+	}
+}
+
+// Socket pair shouldn't work for TCP sockets on any known platform.
+// So this should fail, but fail gracefully and retain the error
+// in both sockets.
+TEST_CASE("failed socket pair", "[socket]") {
+	sockpp::socket sock1, sock2;
+	std::tie(sock1, sock2) = std::move(socket::pair(AF_INET, SOCK_STREAM));
+
+	REQUIRE(!sock1);
+	REQUIRE(!sock2);
+
+	REQUIRE(sock1.last_error() != 0);
+	REQUIRE(sock1.last_error() == sock2.last_error());
+}
+
+// --------------------------------------------------------------------------
+
+// Test that the "last error" call to a socket gives the proper result
+// for the current thread.
+// Here we share a socket across two threads, force an error in one
+// thread, and then check to make sure that the error did not propagate
+// to the other thread.
+//
+#if 0
+TEST_CASE("thread-safe last error", "[socket]") {
+	sockpp::socket sock;
+
+	int state = 0;
+	std::mutex m;
+	std::condition_variable cv;
+
+	std::thread thr([&] {
+		// Test #1
+		REQUIRE(sock.last_error() == 0);
+		{
+			// Wait for Test #2
+			std::unique_lock<std::mutex> lk(m);
+			state = 1;
+			cv.notify_one();
+			cv.wait(lk, [&state]{return state >= 2;});
+		}
+
+		// Test #3
+		REQUIRE(sock.last_error() == 0);
+	});
+
+	{
+		// Wait for Test #1
+		std::unique_lock<std::mutex> lk(m);
+		cv.wait(lk, [&state]{return state >= 1;});
+	}
+
+	// Test #2
+	// Setting options on an un-opened socket should generate an error
+	int reuse = 1;
+	socklen_t len = sizeof(int);
+	bool ok = sock.get_option(SOL_SOCKET, SO_REUSEADDR, &reuse, &len);
+
+	REQUIRE(!ok);
+	REQUIRE(sock.last_error() != 0);
+
+	{
+		std::unique_lock<std::mutex> lk(m);
+		state = 2;
+		cv.notify_one();
+	}
+	thr.join();
+}
+#endif
+
diff --git b/tests/unit/test_stream_socket.cpp a/tests/unit/test_stream_socket.cpp
new file mode 100644
index 0000000..6c59e12
--- /dev/null
+++ a/tests/unit/test_stream_socket.cpp
@@ -0,0 +1,100 @@
+// test_stream_socket.cpp
+//
+// Unit tests for the `stream_socket` class(es).
+//
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+//
+
+#include "sockpp/stream_socket.h"
+#include "sockpp/inet_address.h"
+#include "catch2/catch.hpp"
+#include <string>
+
+using namespace sockpp;
+
+TEST_CASE("stream_socket default constructor", "[stream_socket]") {
+	stream_socket sock;
+	REQUIRE(!sock);
+	REQUIRE(!sock.is_open());
+}
+
+TEST_CASE("stream_socket handle constructor", "[stream_socket]") {
+	constexpr auto HANDLE = socket_t(3);
+
+	SECTION("valid handle") {
+		stream_socket sock(HANDLE);
+		REQUIRE(sock);
+		REQUIRE(sock.is_open());
+	}
+
+	SECTION("invalid handle") {
+		stream_socket sock(INVALID_SOCKET);
+		REQUIRE(!sock);
+		REQUIRE(!sock.is_open());
+		// TODO: Should this set an error?
+		REQUIRE(sock.last_error() == 0);
+	}
+}
+
+#if 0
+TEST_CASE("stream_socket address constructor", "[stream_socket]") {
+	SECTION("valid address") {
+		const auto ADDR = inet_address("localhost", 12345);
+
+		stream_socket sock(ADDR);
+		REQUIRE(sock);
+		REQUIRE(sock.is_open());
+		REQUIRE(sock.last_error() == 0);
+		REQUIRE(sock.address() == ADDR);
+	}
+
+	SECTION("invalid address") {
+		const auto ADDR = sock_address_any();
+
+		stream_socket sock(ADDR);
+		REQUIRE(!sock);
+		REQUIRE(!sock.is_open());
+		REQUIRE(sock.last_error() == EAFNOSUPPORT);
+	}
+}
+#endif
+
+// --------------------------------------------------------------------------
+// Connected tests
+
+TEST_CASE("stream_socket readn, writen", "[stream_socket]") {
+	//auto lsock = stream_socket::create(AF
+}
diff --git b/tests/unit/test_tcp_socket.cpp a/tests/unit/test_tcp_socket.cpp
new file mode 100644
index 0000000..8aa26d4
--- /dev/null
+++ a/tests/unit/test_tcp_socket.cpp
@@ -0,0 +1,154 @@
+// test_tcp_socket.cpp
+//
+// Unit tests for the `tcp_socket` class(es).
+//
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+//
+
+#include "sockpp/tcp_socket.h"
+#include "sockpp/tcp_connector.h"
+#include "sockpp/tcp_acceptor.h"
+#include "catch2/catch.hpp"
+#include <string>
+
+using namespace sockpp;
+
+static const in_port_t TEST_PORT = 12345;
+
+TEST_CASE("tcp_socket default constructor", "[tcp_socket]") {
+	tcp_socket sock;
+	REQUIRE(!sock);
+	REQUIRE(!sock.is_open());
+
+	//REQUIRE(sock.family() == AF_INET);
+}
+
+TEST_CASE("tcp_socket handle constructor", "[tcp_socket]") {
+	constexpr auto HANDLE = socket_t(3);
+
+	SECTION("valid handle") {
+		tcp_socket sock(HANDLE);
+		REQUIRE(sock);
+		REQUIRE(sock.is_open());
+
+		//REQUIRE(sock.family() == AF_INET);
+	}
+
+	SECTION("invalid handle") {
+		tcp_socket sock(INVALID_SOCKET);
+		REQUIRE(!sock);
+		REQUIRE(!sock.is_open());
+		// TODO: Should this set an error?
+		REQUIRE(sock.last_error() == 0);
+
+		//REQUIRE(sock.family() == AF_INET);
+	}
+}
+
+// --------------------------------------------------------------------------
+// Connected tests
+
+TEST_CASE("tcp_socket read/write", "[stream_socket]") {
+	const std::string STR { "This is a test. This is only a test." };
+	const size_t N = STR.length();
+
+	inet_address addr { "localhost", TEST_PORT };
+	tcp_acceptor asock{ addr };
+
+	tcp_connector csock;
+	csock.set_non_blocking();
+
+	REQUIRE(csock.connect(addr));
+
+	auto ssock = asock.accept();
+	REQUIRE(ssock);
+
+	SECTION("read_n/write_n") {
+		char buf[512];  // N
+
+		REQUIRE(csock.write_n(STR.data(), N) == N);
+		REQUIRE(ssock.read_n(buf, N) == N);
+
+		std::string str { buf, buf+N };
+		REQUIRE(str == STR);
+
+		char buf2[512]; // N
+
+		// string write is a write_n()
+		REQUIRE(csock.write(STR) == N);
+		REQUIRE(ssock.read_n(buf2, N) == N);
+
+		std::string str2 { buf2, buf2+N };
+		REQUIRE(str2 == STR);
+	}
+
+	SECTION("scatter/gather") {
+		const std::string HEADER { "<start>" },
+						  FOOTER { "<end>" };
+
+		const size_t N_HEADER = HEADER.length(),
+					 N_FOOTER = FOOTER.length(),
+					 N_TOT = N_HEADER + N + N_FOOTER;
+
+		std::vector<iovec> outv {
+			iovec { (void*) HEADER.data(), N_HEADER },
+			iovec { (void*) STR.data(), N },
+			iovec { (void*) FOOTER.data(), N_FOOTER }
+		};
+
+		char hbuf[512], 	// N_HEADER
+			 buf[512],  	// N
+			 fbuf[512]; 	// N_FOOTER
+
+		std::vector<iovec> inv {
+			iovec { (void*) hbuf, N_HEADER },
+			iovec { (void*) buf, N },
+			iovec { (void*) fbuf, N_FOOTER }
+		};
+
+		REQUIRE(csock.write(outv) == N_TOT);
+		REQUIRE(csock.write(outv) == N_TOT);
+		REQUIRE(csock.write(outv) == N_TOT);
+
+		REQUIRE(ssock.read(inv) == N_TOT);
+
+		REQUIRE(std::string(hbuf, N_HEADER) == HEADER);
+		REQUIRE(std::string(buf, N) == STR);
+		REQUIRE(std::string(fbuf, N_FOOTER) == FOOTER);
+	}
+}
+
+
diff --git b/tests/unit/test_unix_address.cpp a/tests/unit/test_unix_address.cpp
new file mode 100644
index 0000000..11ca6b5
--- /dev/null
+++ a/tests/unit/test_unix_address.cpp
@@ -0,0 +1,124 @@
+// test_unix_address.cpp
+//
+// Unit tests for the `unix_address` class.
+//
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2018 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+//
+
+#include "sockpp/unix_address.h"
+#include "catch2/catch.hpp"
+#include <string>
+
+using namespace sockpp;
+
+std::string PATH { "/tmp/sock" };
+
+TEST_CASE("unix_address default constructor", "[address]") {
+    unix_address addr;
+
+    REQUIRE(!addr.is_set());
+    REQUIRE(addr.path().empty());
+    REQUIRE(sizeof(sockaddr_un) == addr.size());
+
+    // TODO: Do we have a unix_address::create() method yet?
+}
+
+TEST_CASE("unix_address path constructor", "[address]") {
+    unix_address addr(PATH);
+
+    REQUIRE(addr.is_set());
+    REQUIRE(PATH == addr.path());
+    REQUIRE(sizeof(sockaddr_un) == addr.size());
+
+    // Check the low-level struct
+    REQUIRE(AF_UNIX == addr.sockaddr_un_ptr()->sun_family);
+    REQUIRE(0 == strcmp(PATH.c_str(),
+                        (const char*) &addr.sockaddr_un_ptr()->sun_path));
+
+    SECTION("copy constructor") {
+        unix_address addr2(addr);
+
+        REQUIRE(addr2.is_set());
+        REQUIRE(PATH == addr2.path());
+        REQUIRE(sizeof(sockaddr_un) == addr2.size());
+
+        // Check the low-level struct
+        REQUIRE(AF_UNIX == addr2.sockaddr_un_ptr()->sun_family);
+        REQUIRE(0 == strcmp(PATH.c_str(),
+                            (const char*) &addr2.sockaddr_un_ptr()->sun_path));
+    }
+
+    SECTION("sockaddr conversions") {
+        auto sa = addr.sockaddr_ptr();
+        unix_address addr2(*sa);
+
+        REQUIRE(addr2.is_set());
+        REQUIRE(PATH == addr2.path());
+        REQUIRE(sizeof(sockaddr_un) == addr2.size());
+
+        // Check the low-level struct
+        REQUIRE(AF_UNIX == addr2.sockaddr_un_ptr()->sun_family);
+        REQUIRE(0 == strcmp(PATH.c_str(),
+                            (const char*) &(addr2.sockaddr_un_ptr()->sun_path)));
+    }
+}
+
+TEST_CASE("unix_address sockaddr_un constructor", "[address]") {
+    sockaddr_un unaddr;
+    unaddr.sun_family = AF_UNIX;
+    strcpy(unaddr.sun_path, PATH.c_str());
+
+    unix_address addr(unaddr);
+
+    REQUIRE(addr.is_set());
+    REQUIRE(PATH == addr.path());
+    REQUIRE(sizeof(sockaddr_un) == addr.size());
+
+    // Check the low-level struct
+    REQUIRE(AF_UNIX == addr.sockaddr_un_ptr()->sun_family);
+    REQUIRE(0 == strcmp(PATH.c_str(),
+                        (const char*) &addr.sockaddr_un_ptr()->sun_path));
+
+	// TODO: Restore this when all address checks in place
+	/*
+    SECTION("reject bad sockaddr_un") {
+        unaddr.sun_family = AF_INET;
+        REQUIRE_THROWS_AS([&] {
+            unix_address addr2(unaddr);
+        }(), std::invalid_argument);
+    }
+	*/
+}
diff --git b/tests/unit/test_unix_dgram_socket.cpp a/tests/unit/test_unix_dgram_socket.cpp
new file mode 100644
index 0000000..95e97d0
--- /dev/null
+++ a/tests/unit/test_unix_dgram_socket.cpp
@@ -0,0 +1,71 @@
+// test_unix_dgram_socket.cpp
+//
+// Unit tests for the `unix_dgram_socket` class.
+//
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+//
+
+#include "sockpp/unix_dgram_socket.h"
+#include "catch2/catch.hpp"
+#include <string>
+
+using namespace sockpp;
+
+// Test that we can create a Unix-domain datagram socket pair and send data
+// from one of the sockets to the other.
+TEST_CASE("unix dgram socket pair", "[unix_dgram_socket]") {
+	unix_dgram_socket sock1, sock2;
+	std::tie(sock1, sock2) = std::move(unix_dgram_socket::pair());
+
+	REQUIRE(sock1);
+	REQUIRE(sock2);
+
+	REQUIRE(sock1.is_open());
+	REQUIRE(sock2.is_open());
+
+	const std::string MSG { "Hello there!" };
+	const size_t N = MSG.length();
+
+	char buf[512];
+
+	REQUIRE(sock1.send(MSG) == N);
+	REQUIRE(sock2.recv(buf, N) == N);
+
+	std::string msg { buf, buf+N };
+	REQUIRE(msg == MSG);
+}
+
+
diff --git b/tests/unit/test_unix_stream_socket.cpp a/tests/unit/test_unix_stream_socket.cpp
new file mode 100644
index 0000000..77b5960
--- /dev/null
+++ a/tests/unit/test_unix_stream_socket.cpp
@@ -0,0 +1,71 @@
+// test_unix_stream_socket.cpp
+//
+// Unit tests for the `unix_stream_socket` class.
+//
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2019 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+//
+
+#include "sockpp/unix_stream_socket.h"
+#include "catch2/catch.hpp"
+#include <string>
+
+using namespace sockpp;
+
+// Test that we can create a Unix-domain stream socket pair and send
+// data from one of the sockets to the other.
+TEST_CASE("unix stream socket pair", "[unix_stream_socket]") {
+	unix_stream_socket sock1, sock2;
+	std::tie(sock1, sock2) = std::move(unix_stream_socket::pair());
+
+	REQUIRE(sock1);
+	REQUIRE(sock2);
+
+	REQUIRE(sock1.is_open());
+	REQUIRE(sock2.is_open());
+
+	const std::string MSG { "Hello there!" };
+	const size_t N = MSG.length();
+
+	char buf[512];
+
+	REQUIRE(sock1.write(MSG) == N);
+	REQUIRE(sock2.read_n(buf, N) == N);
+
+	std::string msg { buf, buf+N };
+	REQUIRE(msg == MSG);
+}
+
+
diff --git b/tests/unit/unit_tests.cpp a/tests/unit/unit_tests.cpp
new file mode 100644
index 0000000..020a01d
--- /dev/null
+++ a/tests/unit/unit_tests.cpp
@@ -0,0 +1,71 @@
+// unit_tests.cpp
+//
+// Main for unit tests.
+//
+
+// --------------------------------------------------------------------------
+// This file is part of the "sockpp" C++ socket library.
+//
+// Copyright (c) 2018 Frank Pagliughi
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from this
+// software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// --------------------------------------------------------------------------
+//
+
+// Normally, we would just tell Catch2 to define main() like this...
+// This tells Catch to provide a main() - only do this in one cpp file
+//#define CATCH_CONFIG_MAIN
+
+// ...but we need to run the sockpp global initialization before running 
+// any of the tests. Defining a main() is described here:
+// https://github.com/catchorg/Catch2/blob/master/docs/own-main.md 
+//  
+
+#include "sockpp/socket.h"
+
+// This seems to be required, at least for MSVS 2015 on Win7, 
+// using Catch2 v2.9.2
+#if defined(_WIN32)
+    #define CATCH_CONFIG_DISABLE_EXCEPTIONS
+#endif
+
+#define CATCH_CONFIG_RUNNER
+#include "catch2/catch.hpp"
+
+int main(int argc, char* argv[]) 
+{
+    // global setup...
+	sockpp::socket_initializer sockInit;
+
+    int result = Catch::Session().run(argc, argv);
+
+    // global clean-up...
+
+    return result;
+}