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

323 lines
7.2 KiB
V
Raw Normal View History

2019-06-30 17:11:55 +03:00
module net
2019-10-10 20:24:36 +03:00
import os
2019-10-24 19:44:49 +03:00
pub struct Socket {
2019-07-01 15:55:45 +03:00
pub:
2019-06-30 17:11:55 +03:00
sockfd int
2019-07-01 15:55:45 +03:00
family int
_type int
proto int
2019-06-30 17:11:55 +03:00
}
2019-07-20 06:51:45 +03:00
2019-07-01 15:55:45 +03:00
struct C.in_addr {
mut:
s_addr int
2019-06-30 17:11:55 +03:00
}
2019-07-01 15:55:45 +03:00
struct C.sockaddr_in {
mut:
sin_family int
sin_port int
sin_addr C.in_addr
}
2019-06-30 17:11:55 +03:00
struct C.addrinfo {
2019-07-01 15:55:45 +03:00
mut:
ai_family int
ai_socktype int
ai_flags int
ai_protocol int
ai_addrlen int
2019-07-01 15:55:45 +03:00
ai_addr voidptr
ai_canonname voidptr
ai_next voidptr
2019-07-01 15:55:45 +03:00
}
2019-06-30 17:11:55 +03:00
2019-07-01 15:55:45 +03:00
struct C.sockaddr_storage {}
2019-11-24 06:27:02 +03:00
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.read() int
fn C.shutdown() int
fn C.close() int
fn C.ntohs() int
fn C.getsockname() int
2019-06-30 21:57:25 +03:00
2019-07-01 15:55:45 +03:00
// create socket
2019-11-24 06:27:02 +03:00
pub fn new_socket(family int, _type int, proto int) ?Socket {
2019-07-14 00:29:00 +03:00
2019-07-01 15:55:45 +03:00
sockfd := C.socket(family, _type, proto)
2019-08-23 00:00:31 +03:00
one:=1
// This is needed so that there are no problems with reusing the
// same port after the application exits.
2019-11-25 06:23:09 +03:00
C.setsockopt(sockfd, C.SOL_SOCKET, C.SO_REUSEADDR, &one, sizeof(int))
2019-07-20 06:51:45 +03:00
if sockfd == 0 {
return error('net.socket: failed')
2019-07-20 06:51:45 +03:00
}
2019-07-01 15:55:45 +03:00
s := Socket {
sockfd: sockfd
family: family
_type: _type
proto: proto
2019-06-30 17:11:55 +03:00
}
2019-07-01 15:55:45 +03:00
return s
}
pub fn socket_udp() ?Socket {
2019-11-24 06:27:02 +03:00
return new_socket(C.AF_INET, C.SOCK_DGRAM, C.IPPROTO_UDP)
}
2019-07-01 15:55:45 +03:00
// set socket options
2019-09-03 19:10:56 +03:00
pub fn (s Socket) setsockopt(level int, optname int, optvalue &int) ?int {
2019-11-25 06:23:09 +03:00
res := C.setsockopt(s.sockfd, level, optname, optvalue, sizeof(&int))
2019-07-20 06:51:45 +03:00
if res < 0 {
return error('net.setsocketopt: failed with $res')
2019-07-20 06:51:45 +03:00
}
2019-11-24 06:27:02 +03:00
return res
2019-07-01 15:55:45 +03:00
}
// bind socket to port
2019-07-20 06:51:45 +03:00
pub fn (s Socket) bind(port int) ?int {
2019-07-01 15:55:45 +03:00
mut addr := C.sockaddr_in{}
addr.sin_family = s.family
addr.sin_port = C.htons(port)
2019-08-23 00:00:31 +03:00
addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY)
2019-07-01 15:55:45 +03:00
size := 16 // sizeof(C.sockaddr_in)
2019-11-24 06:27:02 +03:00
res := C.bind(s.sockfd, &addr, size)
2019-07-20 06:51:45 +03:00
if res < 0 {
return error('net.bind: failed with $res')
2019-07-20 06:51:45 +03:00
}
return res
2019-07-01 15:55:45 +03:00
}
// put socket into passive mode and wait to receive
2019-07-20 06:51:45 +03:00
pub fn (s Socket) listen() ?int {
2019-07-01 15:55:45 +03:00
backlog := 128
2019-11-24 06:27:02 +03:00
res := C.listen(s.sockfd, backlog)
2019-07-20 06:51:45 +03:00
if res < 0 {
return error('net.listen: failed with $res')
2019-07-20 06:51:45 +03:00
}
2019-08-20 11:18:12 +03:00
$if debug {
println('listen res = $res')
}
2019-08-23 00:00:31 +03:00
return res
2019-07-01 15:55:45 +03:00
}
// put socket into passive mode with user specified backlog and wait to receive
2019-07-20 06:51:45 +03:00
pub fn (s Socket) listen_backlog(backlog int) ?int {
mut n := 0
if backlog > 0 {
n = backlog
}
res := C.listen(s.sockfd, n)
2019-07-20 06:51:45 +03:00
if res < 0 {
return error('net.listen_backlog: failed with $res')
2019-07-20 06:51:45 +03:00
}
2019-11-24 06:27:02 +03:00
return res
}
2019-07-01 15:55:45 +03:00
// helper method to create, bind, and listen given port number
2019-07-20 06:51:45 +03:00
pub fn listen(port int) ?Socket {
2019-08-20 11:18:12 +03:00
$if debug {
println('net.listen($port)')
}
2019-11-24 06:27:02 +03:00
s := new_socket(C.AF_INET, C.SOCK_STREAM, 0) or {
2019-07-20 06:51:45 +03:00
return error(err)
}
bind_res := s.bind(port) or {
return error(err)
}
listen_res := s.listen() or {
return error(err)
2019-06-30 17:11:55 +03:00
}
2019-07-01 15:55:45 +03:00
return s
2019-06-30 17:11:55 +03:00
}
2019-07-01 15:55:45 +03:00
// accept first connection request from socket queue
2019-07-20 06:51:45 +03:00
pub fn (s Socket) accept() ?Socket {
2019-08-20 11:18:12 +03:00
$if debug {
println('accept()')
}
2019-07-01 15:55:45 +03:00
addr := C.sockaddr_storage{}
size := 128 // sizeof(sockaddr_storage)
sockfd := C.accept(s.sockfd, &addr, &size)
if sockfd < 0 {
return error('net.accept: failed with $sockfd')
2019-06-30 22:00:22 +03:00
}
2019-07-01 15:55:45 +03:00
c := Socket {
sockfd: sockfd
family: s.family
_type: s._type
proto: s.proto
2019-06-30 22:00:22 +03:00
}
2019-07-01 15:55:45 +03:00
return c
}
// connect to given addrress and port
2019-07-20 06:51:45 +03:00
pub fn (s Socket) connect(address string, port int) ?int {
2019-07-01 15:55:45 +03:00
mut hints := C.addrinfo{}
hints.ai_family = s.family
hints.ai_socktype = s._type
2019-08-23 00:00:31 +03:00
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
2019-07-01 15:55:45 +03:00
info := &C.addrinfo{!}
sport := '$port'
info_res := C.getaddrinfo(address.str, sport.str, &hints, &info)
2019-07-01 15:55:45 +03:00
if info_res != 0 {
2019-10-10 20:24:36 +03:00
error_message := os.get_error_msg(net.error_code())
return error('net.connect: getaddrinfo failed "$error_message"')
2019-06-30 22:00:22 +03:00
}
2019-11-24 06:27:02 +03:00
res := C.connect(s.sockfd, info.ai_addr, info.ai_addrlen)
2019-07-20 06:51:45 +03:00
if res < 0 {
2019-10-10 20:24:36 +03:00
error_message := os.get_error_msg(net.error_code())
return error('net.connect: connect failed "$error_message"')
2019-07-20 06:51:45 +03:00
}
2019-11-24 06:27:02 +03:00
return res
2019-06-30 21:57:25 +03:00
}
2019-07-01 15:55:45 +03:00
// helper method to create socket and connect
2019-07-20 06:51:45 +03:00
pub fn dial(address string, port int) ?Socket {
2019-11-24 06:27:02 +03:00
s := new_socket(C.AF_INET, C.SOCK_STREAM, 0) or {
2019-07-20 06:51:45 +03:00
return error(err)
}
res := s.connect(address, port) or {
return error(err)
2019-07-01 15:55:45 +03:00
}
return s
2019-06-30 21:57:25 +03:00
}
2019-07-02 18:25:21 +03:00
// send string data to socket
pub fn (s Socket) send(buf byteptr, len int) ?int {
2019-11-24 06:27:02 +03:00
res := C.send(s.sockfd, buf, len, MSG_NOSIGNAL)
if res < 0 {
return error('net.send: failed with $res')
}
2019-07-02 18:25:21 +03:00
return res
}
// receive string data from socket
pub fn (s Socket) recv(bufsize int) (byteptr, int) {
2019-07-02 18:25:21 +03:00
buf := malloc(bufsize)
2019-11-24 06:27:02 +03:00
res := C.recv(s.sockfd, buf, bufsize, 0)
return buf, res
2019-07-02 18:25:21 +03:00
}
2019-08-21 20:04:06 +03:00
// TODO: remove cread/2 and crecv/2 when the Go net interface is done
pub fn (s Socket) cread( buffer byteptr, buffersize int ) int {
2019-11-24 06:27:02 +03:00
return C.read(s.sockfd, buffer, buffersize)
2019-08-21 20:04:06 +03:00
}
// 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.
2019-08-21 20:04:06 +03:00
pub fn (s Socket) crecv( buffer byteptr, buffersize int ) int {
2019-11-24 06:27:02 +03:00
return C.recv(s.sockfd, buffer, buffersize, 0)
2019-08-21 20:04:06 +03:00
}
2019-07-01 15:55:45 +03:00
// shutdown and close socket
2019-07-20 06:51:45 +03:00
pub fn (s Socket) close() ?int {
mut shutdown_res := 0
2019-07-14 00:29:00 +03:00
$if windows {
2019-08-23 00:00:31 +03:00
shutdown_res = C.shutdown(s.sockfd, C.SD_BOTH)
2019-07-14 00:29:00 +03:00
}
$else {
2019-08-23 00:00:31 +03:00
shutdown_res = C.shutdown(s.sockfd, C.SHUT_RDWR)
2019-07-14 00:29:00 +03:00
}
2019-07-20 06:51:45 +03:00
// 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')
2019-07-20 06:51:45 +03:00
// }
2019-07-14 00:29:00 +03:00
2019-07-20 06:51:45 +03:00
mut res := 0
2019-07-14 00:29:00 +03:00
$if windows {
2019-07-20 06:51:45 +03:00
res = C.closesocket(s.sockfd)
2019-07-01 15:55:45 +03:00
}
2019-07-14 00:29:00 +03:00
$else {
2019-07-20 06:51:45 +03:00
res = C.close(s.sockfd)
}
if res < 0 {
return error('net.close: failed with $res')
2019-07-14 00:29:00 +03:00
}
2019-07-01 15:55:45 +03:00
return 0
}
2019-07-29 19:21:36 +03:00
pub const (
CRLF = '\r\n'
MAX_READ = 400
MSG_PEEK = 0x02
2019-08-23 00:00:31 +03:00
)
// write - write a string with CRLF after it over the socket s
pub fn (s Socket) write(str string) ?int {
line := '$str$CRLF'
2019-11-24 06:27:02 +03:00
res := C.send(s.sockfd, line.str, line.len, MSG_NOSIGNAL)
if res < 0 { return error('net.write: failed with $res') }
return res
2019-07-29 19:21:36 +03:00
}
2019-08-23 00:00:31 +03:00
// read_line - retrieves a line from the socket s (i.e. a string ended with \n)
2019-07-29 19:21:36 +03:00
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 := 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
2019-08-20 11:18:12 +03:00
}
}
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
2019-07-29 19:21:36 +03:00
}
pub fn (s Socket) get_port() int {
mut addr := C.sockaddr_in {}
size := 16 // sizeof(sockaddr_in)
2019-11-24 06:27:02 +03:00
C.getsockname(s.sockfd, &addr, &size)
return C.ntohs(addr.sin_port)
}
2019-11-26 13:54:41 +03:00