Last active
January 23, 2024 03:51
-
-
Save lithdew/9e8f069338686d408aed97ac7306e177 to your computer and use it in GitHub Desktop.
zig: basic tcp echo server w/ stdlib event loop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const os = std.os; | |
const net = std.net; | |
const mem = std.mem; | |
const log = std.log.scoped(.server); | |
const assert = std.debug.assert; | |
pub const io_mode = .evented; | |
pub const log_level = .debug; | |
pub fn main() !void { | |
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; | |
defer assert(!gpa.deinit()); | |
var listener = net.StreamServer.init(.{}); | |
defer listener.deinit(); | |
try listener.listen(net.Address.initIp4(.{ 0, 0, 0, 0 }, 9000)); | |
log.info("listening for clients at: {}", .{listener.listen_address}); | |
var server: Server = .{ .listener = listener }; | |
defer { | |
server.shutdown(); | |
server.deinit(&gpa.allocator); | |
} | |
var server_frame = async server.serve(&gpa.allocator); | |
defer await server_frame catch |err| log.warn("server error: {}", .{err}); | |
// usually you'd wait for a Ctrl+C or interrupt signal here | |
} | |
pub const Server = struct { | |
lock: std.Thread.Mutex = .{}, | |
listener: net.StreamServer, | |
clients: std.AutoHashMapUnmanaged(*@Frame(Server.serveClient), net.StreamServer.Connection) = .{}, | |
pub fn deinit(self: *Server, gpa: *mem.Allocator) void { | |
self.clients.deinit(gpa); | |
} | |
pub fn shutdown(self: *Server) void { | |
std.os.shutdown(self.listener.sockfd.?, .recv) catch {}; | |
const held = self.lock.acquire(); | |
defer held.release(); | |
var it = self.clients.valueIterator(); | |
while (it.next()) |conn| { | |
std.os.shutdown(conn.stream.handle, .recv) catch {}; | |
} | |
} | |
pub fn serve(self: *Server, gpa: *mem.Allocator) !void { | |
while (true) { | |
std.event.Loop.instance.?.yield(); | |
const conn = self.listener.accept() catch |err| switch (err) { | |
error.ProcessFdQuotaExceeded, error.SystemFdQuotaExceeded, error.SystemResources => continue, | |
error.SocketNotListening => return, | |
else => return err, | |
}; | |
const frame = gpa.create(@Frame(Server.serveClient)) catch { | |
conn.stream.close(); | |
continue; | |
}; | |
{ | |
const held = self.lock.acquire(); | |
defer held.release(); | |
self.clients.putNoClobber(gpa, frame, conn) catch { | |
gpa.destroy(frame); | |
conn.stream.close(); | |
continue; | |
}; | |
} | |
frame.* = async self.serveClient(gpa, conn); | |
} | |
} | |
pub fn serveClient(self: *Server, gpa: *mem.Allocator, conn: net.StreamServer.Connection) !void { | |
defer { | |
conn.stream.close(); | |
suspend self.deallocateClient(gpa, @frame()); | |
} | |
log.info("peer connected: {}", .{conn.address}); | |
defer log.info("peer disconnected: {}", .{conn.address}); | |
var buffer: [1024]u8 = undefined; | |
while (true) { | |
std.event.Loop.instance.?.yield(); | |
const num_bytes = try conn.stream.read(&buffer); | |
if (num_bytes == 0) return; | |
try conn.stream.writer().writeAll(buffer[0..num_bytes]); | |
} | |
} | |
pub fn deallocateClient(self: *Server, gpa: *mem.Allocator, frame: *@Frame(Server.serveClient)) void { | |
const held = self.lock.acquire(); | |
defer held.release(); | |
assert(self.clients.remove(frame)); | |
gpa.destroy(frame); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment