summaryrefslogblamecommitdiff
path: root/example-network/pong.zig
blob: 3ef861c4fd5b3730ee6d5dadcc27eaa0ea045b2f (plain) (tree)







































































































































                                                                                                                          
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);
}