1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00
v/vlib/net/socket.v
2020-10-15 16:17:52 +03:00

390 lines
8.6 KiB
V

module net
import os
pub struct Socket {
pub:
sockfd int
family int
typ int
proto int
pub mut:
max_single_send_size int = 64000
}
struct C.in_addr {
mut:
s_addr int
}
struct C.sockaddr {
}
struct C.sockaddr_in {
mut:
sin_family int
sin_port int
sin_addr C.in_addr
}
struct C.addrinfo {
mut:
ai_family int
ai_socktype int
ai_flags int
ai_protocol int
ai_addrlen int
ai_addr voidptr
ai_canonname voidptr
ai_next voidptr
}
struct C.sockaddr_storage {
}
fn C.socket() int
fn C.setsockopt() int
fn C.htonl() int
fn C.htons() int
fn C.bind() int
fn C.listen() int
fn C.accept() int
fn C.getaddrinfo() int
fn C.connect() int
fn C.send() int
fn C.recv() int
fn C.shutdown() int
fn C.ntohs() int
fn C.getsockname() int
fn C.inet_ntop(af int, src voidptr, dst charptr, dst_size int) charptr
fn C.getpeername(sockfd int, addr &C.sockaddr_in, addrsize &int) int
// create socket
pub fn new_socket(family int, typ int, proto int) ?Socket {
sockfd := C.socket(family, typ, proto)
one := 1
// This is needed so that there are no problems with reusing the
// same port after the application exits.
C.setsockopt(sockfd, C.SOL_SOCKET, C.SO_REUSEADDR, &one, sizeof(voidptr))
if sockfd == -1 {
return error('net.socket: failed')
}
s := Socket{
sockfd: sockfd
family: family
typ: typ
proto: proto
}
return s
}
pub fn socket_udp() ?Socket {
return new_socket(C.AF_INET, C.SOCK_DGRAM, C.IPPROTO_UDP)
}
// set socket options
pub fn (s Socket) setsockopt(level int, optname int, optvalue &int) ?int {
res := C.setsockopt(s.sockfd, level, optname, optvalue, sizeof(int))
if res < 0 {
return error('net.setsocketopt: failed with $res')
}
return res
}
// bind socket to port
pub fn (s Socket) bind(port int) ?int {
mut addr := C.sockaddr_in{}
addr.sin_family = s.family
addr.sin_port = C.htons(port)
addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY)
size := 16 // sizeof(C.sockaddr_in)
tmp := voidptr(&addr)
skaddr := &C.sockaddr(tmp)
res := C.bind(s.sockfd, skaddr, size)
if res < 0 {
return error('net.bind: failed with $res')
}
return res
}
// put socket into passive mode and wait to receive
pub fn (s Socket) listen() ?int {
backlog := 128
res := C.listen(s.sockfd, backlog)
if res < 0 {
return error('net.listen: failed with $res')
}
$if debug {
println('listen res = $res')
}
return res
}
// put socket into passive mode with user specified backlog and wait to receive
pub fn (s Socket) listen_backlog(backlog int) ?int {
mut n := 0
if backlog > 0 {
n = backlog
}
res := C.listen(s.sockfd, n)
if res < 0 {
return error('net.listen_backlog: failed with $res')
}
return res
}
// helper method to create, bind, and listen given port number
pub fn listen(port int) ?Socket {
$if debug {
println('net.listen($port)')
}
s := new_socket(C.AF_INET, C.SOCK_STREAM, 0) ?
s.bind(port) ?
s.listen() ?
return s
}
// accept first connection request from socket queue
pub fn (s Socket) accept() ?Socket {
$if debug {
println('accept()')
}
addr := C.sockaddr{}
size := sizeof(addr)
sockfd := C.accept(s.sockfd, &addr, &size)
if sockfd < 0 {
return error('net.accept: failed with $sockfd')
}
c := Socket{
sockfd: sockfd
family: s.family
typ: s.typ
proto: s.proto
}
return c
}
pub fn (s Socket) peer_ip() ?string {
buf := [44]byte{}
peeraddr := C.sockaddr_in{}
speeraddr := sizeof(peeraddr)
ok := C.getpeername(s.sockfd, &C.sockaddr(&peeraddr), &speeraddr)
if ok == -1 {
return error('net.peer_ip: getpeername failed')
}
cstr := C.inet_ntop(C.AF_INET, &peeraddr.sin_addr, buf, sizeof(buf))
if cstr == 0 {
return error('net.peer_ip: inet_ntop failed')
}
return cstring_to_vstring(cstr)
}
// connect to given addrress and port
pub fn (s Socket) connect(address string, port int) ?int {
mut hints := C.addrinfo{}
hints.ai_family = s.family
hints.ai_socktype = s.typ
hints.ai_flags = C.AI_PASSIVE
hints.ai_protocol = s.proto
hints.ai_addrlen = 0
hints.ai_canonname = C.NULL
hints.ai_addr = C.NULL
hints.ai_next = C.NULL
info := &C.addrinfo(0)
sport := '$port'
info_res := C.getaddrinfo(address.str, sport.str, &hints, &info)
if info_res != 0 {
error_message := os.get_error_msg(error_code())
return error('net.connect: getaddrinfo failed "$error_message"')
}
res := C.connect(s.sockfd, info.ai_addr, info.ai_addrlen)
if res < 0 {
error_message := os.get_error_msg(error_code())
return error('net.connect: connect failed "$error_message"')
}
return res
}
// helper method to create socket and connect
pub fn dial(address string, port int) ?Socket {
s := new_socket(C.AF_INET, C.SOCK_STREAM, 0) ?
s.connect(address, port) ?
return s
}
// send data to socket (when you have a memory buffer)
pub fn (s Socket) send(buf byteptr, len int) ?int {
mut dptr := buf
mut dlen := len
for {
send_size := if dlen > s.max_single_send_size { s.max_single_send_size } else { dlen }
sbytes := C.send(s.sockfd, dptr, send_size, msg_nosignal)
if sbytes < 0 {
return error('net.send: failed with $sbytes')
}
dlen -= sbytes
if dlen <= 0 {
break
}
unsafe {
dptr += sbytes
}
}
return len
}
// send string data to socket (when you have a v string)
pub fn (s Socket) send_string(sdata string) ?int {
return s.send(sdata.str, sdata.len)
}
// receive string data from socket. NB: you are responsible for freeing the returned byteptr
pub fn (s Socket) recv(bufsize int) (byteptr, int) {
mut buf := byteptr(0)
unsafe {
buf = malloc(bufsize)
}
res := C.recv(s.sockfd, buf, bufsize, 0)
return buf, res
}
// TODO: remove cread/2 and crecv/2 when the Go net interface is done
pub fn (s Socket) cread(buffer byteptr, buffersize int) int {
return C.read(s.sockfd, buffer, buffersize)
}
// Receive a message from the socket, and place it in a preallocated buffer buf,
// with maximum message size bufsize. Returns the length of the received message.
pub fn (s Socket) crecv(buffer voidptr, buffersize int) int {
return C.recv(s.sockfd, byteptr(buffer), buffersize, 0)
}
// shutdown and close socket
pub fn (s Socket) close() ?int {
mut shutdown_res := 0
$if windows {
shutdown_res = C.shutdown(s.sockfd, C.SD_BOTH)
} $else {
shutdown_res = C.shutdown(s.sockfd, C.SHUT_RDWR)
}
// TODO: should shutdown throw an error? close will
// continue even if shutdown failed
// if shutdown_res < 0 {
// return error('net.close: shutdown failed with $shutdown_res')
// }
mut res := 0
$if windows {
res = C.closesocket(s.sockfd)
} $else {
res = C.close(s.sockfd)
}
if res < 0 {
return error('net.close: failed with $res')
}
return 0
}
pub const (
crlf = '\r\n'
max_read = 400
msg_peek = 0x02
)
// write - write a string with CRLF after it over the socket s
pub fn (s Socket) write(str string) ?int {
line := '$str$crlf'
res := 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 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 := 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 in 0 .. n {
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
}
}
bufbp := byteptr(buf)
line = tos_clone(bufbp)
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
}
// TODO
pub fn (s Socket) read_all() string {
mut buf := [max_read]byte{} // where C.recv will store the network data
mut res := '' // The final result, including the ending \n.
for {
n := C.recv(s.sockfd, buf, max_read - 1, 0)
if n == -1 {
return res
}
if n == 0 {
return res
}
bufbp := byteptr(buf)
res += tos_clone(bufbp)
}
return res
}
pub fn (s Socket) get_port() int {
mut addr := C.sockaddr_in{}
size := 16 // sizeof(sockaddr_in)
tmp := voidptr(&addr)
skaddr := &C.sockaddr(tmp)
C.getsockname(s.sockfd, skaddr, &size)
return C.ntohs(addr.sin_port)
}