Skip to content

Instantly share code, notes, and snippets.

@matu3ba
Created April 10, 2022 13:13
Show Gist options
  • Save matu3ba/b742d68bf624a51b28d73396315f2a1a to your computer and use it in GitHub Desktop.
Save matu3ba/b742d68bf624a51b28d73396315f2a1a to your computer and use it in GitHub Desktop.
child_process with tcp connection for portability eventually be used as test_runner
//! client started by server as subprocess
const std = @import("std");
const builtin = @import("builtin");
const net = std.x.net;
const os = std.x.os;
const testing = std.testing;
const tcp = net.tcp;
const ip = net.ip;
const native_os = builtin.os;
const IPv4 = os.IPv4;
const IPv6 = os.IPv6;
const Socket = os.Socket;
const Buffer = os.Buffer;
// 1. server opens socket for client to connect
// 2. server --spawn child_process--> client with transmit ip address to connect to
// 3. client connects to socket
// 4. test stuff
pub fn main() !void {
const allocator = testing.allocator;
std.debug.print("client started\n", .{});
//const std.process.
std.debug.print("pid\n", .{});
_ = allocator;
var it = try std.process.argsWithAllocator(allocator);
defer it.deinit(); // no-op unless WASI or Windows
_ = it.next(); // ignore binary name
const str_ip = it.next().?;
const str_port = it.next().?;
try std.testing.expect(!it.skip());
const client = try tcp.Client.init(.ip, .{ .close_on_exec = true });
defer client.deinit();
const ipv4 = try IPv4.parse(str_ip);
const port = try std.fmt.parseUnsigned(u16, str_port, 10);
const binded_address = ip.Address.initIPv4(ipv4, port);
try client.connect(binded_address);
const message = "hello world";
var buf: [message.len + 1]u8 = undefined;
var msg = Socket.Message.fromBuffers(&[_]Buffer{
Buffer.from(buf[0 .. message.len / 2]),
Buffer.from(buf[message.len / 2 ..]),
});
_ = try client.readMessage(&msg, 0);
try testing.expectEqualStrings(message, buf[0..message.len]);
std.debug.print("comparison successful\n", .{});
try client.shutdown(std.os.ShutdownHow.both);
std.debug.print("client exited\n", .{});
}
// zig build-exe client.zig
//! server starts client as subprocess
const std = @import("std");
const builtin = @import("builtin");
const native_os = builtin.os;
const net = std.x.net;
const os = std.x.os;
const testing = std.testing;
const ChildProcess = std.ChildProcess;
const tcp = net.tcp;
const ip = net.ip;
const IPv4 = os.IPv4;
const IPv6 = os.IPv6;
const Socket = os.Socket;
const Buffer = os.Buffer;
const have_ifnamesize = @hasDecl(std.os.system, "IFNAMESIZE"); // interface namesize
// 1. server opens socket for client to connect
// 2. server --spawn child_process--> client + transmit ip address to connect to
// 3. client connects to socket
// 4. test stuff
// running on raw sockets is not possible without additional permissions
// https://squidarth.com/networking/systems/rc/2018/05/28/using-raw-sockets.html
// only alternative: tcp/udp
pub fn main() !void {
if (!have_ifnamesize) return error.FAILURE;
const allocator = testing.allocator;
std.debug.print("server started\n", .{});
const listener = try tcp.Listener.init(.ip, .{ .close_on_exec = true });
defer listener.deinit();
try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
try listener.listen(128);
var binded_address = try listener.getLocalAddress();
switch (binded_address) {
.ipv4 => |*ipv4| ipv4.host = IPv4.localhost,
.ipv6 => |*ipv6| ipv6.host = IPv6.localhost,
}
// max port nr IPv4: unsigned 16-bit integer, so 65535 (5 digits)
// max ip nr IPv4: 255.255.255.255 => 12 digits + 3 dots + : 1 => 16 digits
std.debug.print("address: {s}\n", .{binded_address.ipv4.host});
const str_ip = try std.fmt.allocPrint(allocator, "{s}", .{binded_address.ipv4.host});
const str_port = try std.fmt.allocPrint(allocator, "{d}", .{binded_address.ipv4.port});
std.debug.print("{s}\n", .{str_ip});
std.debug.print("{s}\n", .{str_port});
const ipv4 = try IPv4.parse(str_ip);
const port = try std.fmt.parseUnsigned(u16, str_port, 10);
std.debug.print("{s}\n", .{ipv4});
std.debug.print("{d}\n", .{port});
const cwd_path = try std.process.getCwdAlloc(allocator);
std.debug.print("cwd_path: {s}\n", .{cwd_path});
// TODO absolute paths
const args = [_][]const u8{ "./client", str_ip, str_port };
var child_proc = try ChildProcess.init(&args, allocator);
defer child_proc.deinit();
try child_proc.spawn();
std.debug.print("child_proc spawned:\n", .{});
std.debug.print("args {s}\n", .{args});
const conn = try listener.accept(.{ .close_on_exec = true }); // accept is blocking
defer conn.deinit();
std.debug.print("connection succesful\n", .{});
const message = "hello world";
_ = try conn.client.writeMessage(Socket.Message.fromBuffers(&[_]Buffer{
Buffer.from(message[0 .. message.len / 2]),
Buffer.from(message[message.len / 2 ..]),
}), 0);
// server initiates shutdown before waiting on client
// reason: client process may not finish => we do not get stdout/stderr and shutdown
try listener.shutdown();
const ret_val = child_proc.wait();
try testing.expectEqual(ret_val, .{ .Exited = 0 });
std.debug.print("server exited\n", .{});
}
// zig build-exe server.zig
// run with ./server
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment