summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Stoelp <johannes.stoelp@gmail.com>2025-01-17 00:31:13 +0100
committerJohannes Stoelp <johannes.stoelp@gmail.com>2025-01-17 00:55:34 +0100
commitf37617645ab220c2d80c1886b20eb2fb8a22bc5c (patch)
tree76fd5e15e6c30e7b54fc348d9ee381c412f49e2d
parent4f2a23efa8b144810b867c9c81587365e2c87d3f (diff)
downloadzig-playground-main.tar.gz
zig-playground-main.zip
net: small message ping-pong example with arg parseHEADmain
-rw-r--r--example-network/.gitignore2
-rw-r--r--example-network/Makefile3
-rw-r--r--example-network/pong.zig136
3 files changed, 141 insertions, 0 deletions
diff --git a/example-network/.gitignore b/example-network/.gitignore
new file mode 100644
index 0000000..5263b7f
--- /dev/null
+++ b/example-network/.gitignore
@@ -0,0 +1,2 @@
+pong
+pong.o* \ No newline at end of file
diff --git a/example-network/Makefile b/example-network/Makefile
index 8fc8a1c..7b51314 100644
--- a/example-network/Makefile
+++ b/example-network/Makefile
@@ -2,3 +2,6 @@ s:
zig run basic.zig -- -s
c:
zig run basic.zig
+
+pong: pong.zig
+ zig build-exe $^ -O ReleaseFast
diff --git a/example-network/pong.zig b/example-network/pong.zig
new file mode 100644
index 0000000..3ef861c
--- /dev/null
+++ b/example-network/pong.zig
@@ -0,0 +1,136 @@
+const std = @import("std");
+
+pub const std_options: std.Options = .{
+ .log_level = .info,
+};
+
+const default_opts = struct {
+ /// Whether to start the server or the client side.
+ is_server: bool,
+ /// IPv4 address to listen (server) on or connect (client) to.
+ addr: [:0]const u8,
+ /// Message size send between client and server.
+ msg_size: usize,
+}{ .is_server = true, .addr = "127.0.0.1", .msg_size = 4096 };
+
+/// Parse command line arguments.
+fn parse_args() !?@TypeOf(default_opts) {
+ var opts = default_opts;
+ var args = std.process.args();
+
+ // Skip program name.
+ _ = args.skip();
+
+ return while (args.next()) |arg| {
+ if (std.mem.eql(u8, arg, "-c")) {
+ opts.is_server = false;
+ } else if (std.mem.eql(u8, arg, "-a")) {
+ opts.addr = args.next() orelse break error.MissingAddressValue;
+ } else if (std.mem.eql(u8, arg, "-m")) {
+ const bytes = args.next() orelse break error.MissingMessageSizeBytesValue;
+ opts.msg_size = try std.fmt.parseInt(usize, bytes, 10);
+ } else if (std.mem.eql(u8, arg, "-h")) {
+ std.debug.print("Usage: pong [args]\n", .{});
+ std.debug.print(" -c start client, rather than server\n", .{});
+ std.debug.print(" -a <addr> listen/connect IPv4 addr (default: {s})\n", .{default_opts.addr});
+ std.debug.print(" -m <bytes> message size in bytes, client only (default: {})\n", .{default_opts.msg_size});
+ std.debug.print(" -h this help message\n", .{});
+ break null;
+ } else {
+ std.log.err("Unknown argument '{s}'", .{arg});
+ break null;
+ }
+ } else opts;
+}
+
+fn run_server(alloc: std.mem.Allocator, addr: std.net.Address) !void {
+ // Start a new server.
+ var server = try addr.listen(.{ .reuse_address = true });
+ defer server.deinit();
+
+ while (true) {
+ // Accept a new client.
+ std.log.info("server listening on {}", .{addr});
+ const client = (try server.accept()).stream;
+ defer client.close();
+
+ // Allocate message buffer from client provided message size.
+ const buf = blk: {
+ // (1) Read message size from client (format: "<bytes>\n").
+ var msg_buf: [16]u8 = undefined;
+ const msg = try client.reader().readUntilDelimiter(&msg_buf, '\n');
+ const msg_size = try std.fmt.parseInt(usize, msg, 10);
+
+ // (2) Allocate actual message buffer.
+ std.log.info("allocate message buffer with {} bytes", .{msg_size});
+ break :blk try alloc.alloc(u8, msg_size);
+ };
+ defer alloc.free(buf);
+
+ // Function to receive and loop back message buffer.
+ const loop_message = struct {
+ fn call(c: std.net.Stream, b: []u8) !void {
+ _ = try c.readAll(b);
+ try c.writeAll(b);
+ }
+ }.call;
+
+ // Send message buffer in a loop, on error wait for a new client.
+ while (true) {
+ loop_message(client, buf) catch break;
+ }
+ }
+}
+
+fn run_client(alloc: std.mem.Allocator, addr: std.net.Address, msg_size: usize) !void {
+ // Connect to a server.
+ std.log.info("client connecting to {}", .{addr});
+ var con = try std.net.tcpConnectToAddress(addr);
+ defer con.close();
+
+ // Send message size and allocate message buffer.
+ const buf = blk: {
+ // (1) Send message size to server (format: "<bytes>\n").
+ var msg_buf: [16]u8 = undefined;
+ const msg = try std.fmt.bufPrint(&msg_buf, "{}\n", .{msg_size});
+ try con.writeAll(msg);
+
+ // (2) Allocate actual message buffer.
+ std.log.info("allocate message buffer with {} bytes", .{msg_size});
+ break :blk try alloc.alloc(u8, msg_size);
+ };
+ defer alloc.free(buf);
+
+ // Send message buffer in a loop, and periodically report throughput.
+ while (true) {
+ var count: usize = 0;
+ const start = try std.time.Instant.now();
+ var delta: u64 = 0;
+
+ while (delta < std.time.ns_per_s) : (delta = (try std.time.Instant.now()).since(start)) {
+ try con.writeAll(buf);
+ _ = try con.readAll(buf);
+ count += 1;
+ }
+
+ const bytes_per_sec = count * 2 * buf.len * (delta / std.time.ns_per_s);
+ std.log.info("{:.2}/sec ping-pong messages", .{std.fmt.fmtIntSizeBin(bytes_per_sec)});
+ }
+}
+
+pub fn main() !void {
+ // Command line options.
+ const opts = try parse_args() orelse return;
+ const addr = try std.net.Address.parseIp4(opts.addr, 8080);
+
+ // Allocator.
+ var gpa = std.heap.GeneralPurposeAllocator(.{}).init;
+ defer std.debug.assert(gpa.deinit() == .ok);
+ const alloc = gpa.allocator();
+
+ // Run server or client.
+ try if (opts.is_server)
+ run_server(alloc, addr)
+ else
+ run_client(alloc, addr, opts.msg_size);
+}