1 | module main |
2 | |
3 | import net |
4 | import time |
5 | |
6 | const xport = 15523 |
7 | |
8 | struct Context { |
9 | mut: |
10 | ok_client_dials int |
11 | fail_client_dials int |
12 | // |
13 | ok_client_close int |
14 | fail_client_close int |
15 | //// |
16 | ok_server_accepts int |
17 | fail_server_accepts int |
18 | // |
19 | ok_server_close int |
20 | fail_server_close int |
21 | // |
22 | received []int |
23 | } |
24 | |
25 | fn elog(msg string) { |
26 | eprintln('${time.now().format_ss_micro()} | ${msg}') |
27 | } |
28 | |
29 | fn receive_data(mut con net.TcpConn, shared ctx Context) { |
30 | mut buf := []u8{len: 5} |
31 | for { |
32 | bytes := con.read(mut buf) or { -1 } |
33 | if bytes < 0 { |
34 | break |
35 | } |
36 | if bytes > 0 { |
37 | lock ctx { |
38 | ctx.received << buf[0] |
39 | } |
40 | } |
41 | } |
42 | con.close() or { |
43 | lock ctx { |
44 | ctx.fail_server_close++ |
45 | } |
46 | return |
47 | } |
48 | lock ctx { |
49 | ctx.ok_server_close++ |
50 | } |
51 | } |
52 | |
53 | fn start_server(schannel chan int, shared ctx Context) { |
54 | elog('server: start_server') |
55 | mut tcp_listener := net.listen_tcp(net.AddrFamily.ip, ':${xport}') or { |
56 | elog('server: start server error ${err}') |
57 | return |
58 | } |
59 | elog('server: server started listening at port :${xport}') |
60 | schannel <- 0 |
61 | |
62 | for { |
63 | mut tcp_con := tcp_listener.accept() or { |
64 | elog('server: accept error: ${err}') |
65 | lock ctx { |
66 | ctx.fail_server_accepts++ |
67 | } |
68 | continue |
69 | } |
70 | spawn receive_data(mut tcp_con, shared ctx) |
71 | lock ctx { |
72 | ctx.ok_server_accepts++ |
73 | } |
74 | elog('server: new tcp connection con.sock.handle: ${tcp_con.sock.handle}') |
75 | continue |
76 | } |
77 | } |
78 | |
79 | fn start_client(i int, shared ctx Context) { |
80 | elog('client [${i}]: start') |
81 | mut tcp_con := net.dial_tcp('127.0.0.1:${xport}') or { |
82 | elog('client [${i}]: net.dial_tcp err ${err}') |
83 | lock ctx { |
84 | ctx.fail_client_dials++ |
85 | } |
86 | return |
87 | } |
88 | lock ctx { |
89 | ctx.ok_client_dials++ |
90 | } |
91 | elog('client [${i}]: conn is connected, con.sock.handle: ${tcp_con.sock.handle}') |
92 | tcp_con.write([u8(i)]) or { elog('client [${i}]: write failed, err: ${err}') } |
93 | time.sleep(1 * time.second) |
94 | elog('client [${i}]: closing connection...') |
95 | tcp_con.close() or { |
96 | elog('client [${i}]: close failed, err: ${err}') |
97 | lock ctx { |
98 | ctx.fail_client_close++ |
99 | } |
100 | return |
101 | } |
102 | lock ctx { |
103 | ctx.ok_client_close++ |
104 | } |
105 | } |
106 | |
107 | fn test_tcp_self_dialing() { |
108 | elog('>>> start') |
109 | start_time := time.now() |
110 | shared ctx := &Context{} |
111 | mut server_channel := chan int{cap: 1} |
112 | spawn start_server(server_channel, shared ctx) |
113 | svalue := <-server_channel |
114 | elog('>>> server was started: ${svalue}. Starting clients:') |
115 | for i := int(0); i < 20; i++ { |
116 | spawn start_client(i, shared ctx) |
117 | elog('>>> started client ${i}') |
118 | // time.sleep(2 * time.millisecond) |
119 | } |
120 | max_dt := 5 * time.second |
121 | for { |
122 | t := time.now() |
123 | dt := t - start_time |
124 | if dt > max_dt { |
125 | elog('>>> exiting after ${dt.milliseconds()} ms ...') |
126 | lock ctx { |
127 | // TODO: fix `dump(ctx)`, when `shared ctx := Type{}` |
128 | final_value_for_ctx := ctx // make a value copy as a temporary workaround. TODO: remove when dump(ctx) works. |
129 | dump(final_value_for_ctx) |
130 | assert ctx.fail_client_dials < 2, 'allowed failed client dials, from ${ctx.ok_server_accepts} connections' |
131 | assert ctx.received.len > ctx.ok_server_accepts / 2, 'at least half the clients sent some data, that was later received by the server' |
132 | } |
133 | elog('>>> goodbye') |
134 | exit(0) |
135 | } |
136 | time.sleep(10 * time.millisecond) |
137 | } |
138 | } |