summaryrefslogtreecommitdiff
path: root/example-network/pong.zig
blob: 3ef861c4fd5b3730ee6d5dadcc27eaa0ea045b2f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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);
}