Sockets with std.net in Zig 0.13.0

I’m a bit confused on how to create sockets with std.net in Zig 0.13.0. There are no tutorials for 0.13, only 0.12, and from what I can see, std.net has changed in this verison.

Right now, I’m using just the regular posix functions to create my UDP sockets. You know, the regular socket(), bind() and then connect() if I’m on a client or just recvfrom() if I’m making the server.

Is there any tutorial and / or resource to see how this works ? Because I can’t seem to decipher exactly what I’m supposed to do with std.net. The only thing I figured out is that std.net.Address is a wrapper for C’s struct sockaddr.

Just pointing out resources or even just telling me if std.net should be used at all would be helpful. Then I’ll just use posix without worrying if there is a correct “Zig way” of doing things.

I haven’t really used it a lot yet, but these topics seem related:

This one is a bit older, back then a bunch of functions were in std.os that are now in std.posix:

I think for these sorts of things the cookbook is often a good starting point:

Thank you so much !

I actually had a look at the “UDP Socket Server in Zig” post you were talking about, but they used Zig 0.11 and 012, and also mostly used std.posix and std.os.linux.

The first post has a poor title, but it was also using just posix. I wish people titled their posts better lmao :D.

I also didn’t know there was a Zig cookbook, so that helped quite a bit. I will probably refer to that quite a bit from now.

Seems like networking in Zig is just using posix, which I don’t mind. I’m familiar with it.

Thanks for the fast reply !

1 Like

There is also this library which seems quite popular:

Hey, I am author of the first post here. I do also greatly regret the post title after creating it (was quite passed out when posting it) :slight_smile:

About your post, I greatly recommend this series on low level network programming with zig (even thought it is about TCP, not UDP but I believe it is still very useful) if you haven’t seen it already:

The author also provides his insights about the current state of std.net abstraction (and why should you use lower-level posix API instead) which I fully agree with.

1 Like

This is a cool library, and I do need both Linux and MacOS support. I’ll probably give this a shot in my project. Thanks !

1 Like

Don’t worry about the title lmao. It happens to the best of us.

As you said, the article you recommended basically answered my question about std.net, namely the fact that it is incomplete. I disagree that we should use the posix api though, reliable cross platform net code would have been so much better. You can’t always trust an OS to properly implement POSIX…

That leaves me with the choice of either using a library or writing my own network abstractions.

I think I’ll probably write my own abstractions because MacOS sockets are mostly just more posix stuff anyways, and I can probably get away without changing much code. As long as I don’t run into any posix breaking stuff on MacOS of course :D.

I think this posix in zig supports windows. For example bind:

/// addr is `*const T` where T is one of the sockaddr
pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!void {
    if (native_os == .windows) {
        const rc = windows.bind(sock, addr, len);
        if (rc == windows.ws2_32.SOCKET_ERROR) {
            switch (windows.ws2_32.WSAGetLastError()) {
                .WSANOTINITIALISED => unreachable, // not initialized WSA
                .WSAEACCES => return error.AccessDenied,
                .WSAEADDRINUSE => return error.AddressInUse,
                .WSAEADDRNOTAVAIL => return error.AddressNotAvailable,
                .WSAENOTSOCK => return error.FileDescriptorNotASocket,
                .WSAEFAULT => unreachable, // invalid pointers
                .WSAEINVAL => return error.AlreadyBound,
                .WSAENOBUFS => return error.SystemResources,
                .WSAENETDOWN => return error.NetworkSubsystemFailed,
                else => |err| return windows.unexpectedWSAError(err),
            }
            unreachable;
        }
        return;
    } else {
        const rc = system.bind(sock, addr, len);
        switch (errno(rc)) {
            .SUCCESS => return,
            .ACCES, .PERM => return error.AccessDenied,
            .ADDRINUSE => return error.AddressInUse,
            .BADF => unreachable, // always a race condition if this error is returned
            .INVAL => unreachable, // invalid parameters
            .NOTSOCK => unreachable, // invalid `sockfd`
            .AFNOSUPPORT => return error.AddressFamilyNotSupported,
            .ADDRNOTAVAIL => return error.AddressNotAvailable,
            .FAULT => unreachable, // invalid `addr` pointer
            .LOOP => return error.SymLinkLoop,
            .NAMETOOLONG => return error.NameTooLong,
            .NOENT => return error.FileNotFound,
            .NOMEM => return error.SystemResources,
            .NOTDIR => return error.NotDir,
            .ROFS => return error.ReadOnlyFileSystem,
            else => |err| return unexpectedErrno(err),
        }
    }
    unreachable;
}

where windows is:

const windows = std.os.windows;

and bind:

pub fn bind(s: ws2_32.SOCKET, name: *const ws2_32.sockaddr, namelen: ws2_32.socklen_t) i32 {
    return ws2_32.bind(s, name, @as(i32, @intCast(namelen)));
}

and ws3_32:

pub const ws2_32 = @import("windows/ws2_32.zig");

and:

pub extern "ws2_32" fn bind(
    s: SOCKET,
    name: *const sockaddr,
    namelen: i32,
) callconv(.winapi) i32;

and ws2_32 is from ws2_32.dll I suppose

if(MSVC)
  target_link_libraries(zig2 LINK_PUBLIC ntdll.lib ws2_32.lib)
elseif(MINGW)
  target_link_libraries(zig2 LINK_PUBLIC ntdll ws2_32)
endif()

and bind in Windows Sockets 2 (ws2):

int bind(
  [in] SOCKET         s,
       const sockaddr *addr,
  [in] int            namelen
);

don’t expect full posix support for sockets on windows

the devil is in the details

what are the details?

EDIT: I have found this:

All of the major OSes uses the BSD style sockets. Windows is the one that’s bit weird with its WSA allowing vendor extensions but yeah.

extracted from my current project:

inline fn setTimeOut(cn: *Conn) !void {
    const timeout = posix.timeval{ .sec = 1, .usec = 0 };
    try posix.setsockopt(cn.connection.?.handle, posix.SOL.SOCKET, posix.SO.RCVTIMEO, &std.mem.toBytes(timeout));
}

It works on linux&macos, but doesn’t on windows

ms never claims it supports posix sockets

btw - thank you for the links

will this work?

inline fn setTimeOut(cn: *Conn) !void {
    if (std.os.tag == .windows) {
        var timeout_ms: std.os.windows.DWORD = 1000; // 1 second = 1000 ms
        try std.os.windows.setsockopt(
            cn.connection.?.handle,
            std.os.windows.SOL_SOCKET,
            std.os.windows.SO_RCVTIMEO,
            @ptrCast(*const u8, &timeout_ms),
            @sizeOf(std.os.windows.DWORD),
        );
    } else {
        const timeout = posix.timeval{ .tv_sec = 1, .tv_usec = 0 };
        try posix.setsockopt(
            cn.connection.?.handle,
            posix.SOL.SOCKET,
            posix.SO.RCVTIMEO,
            &std.mem.toBytes(timeout),
        );
    }
}
1 Like
test "setsockopt RCVTIMEO" {
    if (builtin.single_threaded) return error.SkipZigTest;
    if (builtin.os.tag == .wasi) return error.SkipZigTest;

    if (builtin.os.tag == .windows) {
        _ = try std.os.windows.WSAStartup(2, 2);
    }
    defer {
        if (builtin.os.tag == .windows) {
            std.os.windows.WSACleanup() catch unreachable;
        }
    }

    // Try only the IPv4 variant as some CI builders have no IPv6 localhost
    // configured.
    const localhost = try net.Address.parseIp("127.0.0.1", 0);

    var server = try localhost.listen(.{});
    defer server.deinit();

    const S = struct {
        fn clientFn(server_address: net.Address) !void {
            var stream = try net.tcpConnectToAddress(server_address);
            defer stream.close();

            try tryReadByte(&stream);

            try tryReadByte(&stream);

            _ = try stream.writer().writeAll("Hello world!");
        }
    };

    const t = try std.Thread.spawn(.{}, S.clientFn, .{server.listen_address});
    defer t.join();

    var client = try server.accept();
    defer client.stream.close();
    var buf: [16]u8 = undefined;
    const n = try client.stream.reader().read(&buf);

    try testing.expectEqual(@as(usize, 12), n);
    try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
}

fn tryReadByte(stream: *Stream) !void {
    try setTimeOut(stream);
    var str: [1]u8 = undefined;
    _ = stream.read(&str) catch |er| {
        if (er != error.WouldBlock) {
            return er;
        }
    };
    return;
}

fn setTimeOut(stream: *Stream) !void {
    if (builtin.target.os.tag == .windows) {
        var timeout_ms: std.os.windows.DWORD = 1000 * 5; // 1 second = 1000 ms
        _ = std.os.windows.ws2_32.setsockopt(
            stream.*.handle,
            std.os.windows.ws2_32.SOL.SOCKET,
            std.os.windows.ws2_32.SO.RCVTIMEO,
            @ptrCast(&timeout_ms),
            @sizeOf(std.os.windows.DWORD)
        );
    } else {
        const timeout = posix.timeval{ .sec = 5, .usec = 0 };
        try posix.setsockopt(stream.*.handle, posix.SOL.SOCKET, posix.SO.RCVTIMEO, &std.mem.toBytes(timeout));
    }
}

On macos&linux test works 10s (2 recv * 5 sec time out)
On windows - process stuck
So an answer is - setsockopt+RCVTIMEO does not work on windows
Possible reasons:

  • creation of the socket without overlaped flag
  • setsockopt after bind

It does not matter