diff --git a/vlib/net/init_nix.v b/vlib/net/init_nix.v index 3026466eb9..7e511b4f03 100644 --- a/vlib/net/init_nix.v +++ b/vlib/net/init_nix.v @@ -9,3 +9,7 @@ module net fn error_code() int { return C.errno } + +pub const ( + MSG_NOSIGNAL = 0x4000 +) diff --git a/vlib/net/init_windows.v b/vlib/net/init_windows.v index 6f3e416f76..7c6e090a9d 100644 --- a/vlib/net/init_windows.v +++ b/vlib/net/init_windows.v @@ -32,3 +32,6 @@ fn error_code() int { return C.WSAGetLastError() } +pub const ( + MSG_NOSIGNAL = 0 +) diff --git a/vlib/net/socket.v b/vlib/net/socket.v index a9e147fc9d..7387c6c96b 100644 --- a/vlib/net/socket.v +++ b/vlib/net/socket.v @@ -188,7 +188,7 @@ pub fn dial(address string, port int) ?Socket { // send string data to socket pub fn (s Socket) send(buf byteptr, len int) ?int { - res := int( C.send(s.sockfd, buf, len, 0) ) + res := int( C.send(s.sockfd, buf, len, MSG_NOSIGNAL) ) if res < 0 { return error('net.send: failed with $res') } @@ -241,52 +241,59 @@ pub fn (s Socket) close() ?int { return 0 } -const ( - MAX_READ = 400 +pub const ( + CRLF = '\r\n' + MAX_READ = 400 + MSG_PEEK = 0x02 ) -pub fn (s Socket) write(str string) { - line := '$str\r\n' - C.send(s.sockfd, line.str, line.len, 0) + +// write - write a string with CRLF after it over the socket s +pub fn (s Socket) write(str string) ?int { + line := '$str$CRLF' + res := int( C.send(s.sockfd, line.str, line.len, MSG_NOSIGNAL) ) + if res < 0 { return error('net.write: failed with $res') } + return res } +// read_line - retrieves a line from the socket s (i.e. a string ended with \n) pub fn (s Socket) read_line() string { - mut res := '' - for { - $if debug { - println('.') - } - mut buf := malloc(MAX_READ) - n := int(C.recv(s.sockfd, buf, MAX_READ-1, 0)) - $if debug { - println('numbytes=$n') + mut buf := [MAX_READ]byte // where C.recv will store the network data + mut res := '' // The final result, including the ending \n. + for { + mut line := '' // The current line. Can be a partial without \n in it. + n := int(C.recv(s.sockfd, buf, MAX_READ-1, MSG_PEEK)) + if n == -1 { return res } + if n == 0 { return res } + buf[n] = `\0` + mut eol_idx := -1 + for i := 0; i < n; i++ { + if int(buf[i]) == `\n` { + eol_idx = i + // Ensure that tos_clone(buf) later, + // will return *only* the first line (including \n), + // and ignore the rest + buf[i+1] = `\0` + break } - if n == -1 { - $if debug { - println('recv failed') - } - // TODO - return '' - } - if n == 0 { - break - } - // println('resp len=$numbytes') - buf[n] = `\0` - // C.printf('!!buf= "%s" n=%d\n', buf,n) - line := string(buf) - res += line - // Reached a newline. That's an end of an IRC message - // TODO dont need ends_with check ? - if line.ends_with('\n') || n < MAX_READ - 1 { - // println('NL') - break - } - if line.ends_with('\r\n') { - // println('RNL') - break - } - } - return res + } + line = tos_clone(buf) + if eol_idx > 0 { + // At this point, we are sure that recv returned valid data, + // that contains *at least* one line. + // Ensure that the block till the first \n (including it) + // is removed from the socket's receive queue, so that it does + // not get read again. + C.recv(s.sockfd, buf, eol_idx+1, 0) + res += line + break + } + // recv returned a buffer without \n in it . + C.recv(s.sockfd, buf, n, 0) + res += line + res += CRLF + break + } + return res } pub fn (s Socket) get_port() int { diff --git a/vlib/net/socket_test.v b/vlib/net/socket_test.v index a3c7ce6025..4fd8fc551a 100644 --- a/vlib/net/socket_test.v +++ b/vlib/net/socket_test.v @@ -1,21 +1,23 @@ import net -fn test_socket() { - server := net.listen(0) or { - panic(err) - } +fn setup() (net.Socket, net.Socket, net.Socket) { + server := net.listen(0) or { panic(err) } server_port := server.get_port() - client := net.dial('127.0.0.1', server_port) or { - panic(err) - } - socket := server.accept() or { - panic(err) - } - + client := net.dial('127.0.0.1', server_port) or { panic(err) } + socket := server.accept() or { panic(err) } + return server, client, socket +} + +fn cleanup(server &net.Socket, client &net.Socket, socket &net.Socket) { + server.close() or {} + client.close() or {} + socket.close() or {} +} + +fn test_socket() { + server, client, socket := setup() message := 'Hello World' - socket.send(message.str, message.len) or { - assert false - } + socket.send(message.str, message.len) or { assert false } $if debug { println('message send: $message') } $if debug { println('send socket: $socket.sockfd') } @@ -23,10 +25,33 @@ fn test_socket() { received := tos(bytes, blen) $if debug { println('message received: $received') } $if debug { println('client: $client.sockfd') } - + assert message == received - - server.close() or {} - client.close() or {} - socket.close() or {} + cleanup(server, client, socket) +} + +fn test_socket_write() { + server, client, socket := setup() + message1 := 'a message 1' + socket.write(message1) or { assert false } + line1 := client.read_line() + assert line1 != message1 + assert line1.trim_space() == message1 + cleanup(server, client, socket) +} + +fn test_socket_write_fail_without_panic() { + server, client, socket := setup() + message2 := 'a message 2' + // ensure that socket.write (i.e. done on the server side) + // continues to work, even when the client side has been disconnected + // this test is important for a stable long standing server + client.close() or {} + for i:=0; i<3; i++{ + socket.write(message2) or { + println('write to a socket without a recipient should produce an option fail: $err | $message2') + assert true + } + } + cleanup(server, client, socket) } diff --git a/vlib/strings/builder_c.v b/vlib/strings/builder_c.v index 65433c8a2b..8d39d622d4 100644 --- a/vlib/strings/builder_c.v +++ b/vlib/strings/builder_c.v @@ -42,5 +42,7 @@ pub fn (b mut Builder) str() string { } pub fn (b mut Builder) free() { - //free(b.buf.data) + unsafe{ free(b.buf.data) } + b.buf = make(0, 1, 1) + b.len = 0 }