-
Notifications
You must be signed in to change notification settings - Fork 4
Getting Started
As a submodule:
git submodule add https://github.com/irrld/znet.git external/znet
git submodule update --init --recursiveThen, in your CMakeLists.txt, using the bundled zstd:
add_subdirectory(external/znet/vendor/zstd/build/cmake ${CMAKE_CURRENT_BINARY_DIR}/zstd)
add_subdirectory(external/znet/znet ${CMAKE_CURRENT_BINARY_DIR}/znet)
target_link_libraries(your_target PRIVATE znet)Or with a system zstd from vcpkg, brew or your package manager:
set(ZNET_USE_EXTERNAL_ZSTD ON)
add_subdirectory(external/znet/znet)
target_link_libraries(your_target PRIVATE znet)| Option | Default | Effect |
|---|---|---|
ZNET_CXX_STANDARD |
20 |
14, 17, 20 or 23
|
ZNET_USE_EXTERNAL_ZSTD |
OFF |
Use a zstd you provide instead of the bundled one |
ZNET_ENABLE_METRICS |
ON |
OFF compiles the counters out entirely |
ZNET_ENABLE_LTO |
ON |
Link-time optimization for release builds, silently skipped where unsupported |
Three things happen for every connection: give the session a codec so it can read and write your packets, give it a handler so something receives them, and send.
enum : PacketId { kChatMessage = 1 };
class ChatMessage : public Packet {
public:
ChatMessage() : Packet(kChatMessage) {}
std::string text;
};
class ChatSerializer : public PacketSerializer<ChatMessage> {
public:
std::shared_ptr<Buffer> SerializeTyped(std::shared_ptr<ChatMessage> packet,
std::shared_ptr<Buffer> buffer) override {
buffer->WriteString(packet->text);
return buffer;
}
std::shared_ptr<ChatMessage> DeserializeTyped(std::shared_ptr<Buffer> buffer) override {
auto packet = std::make_shared<ChatMessage>();
packet->text = buffer->ReadString();
return packet;
}
};
class ChatHandler : public PacketHandler<ChatHandler, ChatMessage> {
public:
explicit ChatHandler(std::shared_ptr<PeerSession> session)
: session_(std::move(session)) {}
void OnPacket(std::shared_ptr<ChatMessage> packet) {
auto reply = std::make_shared<ChatMessage>();
reply->text = "echo: " + packet->text;
session_->SendPacket(reply);
}
private:
std::shared_ptr<PeerSession> session_;
};
// one codec for every session: serializers are stateless and shared
std::shared_ptr<Codec> g_codec;
bool OnClientConnected(IncomingClientConnectedEvent& event) {
event.session()->SetCodec(g_codec);
event.session()->SetHandler(std::make_shared<ChatHandler>(event.session()));
return false; // false lets other handlers see the event too
}
void OnEvent(Event& event) {
EventDispatcher dispatcher{event};
dispatcher.Dispatch<IncomingClientConnectedEvent>(
ZNET_BIND_GLOBAL_FN(OnClientConnected));
}
int RunServer() {
g_codec = std::make_shared<Codec>();
g_codec->Add(kChatMessage, std::make_unique<ChatSerializer>());
ServerConfig config{"0.0.0.0", 25000};
Server server{config};
server.SetEventCallback(ZNET_BIND_GLOBAL_FN(OnEvent));
if (server.Bind() != Result::Success) {
return 1;
}
server.Listen(); // returns immediately; the server runs on its own thread
server.Wait(); // blocks until it stops
return 0;
}Listen() returns as soon as the listener is up. Wait() is what blocks, so a
program that has other work to do simply does not call it.
Same shape. The difference is that a client has one session, handed to you when the connection completes rather than on accept.
bool OnConnected(ClientConnectedToServerEvent& event) {
auto codec = std::make_shared<Codec>();
codec->Add(kChatMessage, std::make_unique<ChatSerializer>());
event.session()->SetCodec(codec);
event.session()->SetHandler(std::make_shared<ClientHandler>());
auto hello = std::make_shared<ChatMessage>();
hello->text = "hello";
event.session()->SendPacket(hello);
return false;
}
void OnEvent(Event& event) {
EventDispatcher dispatcher{event};
dispatcher.Dispatch<ClientConnectedToServerEvent>(ZNET_BIND_GLOBAL_FN(OnConnected));
}
int RunClient() {
ClientConfig config{"127.0.0.1", 25000, std::chrono::seconds(10)};
Client client{config};
client.SetEventCallback(ZNET_BIND_GLOBAL_FN(OnEvent));
if (client.Bind() != Result::Success) {
return 1;
}
client.Connect(); // returns immediately
client.Wait();
return 0;
}The third ClientConfig field is the connection timeout. Zero disables it,
which means a client dialing an address that never answers waits forever.
One field, and nothing above it changes:
ClientConfig config{"127.0.0.1", 25000, std::chrono::seconds(10),
ConnectionType::ZDT};The server needs the same, and both ends must agree. What that buys, and what it costs, is in Choosing a Transport.
- Messages of your own: Packets and Serialization
- What else you can respond to: Events
- Before you call znet from your own threads: Threading Model
Full programs live in the examples
folder: basic for TCP, zdt for reliable UDP, multiversion for negotiating
packet versions between builds, and p2p for hole punching.