net: make net.socket.write, net.socket.read_line more robust
parent
1ccd1979a4
commit
c73f34cc5f
|
@ -9,3 +9,7 @@ module net
|
|||
fn error_code() int {
|
||||
return C.errno
|
||||
}
|
||||
|
||||
pub const (
|
||||
MSG_NOSIGNAL = 0x4000
|
||||
)
|
||||
|
|
|
@ -32,3 +32,6 @@ fn error_code() int {
|
|||
return C.WSAGetLastError()
|
||||
}
|
||||
|
||||
pub const (
|
||||
MSG_NOSIGNAL = 0
|
||||
)
|
||||
|
|
|
@ -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,51 +241,58 @@ pub fn (s Socket) close() ?int {
|
|||
return 0
|
||||
}
|
||||
|
||||
const (
|
||||
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 := ''
|
||||
mut buf := [MAX_READ]byte // where C.recv will store the network data
|
||||
mut res := '' // The final result, including the ending \n.
|
||||
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')
|
||||
}
|
||||
if n == -1 {
|
||||
$if debug {
|
||||
println('recv failed')
|
||||
}
|
||||
// TODO
|
||||
return ''
|
||||
}
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
// println('resp len=$numbytes')
|
||||
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`
|
||||
// C.printf('!!buf= "%s" n=%d\n', buf,n)
|
||||
line := string(buf)
|
||||
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
|
||||
}
|
||||
}
|
||||
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
|
||||
// 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')
|
||||
// recv returned a buffer without \n in it .
|
||||
C.recv(s.sockfd, buf, n, 0)
|
||||
res += line
|
||||
res += CRLF
|
||||
break
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
|
|
|
@ -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') }
|
||||
|
||||
|
@ -25,8 +27,31 @@ fn test_socket() {
|
|||
$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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue