Skip to content

Instantly share code, notes, and snippets.

@karlseguin
Forked from andrewrk/basic-tcp-chat.zig
Last active April 29, 2024 14:15
Show Gist options
  • Save karlseguin/53bb8ebf945b20aa0b7472d9d30de801 to your computer and use it in GitHub Desktop.
Save karlseguin/53bb8ebf945b20aa0b7472d9d30de801 to your computer and use it in GitHub Desktop.
Basic TCP Chat Server for Zig 0.12
// For Zig 0.12
const std = @import("std");
const net = std.net;
const ArenaAllocator = std.heap.ArenaAllocator;
pub fn main() anyerror!void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const address = try net.Address.parseIp("127.0.0.1", 5501);
var listener = try address.listen(.{
.reuse_address = true,
.kernel_backlog = 1024,
});
defer listener.deinit();
std.log.info("listening at {any}\n", .{address});
var room = Room{
.lock = .{},
.clients = std.AutoHashMap(*Client, void).init(allocator)
};
while (true) {
if (listener.accept()) |conn| {
var client_arena = ArenaAllocator.init(allocator);
const client = try client_arena.allocator().create(Client);
errdefer client_arena.deinit();
client.* = Client.init(client_arena, conn.stream, &room);
const thread = try std.Thread.spawn(.{}, Client.run, .{client});
thread.detach();
} else |err| {
std.log.err("failed to accept connection {}", .{err});
}
}
}
const Client = struct {
room: *Room,
arena: ArenaAllocator,
stream: net.Stream,
pub fn init(arena: ArenaAllocator, stream: net.Stream, room: *Room) Client {
return .{
.room = room,
.stream = stream,
.arena = arena,
};
}
fn run(self: *Client) !void {
defer self.arena.deinit();
try self.room.add(self);
defer {
self.room.remove(self);
self.stream.close();
}
const stream = self.stream;
_ = try stream.write("server: welcome to the chat server\n");
while (true) {
var buf: [100]u8 = undefined;
const n = try stream.read(&buf);
if (n == 0) {
return;
}
self.room.broadcast(buf[0..n], self);
}
}
};
const Room = struct {
lock: std.Thread.RwLock,
clients: std.AutoHashMap(*Client, void),
pub fn add(self: *Room, client: *Client) !void {
self.lock.lock();
defer self.lock.unlock();
try self.clients.put(client, {});
}
pub fn remove(self: *Room, client: *Client) void {
self.lock.lock();
defer self.lock.unlock();
_ = self.clients.remove(client);
}
fn broadcast(self: *Room, msg: []const u8, sender: *Client) void {
self.lock.lockShared();
defer self.lock.unlockShared();
var it = self.clients.keyIterator();
while (it.next()) |key_ptr| {
const client = key_ptr.*;
if (client == sender) continue;
_ = client.stream.write(msg) catch |e| std.log.warn("unable to send: {}\n", .{e});
}
}
};
Tested with zig 0.12.0.
from https://ziglang.org/download/ should work just fine.
zig build-exe basic-tcp-chat.zig
./basic-tcp-chat
This will print "listening at listening at 127.0.0.1:$PORT"
To play with it you have to open 2 terminal windows and in each one
(replacing $PORT with the one printed):
nc 127.0.0.1 $PORT
Now the terminals can talk to each other when you type your message and press enter.
@rofrol
Copy link

rofrol commented Apr 29, 2024

This if (client == sender) continue; does not work.
Client receives it's message.

@rofrol
Copy link

rofrol commented Apr 29, 2024

Somehow it works now, client does not receive its own message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment