Modern, header-only, compact and cross-platform C++ network/sockets library. Don't mind the crappy name, I suck at naming things.
I needed a small, portable, and easy to use networking library. For years, I just used the POSIX socket api directly, since it's easy and portable (some small platform differences notwithstanding). Since I kept reinventing the wheel, I decided to write this library after trying ASIO, which I found way to heavy for my tastes.
You can just add this repo as a git submodule, which at this point is probably (IMO) the best way to handle C++ dependencies.
git submodule add https://github.com/lpcvoid/cpp-net-lib.git extern
This will check out the lib as a submodule within your project. Now just #include "extern/cpp-net-lib/src/netlib.hpp"
somewhere.
Alternatively, you can run the examples and tests like you would any other CMake based project:
cmake -B build
cmake --build build
There are some cmake flags you can use:
Option | Default | Description |
---|---|---|
BUILD_TESTS | ON | Builds tests using doctest , which is then introduced as a dependency. |
BUILD_EXAMPLES | ON | Builds some small example programs. |
WITH_HTTP | ON | Builds library with HTTP support. My uri library is then a dependency for parsing URLs. |
The library consists of only a few user facing classes:
netlib::client
is a network client implementation that offers async and blocking capabilities. Async is done efficiently via a threadpool.
netlib::server
is a network server which can handle client requests in an easy manner via callbacks. It uses a threadpool internally.
See the examples for some usecases, it's really self explaining.
netlib::endpoint_accessor
is used to retrieve information like IP and port from an incoming client connection. See examples.
netlib::thread_pool
is the threadpool implementation used throughout this lib. You can use it for other stuff too,
if you like.
netlib::socket
is a platform independent socket wrapper over the POSIX socket api.
netlib::server_response
is a struct that you can return in your server callbacks, which instructs the server how to handle your
response. You can pass it some data to relay to clients, or instruct server to terminate the connection (after sending data, if any).
netlib::http::client
is an HTTP client, which can currently only GET http responses (No other Verbs, and no TLS).
In all of these examples, I assume you have using namespace std::chrono_literals
somewhere. If you don't want to "pollute" the std
namespace, you need to
instead use something like std::chrono::milliseconds(n)
.
netlib::client client;
std::error_condition connect_result =
client.connect("example.com", // can also be an ip
"http", // can also be a uint16_t port
netlib::AddressFamily::IPv4, // use IPv4
netlib::AddressProtocol::TCP, // use TCP since we are interested in http
1000ms); // timeout
if (connect_result) {
std::cerr << "Connection failed! Error: " << connect_result.message() << std::endl;
return;
}
static const std::string basic_get = R"(GET / HTTP/1.1\r\nHost: example.com\r\n\r\n)";
std::pair<std::size_t, std::error_condition> send_res =
client.send({basic_get.begin(), basic_get.end()}, 1000ms);
if (send_res.second) {
std::cerr << "Sending failed! Error: " << send_res.second.message() << std::endl;
return;
}
std::pair<std::vector<uint8_t>, std::error_condition> recv_res = client.recv(2048, 3000ms);
if (recv_res.second) {
std::cerr << "Recv failed! Error: " << recv_res.second.message() << std::endl;
return;
}
std::string website(recv_res.first.begin(), recv_res.first.end());
std::cout << website << std::endl;
netlib::client client;
auto connect_future = client.connect_async("example.com",
static_cast<uint16_t>(80),
netlib::AddressFamily::IPv4,
netlib::AddressProtocol::TCP,
10000ms);
while (connect_future.wait_for(10ms) != std::future_status::ready) {
// do something cool while waiting for connect to finish
}
std::error_condition connect_error = connect_future.get();
if (connect_error) {
std::cerr << "Connection failed, error: " << connect_error.message() << std::endl;
return;
}
static const std::string basic_get = R"(GET / HTTP/1.1\r\nHost: example.com\r\n\r\n)";
auto send_future = client.send_async({basic_get.begin(), basic_get.end()},1000ms);
while (send_future.wait_for(10ms) != std::future_status::ready) {
// do something cool while waiting for send to finish
}
std::pair<std::size_t, std::error_condition> send_result = send_future.get();
if (send_result.second) {
std::cerr << "Send error, error: " << send_result.second.message() << std::endl;
return;
}
std::cout << "We sent " << send_result.first << " bytes!" << std::endl;
auto recv_future = client.recv_async(2048, 3000ms);
while (recv_future.wait_for(10ms) != std::future_status::ready) {
// do something cool while waiting for data to come in
}
auto recv_result = recv_future.get();
std::cout << "We got " << recv_result.first.size() << " bytes!" << std::endl;
std::string website(recv_result.first.begin(), recv_result.first.end());
std::cout << website << std::endl;
This code sample creates a TCP server on localhost:1337, which will echo whatever you send to it. Each of the callbacks will be executed by a random thread internally.
You may want to disconnect each client after they get their echo - this can be done by setting .terminate
to true
in
the server_response
struct you return from within your callback. Leaving the answer
vector empty will not send any data.
netlib::server server;
server.register_callback_on_connect(
[&](netlib::client_endpoint endpoint) -> netlib::server_response {
std::string ip = netlib::endpoint_accessor::ip_to_string(endpoint.addr, endpoint.addr_len).value();
std::cout << "Client connected! IP: " << ip << std::endl;
return {};
});
server.register_callback_on_recv(
[&](netlib::client_endpoint endpoint,
const std::vector<uint8_t> &data) -> netlib::server_response{
std::cout << "Client sent some data, echoing it back!" << std::endl;
return {.answer = data};
});
std::error_condition server_create_res = server.create("localhost",
static_cast<uint16_t>(1337),
netlib::AddressFamily::IPv4,
netlib::AddressProtocol::TCP);
if (server_create_res) {
std::cerr << "Error initializing server: " << server_create_res.message() << std::endl;
}
netlib::http::http_client client;
auto res = client.get("http://example.com");
if (res.second) {
std::cerr << "Error: " << res.second.message() << std::endl;
exit(1);
}
std::cout << "Got HTTP response: " << res.first->response_code <<
", version " << res.first->version.first << "." << res.first->version.second << std::endl;
std::cout << "Header entries:" << std::endl;
std::for_each(res.first->headers.begin(), res.first->headers.end(), [](auto header_entry) {
std::cout << header_entry.first << " = " << header_entry.second << std::endl;
});
std::cout << "Body:" << std::endl;
std::cout << res.first.value().body << std::endl;