Help trying to translate some C code to Zig

So I’m trying to convert some C code for an IRC server to Zig, and right now I’m not really sure where some stuff can be found.

bool server_init (Server_t *const server, i32 port, const char *const password) {
        if (!server or port < 1024 or port >= 65536 or !password) {
                return (false);
        }

        signal (SIGINT, server_handle_signal);
        signal (SIGQUIT, server_handle_signal);
        log_info ("server : signal_handler setup.");

        memset (&server->address, 0, sizeof (struct sockaddr_in));
        server->address.sin_family      = AF_INET;
        server->address.sin_addr.s_addr = INADDR_ANY;
        server->address.sin_port        = htons (port);
        log_info ("server : port resolution done.");

        server->password = strdup (password);
        if (!server->password) {
                return (false);
        }
        log_info ("server : password setup.");

        server->socket = socket (AF_INET, SOCK_STREAM, 0);
        if (server->socket == -1) {
                return (false);
        }
        log_info ("server : socket openned.");

        isize error_or_value = 0;
        isize optval         = 0;

        error_or_value = setsockopt (server->socket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof (optval));
        if (error_or_value == -1) {
                return (false);
        }
        log_info ("server : socket options setup.");

        error_or_value = fcntl (server->socket, F_SETFL, O_NONBLOCK);
        if (error_or_value == -1) {
                return (false);
        }
        log_info ("server : socket switched to O_NONBLOCK.");

        error_or_value = bind (server->socket, (const struct sockaddr *) &server->address, sizeof (struct sockaddr_in));
        if (error_or_value == -1) {
                return (false);
        }
        log_info ("server : socket binded.");

        error_or_value = listen (server->socket, SOMAXCONN);
        if (error_or_value == -1) {
                return (false);
        }
        log_info ("server : socket is listenning.");

        server->pollfds.push_back ((struct pollfd){
            .fd      = server->socket,
            .events  = POLLIN,
            .revents = 0,
        });

        log_info ("server : initialization is done. waiting for new connections...");
        return (true);
}

So first of all I’m not a network/web person, I’m very ignorant about that kind of programming, so this is why I’m asking because since I’m not very familiar with this kind of code, the different options in the std weren’t very clear to me.

The first thing that I need is about the signal handling, I’ve looked in a few namespace for the signal function to map a handler to a signal but I can’t seem to find one in the different namespaces of the std.

Secondly I need htons which If my understanding is correct is simply a macro that reverse the bits of the port to be in Big endian ?.

also I’ve noticed that there exist the std.net namespace, which I think is a different API which can achieve the same thing, if anyone could indicate me what would be the equivalent code using the std.net, or if anyone knows some code that I can look at to understand it would be appreciated.

For signal handling use sigaction.

htons: h means host, n means networking, s means short.
i.e. convert short (e.g. port number) from host to networking (big endian) byte order.

The socket networking functions and structures are at std.posix namespace.

for htons I think std.mem.nativeToBig is the answer right ? Ok thanks for the sigaction, I know it’s better to use sigaction, but I wanted signal just for the now before doing the right thing, but I guess sigaction it is.

1 Like

This may be useful to you: Beej's Guide to Network Programming

Look inside the std.posix namespace, in your case std.posix.sigaction().

Pretty much, yeah.

That C code is using the POSIX sockets APIs, so you might have more luck looking inside std.posix (e.g. std.posix.socket() to create a socket).

With the std.net APIs, you can do something like this (this is a barebones echo server):

const std = @import("std");

pub fn main() !void {
    const port: u16 = 1234; // change as needed :)
    const addr = std.net.Address.initIp4(.{ 0, 0, 0, 0 }, port);
    var server = try addr.listen(.{ .reuse_port = true });

    while (true) {
        const conn = try server.accept();
        const reader = conn.stream.reader();
        const writer = conn.stream.writer();
        while (true) {
            var buf: [1024]u8 = undefined;
            const n = try reader.read(&buf);
            try writer.writeAll(buf[0..n]);
        }
    }
}
2 Likes

Yes, it is perfect.

1 Like

Yes thanks for your advice and example it’s everything I was looking for, I’ve also figured that actually most things I was looking for are macros so things like INADDR_ANY is just this :

/* Address to accept any incoming messages.  */
#define	INADDR_ANY		((in_addr_t) 0x00000000)

I’m tired of getting bamboozled and bullied by the C standard, I know it was a macro but I thought it was more than that lol. I think I will first try to do a faithful translation using the exact same thing, and then another one using the std.net API’s.

In any case thanks everyone :slight_smile:

I realized my previous response was not great as it never returned from the inner loop handling the connection, meaning it’s only capable of handling up to 1 connection and then it hangs there :sweat_smile:

Here’s a revised version:

const std = @import("std");

var running: bool = true;

fn interrupt(_: i32) callconv(.C) void {
    running = false;
}

pub fn main() !void {
    const port: u16 = 1234; // change as needed :)
    const addr = std.net.Address.initIp4(.{ 0, 0, 0, 0 }, port);
    var server = try addr.listen(.{ .reuse_port = true });
    defer server.deinit();

    var sa: std.posix.Sigaction = .{
        .handler = .{ .handler = interrupt },
        .mask = std.posix.empty_sigset,
        .flags = std.posix.SA.RESTART,
    };
    std.posix.sigaction(std.posix.SIG.INT, &sa, null);

    std.debug.print(" [::] Listening on port {d}...\n", .{addr.getPort()});

    while (running) {
        const conn = try server.accept();
        defer conn.stream.close();

        const reader = conn.stream.reader();
        const writer = conn.stream.writer();

        std.debug.print(" [->] Accepted a connection (port {d})\n", .{conn.address.getPort()});

        while (running) {
            var buf: [1024]u8 = undefined;
            const n = try reader.read(&buf);
            if (n == 0) break;
            try writer.writeAll(buf[0..n]);
        }

        std.debug.print(" [<-] Closing connection.\n", .{});
    }

    std.debug.print(" [::] Goodbye!\n", .{});
}

This is still suboptimal because blocking syscalls like server.accept() and reader.read() are automatically restarted on EINTR, meaning that the SIGINT handler catches the Ctrl+C correctly but then keeps waiting on the server.accept() or reader.read() until a new loop can run, either by handling a new connection or filling up the 1 KiB buffer…

I guess the best option for now is to use the POSIX layer directly and skip std.net altogether :slight_smile: actually this happens in the POSIX layer which is indirectly used by std.net, so that wouldn’t help :frowning:

1 Like

Ah don’t worry mate, I was just looking for bit of help, there are a few things that are not very intuitive going from C to Zig for the code I was porting, like for example I was expecting to find the O_NONBLOCK, inside posix.O.NONBLOCK, but it was in posix.SOCK.NONBLOCK, there are a few things here and there, but thanks for pointing me in the right direction, I’m really happy with the result, The Zig IRC server, is really better than the one in C++ or the one in C that I have. More reliable and the memory footprint, is 20x improvement over my C++ solution. I’m sure I could have done a better job in C++, but the good thing about Zig is that even the “naive” implementation ran on 512k of memory usage, on the other hand my “naive” C++ implementation is like 9mb of ram IDLE.

3 Likes