v / vlib / net / tcp.c.v
749 lines · 668 sloc · 21.93 KB · 75789c6f9989ff8de76988c5d312979ca5e27853
Raw
1module net
2
3import io
4import time
5import strings
6
7pub const tcp_default_read_timeout = 30 * time.second
8pub const tcp_default_write_timeout = 30 * time.second
9
10// TCPDialer is a concrete instance of the Dialer interface,
11// for creating tcp connections.
12pub struct TCPDialer {}
13
14// dial will try to create a new abstract connection to the given address.
15// It will return an error, if that is not possible.
16pub fn (t TCPDialer) dial(address string) !Connection {
17 return dial_tcp(address)!
18}
19
20// default_tcp_dialer will give you an instance of Dialer, that is suitable for making new tcp connections.
21pub fn default_tcp_dialer() Dialer {
22 return &TCPDialer{}
23}
24
25@[heap]
26pub struct TcpConn {
27pub mut:
28 sock TcpSocket
29 handle int
30 write_deadline time.Time
31 read_deadline time.Time
32 read_timeout time.Duration
33 write_timeout time.Duration
34 is_blocking bool = true
35 // last_write_sent is the exact number of bytes the most recent write_ptr
36 // call sent, valid even when it then returned an error (send() is
37 // byte-accurate), so a caller can tell a zero-byte failure — 0, safe to
38 // replay — from a partial write. (The TLS backends expose the same field but
39 // use -1 for the indeterminate case they cannot prove; plain TCP never does.)
40 last_write_sent int
41}
42
43// dial_tcp will try to create a new TcpConn to the given address.
44pub fn dial_tcp(oaddress string) !&TcpConn {
45 mut address := oaddress
46 $if windows {
47 // resolving 0.0.0.0 to localhost, works on linux and macos, but not on windows, so try to emulate it:
48 if address.starts_with(':::') {
49 address = address.replace_once(':::', 'localhost:')
50 }
51 if address.starts_with('0.0.0.0:') {
52 address = address.replace_once('0.0.0.0:', 'localhost:')
53 }
54 }
55 addrs := resolve_addrs_fuzzy(address, .tcp) or {
56 return error('${err.msg()}; could not resolve address ${address} in dial_tcp')
57 }
58
59 // Keep track of dialing errors that take place
60 mut errs := []IError{}
61
62 // Very simple dialer
63 for addr in addrs {
64 mut s := new_tcp_socket(addr.family()) or {
65 return error('${err.msg()}; could not create new tcp socket in dial_tcp')
66 }
67 s.connect(addr) or {
68 errs << err
69 // Connection failed
70 s.close() or { continue }
71 continue
72 }
73
74 mut conn := &TcpConn{
75 sock: s
76 read_timeout: tcp_default_read_timeout
77 write_timeout: tcp_default_write_timeout
78 }
79 // The blocking / non-blocking mode is determined before the connection is established.
80 $if net_nonblocking_sockets ? {
81 conn.is_blocking = false
82 }
83 return conn
84 }
85
86 // Once we've failed now try and explain why we failed to connect
87 // to any of these addresses
88 mut err_builder := strings.new_builder(1024)
89 err_builder.write_string('dial_tcp failed for address ${address}\n')
90 err_builder.write_string('tried addrs:\n')
91 for i := 0; i < errs.len; i++ {
92 addr := addrs[i]
93 why := errs[i]
94 err_builder.write_string('\t${addr}: ${why}\n')
95 }
96
97 // failed
98 return error(err_builder.str())
99}
100
101// dial_tcp_with_bind will bind the given local address `laddr` and dial.
102pub fn dial_tcp_with_bind(saddr string, laddr string) !&TcpConn {
103 addrs := resolve_addrs_fuzzy(saddr, .tcp) or {
104 return error('${err.msg()}; could not resolve address ${saddr} in dial_tcp_with_bind')
105 }
106
107 // Very simple dialer
108 for addr in addrs {
109 mut s := new_tcp_socket(addr.family()) or {
110 return error('${err.msg()}; could not create new tcp socket in dial_tcp_with_bind')
111 }
112 s.bind(laddr) or {
113 s.close() or { continue }
114 continue
115 }
116 s.connect(addr) or {
117 // Connection failed
118 s.close() or { continue }
119 continue
120 }
121
122 mut conn := &TcpConn{
123 sock: s
124 read_timeout: tcp_default_read_timeout
125 write_timeout: tcp_default_write_timeout
126 }
127 // The blocking / non-blocking mode is determined before the connection is established.
128 $if net_nonblocking_sockets ? {
129 conn.is_blocking = false
130 }
131 return conn
132 }
133 // failed
134 return error('dial_tcp_with_bind failed for address ${saddr}')
135}
136
137// close closes the tcp connection
138pub fn (mut c TcpConn) close() ! {
139 $if trace_tcp ? {
140 eprintln(' TcpConn.close | c.sock.handle: ${c.sock.handle:6}')
141 }
142 c.sock.close()!
143}
144
145// read_ptr reads data from the tcp connection to the given buffer. It reads at most `len` bytes.
146// It returns the number of actually read bytes, which can vary between 0 to `len`.
147pub fn (c TcpConn) read_ptr(buf_ptr &u8, len int) !int {
148 mut res := 0
149 mut ecode := 0
150 $if is_coroutine ? {
151 res = C.photon_recv(c.sock.handle, voidptr(buf_ptr), len, 0, c.read_timeout)
152 ecode = error_code()
153 } $else {
154 if c.is_blocking {
155 // Honor read deadlines/timeouts first, then use a normal blocking recv.
156 // This avoids transient EAGAIN-style reads on newly accepted sockets.
157 c.wait_for_read()!
158 res = C.recv(c.sock.handle, voidptr(buf_ptr), len, 0)
159 } else {
160 res = C.recv(c.sock.handle, voidptr(buf_ptr), len, msg_dontwait)
161 }
162 ecode = error_code()
163 }
164 if res == 0 {
165 return io.Eof{}
166 }
167 if res > 0 {
168 $if trace_tcp ? {
169 eprintln(
170 '<<< TcpConn.read_ptr | c.sock.handle: ${c.sock.handle} | buf_ptr: ${ptr_str(buf_ptr)} | len: ${len} | res: ${res} |\n' +
171 unsafe { buf_ptr.vstring_with_len(len) })
172 }
173 $if trace_tcp_data_read ? {
174 eprintln(
175 '<<< TcpConn.read_ptr | 1 data.len: ${res:6} | hex: ${unsafe { buf_ptr.vbytes(res) }.hex()} | data: ' +
176 unsafe { buf_ptr.vstring_with_len(res) })
177 }
178 return res
179 }
180 if ecode in [int(error_ewouldblock), int(error_eagain), C.EINTR] {
181 c.wait_for_read()!
182 res = $if is_coroutine ? {
183 C.photon_recv(c.sock.handle, voidptr(buf_ptr), len, 0, c.read_timeout)
184 } $else {
185 if c.is_blocking {
186 C.recv(c.sock.handle, voidptr(buf_ptr), len, 0)
187 } else {
188 C.recv(c.sock.handle, voidptr(buf_ptr), len, msg_dontwait)
189 }
190 }
191 if res == 0 {
192 return io.Eof{}
193 }
194 $if trace_tcp ? {
195 eprintln(
196 '<<< TcpConn.read_ptr | c.sock.handle: ${c.sock.handle} | buf_ptr: ${ptr_str(buf_ptr)} | len: ${len} | res: ${res} | code: ${ecode} |\n' +
197 unsafe { buf_ptr.vstring_with_len(len) })
198 }
199 $if trace_tcp_data_read ? {
200 if res > 0 {
201 eprintln(
202 '<<< TcpConn.read_ptr | 2 data.len: ${res:6} | hex: ${unsafe { buf_ptr.vbytes(res) }.hex()} | data: ' +
203 unsafe { buf_ptr.vstring_with_len(res) })
204 }
205 }
206 return socket_error(res)
207 } else {
208 wrap_error(ecode)!
209 }
210 return error('none')
211}
212
213// read reads data from the tcp connection into the mutable buffer `buf`.
214// The number of bytes read is limited to the length of the buffer `buf.len`.
215// The returned value is the number of read bytes (between 0 and `buf.len`).
216pub fn (c TcpConn) read(mut buf []u8) !int {
217 return c.read_ptr(buf.data, buf.len)!
218}
219
220pub fn (mut c TcpConn) read_deadline() !time.Time {
221 if c.read_deadline.unix() == 0 {
222 return c.read_deadline
223 }
224 return error('none')
225}
226
227// write_ptr blocks and attempts to write all data
228pub fn (mut c TcpConn) write_ptr(b &u8, len int) !int {
229 $if trace_tcp_sock_handle ? {
230 eprintln('>>> TcpConn.write_ptr | c: ${ptr_str(c)} | c.sock.handle: ${c.sock.handle} | b: ${ptr_str(b)} | len: ${len}')
231 }
232 $if trace_tcp ? {
233 eprintln(
234 '>>> TcpConn.write_ptr | c.sock.handle: ${c.sock.handle} | b: ${ptr_str(b)} len: ${len} |\n' +
235 unsafe { b.vstring_with_len(len) })
236 }
237 $if trace_tcp_data_write ? {
238 eprintln(
239 '>>> TcpConn.write_ptr | data.len: ${len:6} | hex: ${unsafe { b.vbytes(len) }.hex()} | data: ' +
240 unsafe { b.vstring_with_len(len) })
241 }
242 c.last_write_sent = 0
243 unsafe {
244 mut ptr_base := &u8(b)
245 mut total_sent := 0
246 for total_sent < len {
247 ptr := ptr_base + total_sent
248 remaining := len - total_sent
249 mut sent := $if is_coroutine ? {
250 C.photon_send(c.sock.handle, ptr, remaining, msg_nosignal, c.write_timeout)
251 } $else {
252 C.send(c.sock.handle, ptr, remaining, msg_nosignal)
253 }
254 code := error_code()
255 $if trace_tcp_data_write ? {
256 eprintln('>>> TcpConn.write_ptr | data chunk, total_sent: ${total_sent:6}, remaining: ${remaining:6}, ptr: ${voidptr(ptr):x} => sent: ${sent:6}')
257 }
258 if sent < 0 {
259 $if trace_tcp_send_failures ? {
260 eprintln('>>> TcpConn.write_ptr | send_failure, data.len: ${len:6}, total_sent: ${total_sent:6}, remaining: ${remaining:6}, ptr: ${voidptr(ptr):x}, c.write_timeout: ${c.write_timeout:3} => sent: ${sent:6}, error code: ${code:3}')
261 }
262 if code in [int(error_ewouldblock), int(error_eagain), C.EINTR] {
263 c.wait_for_write()!
264 continue
265 } else {
266 wrap_error(code)!
267 }
268 }
269 total_sent += sent
270 c.last_write_sent = total_sent
271 }
272 return total_sent
273 }
274}
275
276// write blocks and attempts to write all data
277pub fn (mut c TcpConn) write(bytes []u8) !int {
278 return c.write_ptr(bytes.data, bytes.len)
279}
280
281// write_string blocks and attempts to write all data
282pub fn (mut c TcpConn) write_string(s string) !int {
283 return c.write_ptr(s.str, s.len)
284}
285
286pub fn (mut c TcpConn) set_read_deadline(deadline time.Time) {
287 c.read_deadline = deadline
288}
289
290pub fn (mut c TcpConn) write_deadline() !time.Time {
291 if c.write_deadline.unix() == 0 {
292 return c.write_deadline
293 }
294 return error('none')
295}
296
297pub fn (mut c TcpConn) set_write_deadline(deadline time.Time) {
298 c.write_deadline = deadline
299}
300
301pub fn (c &TcpConn) read_timeout() time.Duration {
302 return c.read_timeout
303}
304
305pub fn (mut c TcpConn) set_read_timeout(t time.Duration) {
306 c.read_timeout = t
307}
308
309pub fn (c &TcpConn) write_timeout() time.Duration {
310 return c.write_timeout
311}
312
313pub fn (mut c TcpConn) set_write_timeout(t time.Duration) {
314 c.write_timeout = t
315}
316
317@[inline]
318pub fn (c TcpConn) wait_for_read() ! {
319 return wait_for_read(c.sock.handle, c.read_deadline, c.read_timeout)
320}
321
322@[inline]
323pub fn (mut c TcpConn) wait_for_write() ! {
324 return wait_for_write(c.sock.handle, c.write_deadline, c.write_timeout)
325}
326
327// set_sock initialises the c.sock field. It should be called after `.accept_only()!`.
328// Note: just use `.accept()!`. In most cases it is simpler, and calls `.set_sock()!` for you.
329pub fn (mut c TcpConn) set_sock() ! {
330 c.sock = tcp_socket_from_handle(c.handle)!
331 $if trace_tcp ? {
332 eprintln(' TcpListener.accept | << new_sock.handle: ${c.handle:6}')
333 }
334}
335
336// peer_addr retrieves the ip address and port number used by the peer
337pub fn (c &TcpConn) peer_addr() !Addr {
338 return peer_addr_from_socket_handle(c.sock.handle)
339}
340
341// peer_ip retrieves the ip address used by the peer, and returns it as a string
342pub fn (c &TcpConn) peer_ip() !string {
343 address := c.peer_addr()!.str()
344 if address.contains(']:') {
345 // ipv6 addresses similar to this: '[::1]:46098'
346 ip := address.all_before(']:').all_after('[')
347 return ip
348 }
349 // ipv4 addresses similar to '127.0.0.1:7346'
350 ip := address.all_before(':')
351 return ip
352}
353
354pub fn (c &TcpConn) addr() !Addr {
355 return c.sock.address()
356}
357
358pub fn (c TcpConn) str() string {
359 s := c.sock.str().replace('\n', ' ').replace(' ', ' ')
360 return 'TcpConn{ write_deadline: ${c.write_deadline}, read_deadline: ${c.read_deadline}, read_timeout: ${c.read_timeout}, write_timeout: ${c.write_timeout}, sock: ${s} }'
361}
362
363pub struct TcpListener {
364pub mut:
365 sock TcpSocket
366 accept_timeout time.Duration
367 accept_deadline time.Time
368 is_blocking bool = true
369}
370
371@[params]
372pub struct ListenOptions {
373pub:
374 dualstack bool = true
375 backlog int = 128
376}
377
378pub fn listen_tcp(family AddrFamily, saddr string, options ListenOptions) !&TcpListener {
379 if family !in [.ip, .ip6] {
380 return error('listen_tcp only supports ip and ip6')
381 }
382 return listen_tcp_with_family(family, saddr, options) or {
383 if should_fallback_to_ipv4_listener(family, saddr, options, err.code()) {
384 fallback_saddr := ipv4_fallback_listen_addr(saddr) or { return err }
385 return listen_tcp_with_family(.ip, fallback_saddr, options)
386 }
387 return err
388 }
389}
390
391fn should_fallback_to_ipv4_listener(family AddrFamily, saddr string, options ListenOptions, err_code int) bool {
392 // Treat an unspecified IPv6 listener as "dual stack if available, otherwise IPv4 only".
393 if family != .ip6 || !options.dualstack {
394 return false
395 }
396 if !is_unspecified_ip6_listen_addr(saddr) {
397 return false
398 }
399 return is_ipv6_unavailable_error(err_code)
400}
401
402fn is_unspecified_ip6_listen_addr(saddr string) bool {
403 address, _ := split_address(saddr) or { return false }
404 return address in ['', '::']
405}
406
407fn is_ipv6_unavailable_error(err_code int) bool {
408 $if windows {
409 return err_code in [int(WsaError.wsaeafnosupport), int(WsaError.wsaeprotonosupport),
410 int(WsaError.wsaeaddrnotavail)]
411 } $else {
412 return err_code in [C.EAFNOSUPPORT, C.EPROTONOSUPPORT, C.EADDRNOTAVAIL]
413 }
414}
415
416fn ipv4_fallback_listen_addr(saddr string) !string {
417 _, port := split_address(saddr)!
418 return ':${port}'
419}
420
421fn listen_tcp_with_family(family AddrFamily, saddr string, options ListenOptions) !&TcpListener {
422 mut s := new_tcp_socket(family) or { return error('${err.msg()}; could not create new socket') }
423 s.set_dualstack(options.dualstack) or {}
424
425 addrs := resolve_addrs(saddr, family, .tcp) or {
426 return error('${err.msg()}; could not resolve address ${saddr}')
427 }
428 // TODO(logic to pick here)
429 addr := addrs[0]
430
431 // cast to the correct type
432 alen := addr.len()
433 socket_error_message(C.bind(s.handle, voidptr(&addr), alen), 'binding to ${saddr} failed')!
434 mut res := C.listen(s.handle, options.backlog)
435 if res == 0 {
436 mut listener := &TcpListener{
437 sock: s
438 accept_deadline: no_deadline
439 accept_timeout: infinite_timeout
440 }
441 // The blocking / non-blocking mode is determined before the connection is established.
442 $if net_nonblocking_sockets ? {
443 listener.is_blocking = false
444 }
445 return listener
446 }
447
448 $if !net_nonblocking_sockets ? {
449 socket_error_message(res,
450 'listening on ${saddr} with maximum backlog pending queue of ${options.backlog}, failed')!
451 return &TcpListener(unsafe { nil }) // for compiler passed
452 } $else {
453 // non-blocking sockets may also not succeed immediately when they listen() and need to check the status and take action accordingly.
454 for {
455 code := error_code()
456 if code in [int(error_einprogress), int(error_ewouldblock), int(error_eagain), C.EINTR] {
457 select(s.handle, .read, connect_timeout)!
458 res = C.listen(s.handle, options.backlog)
459 if res == 0 {
460 break
461 }
462 } else {
463 socket_error_message(res,
464 'listening on ${saddr} with maximum backlog pending queue of ${options.backlog}, failed')!
465 break // for compiler passed
466 }
467 }
468 mut listener := &TcpListener{
469 sock: s
470 accept_deadline: no_deadline
471 accept_timeout: infinite_timeout
472 }
473 // The blocking / non-blocking mode is determined before the connection is established.
474 $if net_nonblocking_sockets ? {
475 listener.is_blocking = false
476 }
477 return listener
478 }
479}
480
481// accept a tcp connection from an external source to the listener `l`.
482pub fn (mut l TcpListener) accept() !&TcpConn {
483 mut res := l.accept_only()!
484 res.set_sock()!
485 return res
486}
487
488// accept_only accepts a tcp connection from an external source to the listener `l`.
489// Unlike `accept`, `accept_only` *will not call* `.set_sock()!` on the result,
490// and is thus faster.
491//
492// Note: you *need* to call `.set_sock()!` manually, before using the
493// connection after calling `.accept_only()!`, but that does not have to happen
494// in the same thread that called `.accept_only()!`.
495// The intention of this API, is to have a more efficient way to accept
496// connections, that are later processed by a thread pool, while the main
497// thread remains active, so that it can accept other connections.
498// See also vlib/veb/veb.v .
499//
500// If you do not need that, just call `.accept()!` instead, which will call
501// `.set_sock()!` for you.
502pub fn (mut l TcpListener) accept_only() !&TcpConn {
503 $if trace_tcp ? {
504 eprintln(' TcpListener.accept | l.sock.handle: ${l.sock.handle:6}')
505 }
506
507 // The blocking mode `accept()` does not support a timeout option, so `select` is used instead.
508 $if !is_coroutine ? {
509 if l.is_blocking {
510 l.wait_for_accept()!
511 }
512 }
513
514 mut new_handle := $if is_coroutine ? {
515 C.photon_accept(l.sock.handle, 0, 0, tcp_default_read_timeout)
516 } $else {
517 C.accept(l.sock.handle, 0, 0)
518 }
519 code := error_code()
520 if !l.is_blocking && new_handle <= 0 {
521 if code in [int(error_einprogress), int(error_ewouldblock), int(error_eagain), C.EINTR] {
522 l.wait_for_accept()!
523 new_handle = $if is_coroutine ? {
524 C.photon_accept(l.sock.handle, 0, 0, tcp_default_read_timeout)
525 } $else {
526 C.accept(l.sock.handle, 0, 0)
527 }
528 }
529 }
530 if new_handle <= 0 {
531 return error('accept failed')
532 }
533
534 return &TcpConn{
535 handle: new_handle
536 read_timeout: tcp_default_read_timeout
537 write_timeout: tcp_default_write_timeout
538 is_blocking: l.is_blocking
539 }
540}
541
542pub fn (c &TcpListener) accept_deadline() !time.Time {
543 if c.accept_deadline.unix() != 0 {
544 return c.accept_deadline
545 }
546 return error('invalid deadline')
547}
548
549pub fn (mut c TcpListener) set_accept_deadline(deadline time.Time) {
550 c.accept_deadline = deadline
551}
552
553pub fn (c &TcpListener) accept_timeout() time.Duration {
554 return c.accept_timeout
555}
556
557pub fn (mut c TcpListener) set_accept_timeout(t time.Duration) {
558 c.accept_timeout = t
559}
560
561pub fn (mut c TcpListener) wait_for_accept() ! {
562 return wait_for_read(c.sock.handle, c.accept_deadline, c.accept_timeout)
563}
564
565pub fn (mut c TcpListener) close() ! {
566 c.sock.close()!
567}
568
569pub fn (c &TcpListener) addr() !Addr {
570 return c.sock.address()
571}
572
573pub struct TcpSocket {
574 Socket
575}
576
577// This is a workaround for issue https://github.com/vlang/v/issues/20858
578// `noline` ensure that in `-prod` mode(CFLAG = `-O3 -flto`), gcc does not generate wrong instruction sequence
579@[noinline]
580pub fn new_tcp_socket(family AddrFamily) !TcpSocket {
581 handle := $if is_coroutine ? {
582 socket_error(C.photon_socket(i32(family), i32(SocketType.tcp), 0))!
583 } $else {
584 socket_error(C.socket(i32(family), i32(SocketType.tcp), 0))!
585 }
586 mut s := TcpSocket{
587 handle: handle
588 }
589 $if trace_tcp ? {
590 eprintln(' new_tcp_socket | s.handle: ${s.handle:6}')
591 }
592
593 // TODO(emily):
594 // we shouldn't be using ioctlsocket in the 21st century
595 // use the non-blocking socket option instead please :)
596
597 // Some options need to be set before the connection is established, otherwise they will not work.
598 s.set_default_options(family)!
599
600 // Set the desired "blocking/non-blocking" mode before the connection is established,
601 // and do not change it once the connection is successful.
602 $if net_nonblocking_sockets ? {
603 set_blocking(handle, false)!
604 }
605 return s
606}
607
608fn tcp_socket_from_handle(sockfd int) !TcpSocket {
609 mut s := TcpSocket{
610 handle: sockfd
611 }
612 $if trace_tcp ? {
613 eprintln(' tcp_socket_from_handle | s.handle: ${s.handle:6}')
614 }
615
616 s.set_dualstack(true) or {
617 // Not ipv6, we dont care
618 }
619 addr := addr_from_socket_handle(sockfd)
620 s.set_default_options(addr.family())!
621
622 return s
623}
624
625// tcp_socket_from_handle_raw is similar to tcp_socket_from_handle, but it does not modify any socket options
626pub fn tcp_socket_from_handle_raw(sockfd int) TcpSocket {
627 mut s := TcpSocket{
628 handle: sockfd
629 }
630 $if trace_tcp ? {
631 eprintln(' tcp_socket_from_handle_raw | s.handle: ${s.handle:6}')
632 }
633 return s
634}
635
636fn (mut s TcpSocket) set_option(level int, opt int, value int) ! {
637 socket_error(C.setsockopt(s.handle, level, opt, &value, sizeof(int)))!
638}
639
640pub fn (mut s TcpSocket) set_option_bool(opt SocketOption, value bool) ! {
641 // TODO: reenable when this `in` operation works again
642 // if opt !in opts_can_set {
643 // return err_option_not_settable
644 // }
645 // if opt !in opts_bool {
646 // return err_option_wrong_type
647 // }
648 x := int(value)
649 s.set_option(C.SOL_SOCKET, int(opt), x)!
650}
651
652pub fn (mut s TcpSocket) set_option_int(opt SocketOption, value int) ! {
653 s.set_option(C.SOL_SOCKET, int(opt), value)!
654}
655
656pub fn (mut s TcpSocket) set_dualstack(on bool) ! {
657 x := int(!on)
658 s.set_option(C.IPPROTO_IPV6, int(SocketOption.ipv6_only), x)!
659}
660
661fn (mut s TcpSocket) set_default_options(af AddrFamily) ! {
662 s.set_option_int(.reuse_addr, 1)!
663
664 // At the socket level to ignore the exception signal (usually SIGNPIPE).
665 // In Linux, instead of using set_option(), specify the C.MSG_NOSIGNAL flag in c.send().
666 // In Windows, there is no need to process this signal.
667 $if macos {
668 s.set_option(C.SOL_SOCKET, C.SO_NOSIGPIPE, 1)!
669 }
670
671 // Enable the NODELAY option by default.
672 if af != .unix {
673 s.set_option(C.IPPROTO_TCP, C.TCP_NODELAY, 1)!
674 }
675}
676
677// bind a local rddress for TcpSocket
678pub fn (mut s TcpSocket) bind(addr string) ! {
679 addrs := resolve_addrs(addr, AddrFamily.ip, .tcp) or {
680 return error('${err.msg()}; could not resolve address ${addr}')
681 }
682
683 // TODO(logic to pick here)
684 a := addrs[0]
685
686 // cast to the correct type
687 alen := a.len()
688 socket_error_message(C.bind(s.handle, voidptr(&a), alen), 'binding to ${addr} failed') or {
689 return err
690 }
691}
692
693fn (mut s TcpSocket) close() ! {
694 shutdown(s.handle)
695 return close(s.handle)
696}
697
698const connect_timeout = 5 * time.second
699
700fn (mut s TcpSocket) connect(a Addr) ! {
701 $if net_nonblocking_sockets ? {
702 res := $if is_coroutine ? {
703 C.photon_connect(s.handle, voidptr(&a), a.len(), tcp_default_read_timeout)
704 } $else {
705 C.connect(s.handle, voidptr(&a), a.len())
706 }
707 ecode := error_code()
708 if res == 0 {
709 return
710 }
711 // On nix non-blocking sockets we expect einprogress
712 // On windows we expect res == -1 && error_code() == ewouldblock
713 if (is_windows && ecode == int(error_ewouldblock)) || (!is_windows && res == -1
714 && ecode in [int(error_einprogress), int(error_eagain), C.EINTR]) {
715 // The socket is nonblocking and the connection cannot be completed
716 // immediately. (UNIX domain sockets failed with EAGAIN instead.)
717 // It is possible to select(2) or poll(2) for completion by selecting
718 // the socket for writing. After select(2) indicates writability,
719 // use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to
720 // determine whether connect() completed successfully (SO_ERROR is zero) or
721 // unsuccessfully (SO_ERROR is one of the usual error codes listed here,
722 // ex‐ plaining the reason for the failure).
723 write_result := select(s.handle, .write, connect_timeout)!
724 err := 0
725 len := sizeof(err)
726 xyz := C.getsockopt(s.handle, C.SOL_SOCKET, C.SO_ERROR, &err, &len)
727 if xyz == 0 && err == 0 {
728 return
729 }
730 if write_result {
731 if xyz == 0 {
732 wrap_error(err)!
733 return
734 }
735 return
736 }
737 return err_timed_out
738 }
739 wrap_error(ecode)!
740 return
741 } $else {
742 x := $if is_coroutine ? {
743 C.photon_connect(s.handle, voidptr(&a), a.len(), tcp_default_read_timeout)
744 } $else {
745 C.connect(s.handle, voidptr(&a), a.len())
746 }
747 socket_error(x)!
748 }
749}
750