Skip to content

Commit 333ff8c

Browse files
author
Ray
committed
Updated readme for v1.0.0
1 parent 834bfd4 commit 333ff8c

File tree

3 files changed

+109
-57
lines changed

3 files changed

+109
-57
lines changed

README.md

+106-47
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,149 @@
11
<div align="center">
22
<img src="https://pulsenet.dev/images/pulse-networking-social.png" alt="Pulse Networking" width="1200">
3-
<h1>pulseudp</h1>
4-
<p><strong>Raw non-blocking UDP sockets with Go-style ergonomics.</strong></p>
3+
<h1>pulse::net::udp</h1>
4+
<p><strong>Raw non-blocking UDP sockets with Go-style ergonomics, in modern C++23.</strong></p>
55
<p>
66
<a href="#features">Features</a> •
77
<a href="#why">Why?</a> •
88
<a href="#usage">Usage</a> •
9+
<a href="#build-requirements">Build Requirements</a> •
10+
<a href="#fetchcontent">FetchContent</a> •
911
<a href="#platform-support">Platform Support</a> •
1012
<a href="#license">License</a>
1113
</p>
1214
</div>
1315

1416
---
1517

16-
pulseudp is a cross-platform UDP socket wrapper for C++ that gives you a clean, Go-style interface — no blocking nonsense, no bloated event loops, and no libuv circus.
18+
`pulse::net::udp` is a minimal, modern, cross-platform UDP socket layer written in pure C++23 — with sane error handling (`std::expected`), zero dependencies, and no framework bloat.
1719

18-
Use it as the foundation for anything low-latency and real-time: games, protocols, transport layers, etc.
20+
It does one thing well: **non-blocking UDP** across Unix and Windows.
1921

20-
It’s designed to get out of your way and let the OS do its job.
22+
## 🚀 Features
2123

22-
## Features
23-
- ✅ Non-blocking UDP I/O (just like Go)
24-
- ✅ Listen() for servers
25-
- ✅ Dial() for clients
26-
- ✅ sendTo() and recvFrom() like you’d expect
27-
- ✅ Optional send() and recv() with connected semantics
28-
- ✅ IPv4 + IPv6 support
29-
- ✅ No third-party dependencies
30-
- ✅ Portable between Unix and Windows
31-
- ✅ Easy to test, drop-in for higher-level protocols
24+
- ✅ Modern C++23 (`std::expected`, no exceptions)
25+
- ✅ Non-blocking UDP sockets
26+
-`Listen()` and `Dial()` like Go
27+
-`send()` / `sendTo()` and `recvFrom()` with structured error handling
28+
- ✅ Zero dependencies
29+
- ✅ Cross-platform: Unix (Linux/macOS) and Windows (Winsock2)
30+
- ✅ Dead simple integration
3231

33-
## Why?
32+
## 🧠 Why?
3433

35-
Because C++ UDP sockets are a mess:
36-
- On Unix, you’re writing raw socket()/bind()/recvfrom() boilerplate.
37-
- On Windows, you’re dancing with Winsock and WSAStartup() just to send a packet.
38-
- Nobody agrees on non-blocking behavior or even a clean API.
34+
Because writing portable UDP in C++ is still a flaming trash heap:
35+
- POSIX and Winsock APIs barely resemble each other
36+
- Most libraries are bloated, legacy-bound, or layered abstractions on top of boost or libuv
37+
- Nobody should still be writing socket() / bind() / recvfrom() directly in 2025
3938

40-
Go got it right. So we copied it.
39+
This library fixes that with a clean, modern API that doesn’t try to reinvent networking — just makes it suck less.
4140

42-
This isn’t an abstraction layer built on top of a ten-ton framework. It’s just clean, minimal UDP done right.
43-
44-
## Usage
41+
## 🧑‍💻 Usage
4542

4643
```cpp
4744
#include <pulse/net/udp/udp.h>
4845
#include <iostream>
46+
#include <vector>
4947

5048
int main() {
51-
pulse::net::udp::Addr serverAddr("127.0.0.1", 9000);
52-
auto server = pulse::net::udp::Listen(serverAddr);
53-
auto client = pulse::net::udp::Dial(serverAddr);
49+
using namespace pulse::net::udp;
50+
51+
Addr serverAddr("127.0.0.1", 9000);
5452

55-
std::vector<uint8_t> msg = {'h', 'e', 'l', 'l', 'o'};
56-
client->send(msg);
53+
auto serverResult = Listen(serverAddr);
54+
if (!serverResult) {
55+
std::cerr << "Listen failed: " << static_cast<int>(serverResult.error()) << "\n";
56+
return 1;
57+
}
58+
auto& server = *serverResult;
5759

58-
std::optional<pulse::net::udp::Addr> from;
59-
std::vector<uint8_t> data;
60+
auto clientResult = Dial(serverAddr);
61+
if (!clientResult) {
62+
std::cerr << "Dial failed: " << static_cast<int>(clientResult.error()) << "\n";
63+
return 1;
64+
}
65+
auto& client = *clientResult;
6066

61-
if (auto maybe = server->recvFrom()) {
62-
std::tie(data, from) = *maybe;
63-
std::cout << "Got packet from " << from->ip << ":" << from->port << std::endl;
67+
std::vector<uint8_t> message = {'h', 'e', 'l', 'l', 'o'};
68+
if (auto res = client->send(message.data(), message.size()); !res) {
69+
std::cerr << "Send failed: " << static_cast<int>(res.error()) << "\n";
70+
return 1;
6471
}
6572

73+
auto recvResult = server->recvFrom();
74+
if (!recvResult) {
75+
std::cerr << "Receive failed: " << static_cast<int>(recvResult.error()) << "\n";
76+
return 1;
77+
}
78+
79+
const ReceivedPacket& packet = *recvResult;
80+
std::string msg(reinterpret_cast<const char*>(packet.data), packet.length);
81+
82+
std::cout << "Received: " << msg << " from " << packet.addr.ip << ":" << packet.addr.port << "\n";
6683
return 0;
6784
}
6885
```
6986

70-
## Platform Support
87+
## 🏗 Build Requirements
88+
89+
* **C++23**
90+
* **CMake ≥ 3.15**
91+
92+
If your compiler doesn’t support `std::expected`, upgrade. This is not a museum.
93+
94+
### ⚠️ Error Handling Philosophy
95+
96+
`pulse::net::udp` uses `std::expected` for all runtime operations. No exceptions are thrown during normal usage.
97+
98+
The **only exceptions** are constructors like `Addr(ip, port)`, where C++ gives no sane way to return an error. If construction fails due to invalid input (e.g. garbage IP address), you'll get a `std::invalid_argument`.
99+
100+
Everything else—`send()`, `recvFrom()`, `Dial()`, `Listen()`—uses `std::expected<T, ErrorCode>` so you can handle failures explicitly, without try/catch nonsense.
101+
102+
## 📦 FetchContent
103+
104+
You can pull in `pulse::net::udp` via `FetchContent` like this:
105+
106+
```cmake
107+
include(FetchContent)
108+
109+
FetchContent_Declare(
110+
pulse_udp
111+
GIT_REPOSITORY https://git.pulsenet.dev/pulse/udp
112+
GIT_TAG v1.0.0
113+
)
114+
115+
FetchContent_MakeAvailable(pulse_udp)
116+
117+
set(CMAKE_CXX_STANDARD 23)
118+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
119+
120+
target_link_libraries(your_target PRIVATE pulse::net::udp)
121+
```
122+
123+
The actual target is aliased to `pulse::net::udp`, even though the library name is `pulsenet_udp`.
124+
125+
## 🪟 Platform Support
126+
127+
| Platform | Supported? | Notes |
128+
| -------- | ---------- | ----------------------------- |
129+
| Linux || `fcntl()` for non-blocking |
130+
| macOS || Same as above |
131+
| Windows || Raw Winsock2 + `WSAStartup()` |
132+
133+
## ⚖️ License
71134

72-
| Platform | Supported? | Impl Notes |
73-
| --- | --- | --- |
74-
| macOS || fcntl() non-blocking sockets |
75-
| Linux || Same as above |
76-
| Windows || Raw Winsock2 w/ WSAStartup() |
135+
**AGPLv3**. If that offends you, congratulations — it’s working as intended.
77136

78-
You don’t need libuv, boost, or some “cross-platform networking abstraction” from hell. It’s all native. It’s all simple.
137+
Want to use this in a proprietary product? [Buy a commercial license](https://pulsenet.dev/) or go write your own UDP stack.
79138

80-
## License
139+
## 🧨 Final Word
81140

82-
AGPLv3. If you’re using this in a closed-source project, you need to license your project AGPLv3 as well or purchase a commercial license.
141+
This isn’t boost. This isn’t some academic networking playground.
83142

84-
## Final Word
143+
If you want a fast, lean UDP layer that doesn’t try to abstract away the world — and doesn’t get in your way when you're building serious low-latency systems — you're in the right place.
85144

86-
pulseudp is not a toy. It’s not trying to hold your hand. It gives you raw UDP, cleanly wrapped, so you can go build real-time systems without dragging 200 build dependencies behind you.
145+
If you need a coroutine DSL, TLS tunnels, and a metrics dashboard, leave now.
87146

88-
If you want a networking library with 300 contributors and an official Discord server, you’re in the wrong place.
147+
## Version
89148

90-
If you want simple, fast, understandable UDP for serious use — welcome.
149+
**pulse::net::udp v1.0.0**

include/pulse/net/udp/udp.h

+2-7
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,7 @@
77
#include <cstdint>
88
#include <optional>
99
#include <utility>
10-
11-
#if __cpp_lib_expected >= 202211L
12-
#include <expected>
13-
#else
14-
#error "std::expected is not available. Upgrade your compiler."
15-
#endif
10+
#include <expected>
1611

1712
namespace pulse::net::udp {
1813

@@ -46,4 +41,4 @@ class Socket {
4641
std::expected<std::unique_ptr<Socket>, ErrorCode> Listen(const Addr& bindAddr);
4742
std::expected<std::unique_ptr<Socket>, ErrorCode> Dial(const Addr& remoteAddr);
4843

49-
}
44+
}

src/udp_unix.cpp

+1-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ class SocketUnix : public Socket {
2222
inline std::unexpected<ErrorCode> mapSendErrno(int err) {
2323
switch (err) {
2424
case EWOULDBLOCK:
25-
case EAGAIN:
2625
return std::unexpected(ErrorCode::WouldBlock);
2726
case EBADF:
2827
case ENOTSOCK:
@@ -86,7 +85,6 @@ class SocketUnix : public Socket {
8685
if (received < 0) {
8786
switch (errno) {
8887
case EWOULDBLOCK:
89-
case EAGAIN:
9088
return std::unexpected(ErrorCode::WouldBlock);
9189

9290
case EBADF:
@@ -245,4 +243,4 @@ std::expected<std::unique_ptr<Socket>, ErrorCode> Dial(const Addr& remoteAddr) {
245243
return std::make_unique<SocketUnix>(sockfd);
246244
}
247245

248-
} // namespace pulse::net::udp
246+
} // namespace pulse::net::udp

0 commit comments

Comments
 (0)