bmaxa
Legenda
- Poruka
- 70.808
Elem opet sam se malo igrao za zigom, pa napravih mali server, koji donekle dobro radi.
Radi ovako: primi do 100 konekcija, pa onda ceka da se sve zavrse.
Normalno, lako se obori, samo treba otvoriti jednu konekciju izmedju ostalih,
i cutati, pa ce da se zaglupi. Tako se u principu moze oboriti svaki server,
zato oni imaju read timeout, sto ovde nema.
No necemo sad da pravimo bulletproof server, nego da pokazemo
kako se async koristi u zigu.
Prvo sam probao bez threadova, ali ako se pozovu dva async poziva
nad istom f-jom u istom threadu, dolazi do gazenja konteksta f-je,
sto nije dobro. Dakle jedan async poziv iste f-je, u jednom threadu.
To zato da ne bi morali da zovemo await odmah nakon async,
sto nema poentu, jer onda nije asinhrono
dakle evo ga:
zamenite samo u parseIp sa nekom drugom adresom (recimo "0.0.0.0" za bilo koju), ukoliko nemate ipv6.
ovo se kompajlira sa recimo: zig build-exe -fstage1 -O ReleaseSmall test.zig
ovo fstage1 je potrebno za async, jer je lik poceo da pravi self hosted compiler pa jos nije tu implementirao async.
E sad klijent za testiranje ovog programa, u Go-u:
koristi se ovako:
dakle ide concurrency sto znaci sa koliko konekcija paralelno da napadne server,
broj requestova ukupno,
i ovo zadnje je koja adresa
ort
Ne pokusavajte ovaj program na internetu, jer ce vas verovatno banovati, samo na ovom serveru.
Dakle ovo radi svuda bez izmena, pa malo eksperimentisite
Radi ovako: primi do 100 konekcija, pa onda ceka da se sve zavrse.
Normalno, lako se obori, samo treba otvoriti jednu konekciju izmedju ostalih,
i cutati, pa ce da se zaglupi. Tako se u principu moze oboriti svaki server,
zato oni imaju read timeout, sto ovde nema.
No necemo sad da pravimo bulletproof server, nego da pokazemo
kako se async koristi u zigu.
Prvo sam probao bez threadova, ali ako se pozovu dva async poziva
nad istom f-jom u istom threadu, dolazi do gazenja konteksta f-je,
sto nije dobro. Dakle jedan async poziv iste f-je, u jednom threadu.
To zato da ne bi morali da zovemo await odmah nakon async,
sto nema poentu, jer onda nije asinhrono

dakle evo ga:
Kod:
bmaxa@Branimirs-Air zigrays % cat test.zig
const std = @import("std");
const net = std.net;
const mem = std.mem;
const testing = std.testing;
pub const log_level: std.log.Level = .debug;
pub const io_mode = .evented;
pub fn main() !void {
if (!std.io.is_async) return error.SkipZigTest;
// Ignore sigpipe
var act = std.os.Sigaction{
.handler = .{ .handler = std.os.SIG.IGN },
.mask = std.os.empty_sigset,
.flags = 0,
};
try std.os.sigaction(std.os.SIG.PIPE, &act, null);
const localhost = try net.Address.parseIp("::", 6666);
var server = net.StreamServer.init(net.StreamServer.Options{ .reuse_address = true });
defer server.deinit();
try server.listen(localhost);
var server_frame = async testServer(&server);
var client_frame = async testClient(server.listen_address);
try await server_frame;
try await client_frame;
}
fn testClient(addr: net.Address) anyerror!void {
const socket_file = try net.tcpConnectToAddress(addr);
defer socket_file.close();
var buf: [100]u8 = undefined;
_ = try socket_file.write("abcd");
const len = try socket_file.read(&buf);
const msg = buf[0..len];
_ = try testing.expect(mem.eql(u8, msg, "hello from server\n"));
}
fn testServer(server: *net.StreamServer) anyerror!void {
var connections:u32 = 0;
var tasks:std.ArrayList(@Frame(handle)) = std.ArrayList(@Frame(handle)).init(std.heap.c_allocator);
var threads:std.ArrayList(std.Thread) = std.ArrayList(std.Thread).init(std.heap.c_allocator);
var clients: std.ArrayList(net.StreamServer.Connection) = std.ArrayList(net.StreamServer.Connection).init(std.heap.c_allocator);
while (true) {
while (connections<100) :(connections += 1) {
var client = try server.accept();
std.log.info("accepted: {}",.{client});
_ = try tasks.addOne();
_ = try clients.append(client);
_ = try threads.append(try std.Thread.spawn(std.Thread.SpawnConfig{}, tf, .{&tasks.items[tasks.items.len-1],client}));
}
for (tasks.items) |*t,index| { std.log.info("waiting {} {}",.{index,t});await t catch |err| {
std.log.warn("Disconnected {}: {}\n", .{ clients.items[index], err });
};
}
for (threads.items) |*t| {
t.join();
}
tasks.clearRetainingCapacity();
threads.clearRetainingCapacity();
clients.clearRetainingCapacity();
connections = 0;
}
}
fn handle(client: net.StreamServer.Connection) !void {
defer client.stream.close();
const wstream = client.stream.writer();
const readstream = client.stream.reader();
var buf: [256]u8 = undefined;
const len = try readstream.read(buf[0..]);
std.debug.print("got {s}\n",.{buf[0..len]});
_ = try wstream.print("hello from server\n", .{});
}
fn tf(frame: *@Frame(handle), client:net.StreamServer.Connection)!void{
frame.* = async handle(client);
}
zamenite samo u parseIp sa nekom drugom adresom (recimo "0.0.0.0" za bilo koju), ukoliko nemate ipv6.
ovo se kompajlira sa recimo: zig build-exe -fstage1 -O ReleaseSmall test.zig
ovo fstage1 je potrebno za async, jer je lik poceo da pravi self hosted compiler pa jos nije tu implementirao async.
E sad klijent za testiranje ovog programa, u Go-u:
koristi se ovako:
Kod:
bmaxa@Branimirs-Air tcpclient % ./tcpclient
numcpu: 8
usage : tcpclient conc nreqs address:port
broj requestova ukupno,
i ovo zadnje je koja adresa

Ne pokusavajte ovaj program na internetu, jer ce vas verovatno banovati, samo na ovom serveru.
Kod:
bmaxa@Branimirs-Air tcpclient % cat main.go
package main
import (
"net"
"io/ioutil"
"os"
"fmt"
"flag"
"strconv"
"sync"
"time"
"runtime"
)
var exit chan int = make(chan int)
func main() {
timeout, err := time.ParseDuration("2s")
runtime.GOMAXPROCS(runtime.NumCPU())
fmt.Println("numcpu:",runtime.NumCPU())
flag.Parse()
if flag.NArg() != 3 {
fmt.Println("usage : tcpclient conc nreqs address:port");
return
}
ta,err := net.ResolveTCPAddr("tcp",flag.Arg(2))
if err != nil {
fmt.Fprintln(os.Stderr,err)
return
}
fmt.Println(flag.Arg(2))
conc,_ := strconv.Atoi(flag.Arg(0))
nreqs,_ := strconv.Atoi(flag.Arg(1))
reqs := float64(nreqs)
bytes := 0
l := sync.Mutex{}
pb := sync.Mutex{}
cnd := sync.NewCond(&l)
count := 0
start := time.Now()
exit1 := make(chan int)
go func(){
var rc,errcnt int
for reqs:=nreqs;reqs > 0;reqs-- {
if reqs % 100 == 0 {
fmt.Println("completed",nreqs-reqs)
}
rc = <- exit
errcnt += rc
}
fmt.Println("errors",errcnt)
exit1 <- 1
}()
for nreqs:=reqs;nreqs>0; {
cnd.L.Lock()
cu := count
cnd.L.Unlock()
if cu < conc {
nreqs--
cnd.L.Lock()
count++
cnd.L.Unlock()
go func() {
conn,e := net.DialTCP("tcp",nil,ta)
defer func() {
cnd.L.Lock()
count--
cnd.Signal()
cnd.L.Unlock()
var rc = 1
if conn != nil { conn.Close();rc = 0; }
exit <- rc
} ()
if e != nil {
fmt.Fprintln(os.Stderr,e)
return
}
conn.SetDeadline(time.Now().Add(timeout))
conn.Write([]byte("GET / HTTP/1.0\r\n\r\n"))
b,_ := ioutil.ReadAll(conn)
pb.Lock()
bytes += len(b)
pb.Unlock()
}()
} else {
cnd.L.Lock()
for count >= conc {
cnd.Wait()
}
cnd.L.Unlock()
}
}
<- exit1
end := time.Since(start)
fmt.Printf("%f\nbytes %d\n",reqs/end.Seconds(),bytes)
}
Dakle ovo radi svuda bez izmena, pa malo eksperimentisite
