This commit is contained in:
ge
2026-04-13 04:02:42 +03:00
parent 2d2281b8cc
commit 7b17a4a33b
9 changed files with 253 additions and 73 deletions
-4
View File
@@ -1,12 +1,8 @@
# Networking Library for V # Networking Library for V
**netio** is a flexible networking library for V programming language. **netio** is a flexible networking library for V programming language.
It heavily relies on the libc.
Differences with the V standard library `net` module: Differences with the V standard library `net` module:
* Provides the low-level wrappers around C API. * Provides the low-level wrappers around C API.
* Supports any kind of sockets, socket options, address families and protocols. * Supports any kind of sockets, socket options, address families and protocols.
**netio** also provides a high-level abstractions over BSD sockets: `TCPListener`,
`TCPStream`, `UDPSocket`.
+22
View File
@@ -216,6 +216,28 @@ pub const ip_ttl = SocketOption(C.IP_TTL)
pub const ip_unblock_source = SocketOption(C.IP_UNBLOCK_SOURCE) pub const ip_unblock_source = SocketOption(C.IP_UNBLOCK_SOURCE)
pub const ip_unicast_if = SocketOption(C.IP_UNICAST_IF) pub const ip_unicast_if = SocketOption(C.IP_UNICAST_IF)
pub const ip_xfrm_policy = SocketOption(C.IP_XFRM_POLICY) pub const ip_xfrm_policy = SocketOption(C.IP_XFRM_POLICY)
pub const msg_batch = MsgFlag(C.MSG_BATCH)
pub const msg_cmsg_cloexec = MsgFlag(C.MSG_CMSG_CLOEXEC)
pub const msg_confirm = MsgFlag(C.MSG_CONFIRM)
pub const msg_ctrunc = MsgFlag(C.MSG_CTRUNC)
pub const msg_dontroute = MsgFlag(C.MSG_DONTROUTE)
pub const msg_dontwait = MsgFlag(C.MSG_DONTWAIT)
pub const msg_eor = MsgFlag(C.MSG_EOR)
pub const msg_errqueue = MsgFlag(C.MSG_ERRQUEUE)
pub const msg_fastopen = MsgFlag(C.MSG_FASTOPEN)
pub const msg_fin = MsgFlag(C.MSG_FIN)
pub const msg_more = MsgFlag(C.MSG_MORE)
pub const msg_nosignal = MsgFlag(C.MSG_NOSIGNAL)
pub const msg_oob = MsgFlag(C.MSG_OOB)
pub const msg_peek = MsgFlag(C.MSG_PEEK)
pub const msg_proxy = MsgFlag(C.MSG_PROXY)
pub const msg_rst = MsgFlag(C.MSG_RST)
pub const msg_sock_devmem = MsgFlag(C.MSG_SOCK_DEVMEM)
pub const msg_syn = MsgFlag(C.MSG_SYN)
pub const msg_trunc = MsgFlag(C.MSG_TRUNC)
pub const msg_waitall = MsgFlag(C.MSG_WAITALL)
pub const msg_waitforone = MsgFlag(C.MSG_WAITFORONE)
pub const msg_zerocopy = MsgFlag(C.MSG_ZEROCOPY)
pub const ni_dgram = NameInfoFlag(C.NI_DGRAM) pub const ni_dgram = NameInfoFlag(C.NI_DGRAM)
pub const ni_maxhost = NameInfoFlag(C.NI_MAXHOST) pub const ni_maxhost = NameInfoFlag(C.NI_MAXHOST)
pub const ni_maxserv = NameInfoFlag(C.NI_MAXSERV) pub const ni_maxserv = NameInfoFlag(C.NI_MAXSERV)
-47
View File
@@ -1,47 +0,0 @@
import netio
fn main() {
ai := netio.translate_addr(
service: '1081'
sock_type: netio.sock_stream
family: netio.af_inet6
flags: netio.ai_passive
)!
mut socket := netio.Socket{}
mut listen_addr := netio.SocketAddr{}
for a in ai {
socket = netio.Socket.new(a.family, a.sock_type, a.protocol)!
socket.set_option(netio.ipproto_ipv6, netio.ipv6_v6only, 0)!
socket.bind(a.addr) or {
socket.close()!
continue
}
listen_addr = a.addr
break
}
defer {
socket.close() or { panic(err) }
}
socket.listen(10) or {
eprintln('LISTEN: ${err}')
exit(1)
}
println('Listening on ${listen_addr}...')
for {
conn, remote_addr := socket.accept() or {
eprintln('ACCEPT: ${err}')
exit(1)
}
eprintln(netio.translate_name(remote_addr,
flags: netio.ni_numerichost | netio.ni_numericserv
)!)
eprintln(netio.translate_name(remote_addr)!)
eprintln('Remote address: ${remote_addr}')
conn.close()!
}
}
+42
View File
@@ -0,0 +1,42 @@
import netio
fn main() {
// Create new TCP socket.
mut socket := netio.Socket.new(netio.af_inet, netio.sock_stream, 0)!
defer {
socket.close() or { panic(err) }
}
// Create the server socket address.
server_addr := netio.SocketAddr.ipv4([u8(127), 0, 0, 1]!, 1081)
// Connect socket to the server address.
socket.connect(server_addr) or {
eprintln('CONNECT: ${err}')
exit(1)
}
eprintln('Connected to server ${server_addr}...')
// Send message to the server.
msg := 'Hello from client!'
sent := socket.send(msg.bytes(), 0)!
eprintln('Sent to the server: ${sent} bytes, data: ${msg}')
// Read the server reply.
mut buf := []u8{len: 512}
read := socket.recv(mut buf, 0) or {
eprintln('RECV: ${err}')
exit(1)
}
if read > 0 {
eprintln('Received from server: ${read} bytes, data: ${buf.bytestr()}')
} else if read == 0 {
eprintln('Server closed the connection.')
}
}
+99
View File
@@ -0,0 +1,99 @@
import netio
fn main() {
// We want to bind a server socket to the all available local addresses,
// (both IPv4 and IPv6) so collect the address info entries for it.
ai := netio.translate_addr(
service: '1081' // The port number for listen.
sock_type: netio.sock_stream // Address must support TCP transport.
family: netio.af_inet6 // IPv6 support.
flags: netio.ai_passive // Passive mode for binding to any address (0.0.0.0, ::).
)!
// Just initialize variables.
mut socket := netio.Socket{}
mut listen_addr := netio.SocketAddr{}
// Create socket and bind to the first available address.
for a in ai {
// Create a socket with advertised parameters.
socket = netio.Socket.new(a.family, a.sock_type, a.protocol)!
// Set SO_REUSEADDR enabled. It allows a server to bind to a port that
// is still in a `TIME-WAIT` state from a previous connection.
// https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Protocol_operation
socket.set_option(netio.sol_socket, netio.so_reuseaddr, 1)!
// Allow connections through IPv4, not only IPv6.
socket.set_option(netio.ipproto_ipv6, netio.ipv6_v6only, 0)!
// Bind socket to the address.
socket.bind(a.addr) or {
// Close previously created socket on bind error and continue with
// the next socket address.
socket.close()!
continue
}
// Set listen_addr.
listen_addr = a.addr
break
}
// If the socket.fd is -1 this means that we does not find any socket address.
if socket.fd == -1 {
eprintln('Cannot create socket...')
exit(1)
}
// Close the server socket on exit.
defer {
socket.close() or { panic(err) }
}
// Start listening for incoming connections on socket.
socket.listen(10) or {
eprintln('LISTEN: ${err}')
exit(1)
}
println('Listening on ${listen_addr}...')
// Accept the connection from remote. This is a blocking call.
// conn will store the new socket connected to the remote.
conn, remote_addr := socket.accept() or {
eprintln('ACCEPT: ${err}')
exit(1)
}
// Close connection on exit.
defer {
conn.close() or { panic(err) }
}
// Get remote host and port in numeric format.
remote_host, remote_port := netio.translate_name(remote_addr,
flags: netio.ni_numerichost | netio.ni_numericserv
)!
eprintln('Accpeted connection. Remote address: ${remote_host}, remote port: ${remote_port}')
// Read 512 bytes of data from socket.
mut buf := []u8{len: 512} // Initialize the buffer to store message.
// Receive data and write it to the buffer.
read := conn.recv(mut buf, 0) or {
eprintln('RECV: ${err}')
exit(1)
}
// Create a string from buffer without the trailing zeros.
msg := unsafe { tos_clone(buf.data) }
eprintln('Received from client: ${read} bytes, data: ${msg}')
// Send reply to the client.
sent := conn.send(msg.bytes(), 0) or {
eprintln('SEND: ${err}')
exit(1)
}
eprintln('Sent to the client: ${sent} bytes, data: ${msg}')
}
+2 -1
View File
@@ -21,7 +21,7 @@ case $system in
esac esac
KIND=$1 KIND=$1
KIND=${KIND:-"SocketType,SocketLevel,SocketOption,AddrFamily,AddrInfoFlag,NameInfoFlag"} KIND=${KIND:-"SocketType,SocketLevel,SocketOption,AddrFamily,AddrInfoFlag,NameInfoFlag,MsgFlag"}
echo module netio echo module netio
echo echo
@@ -41,5 +41,6 @@ awk -v KIND=$KIND '
$2 ~ /^AF_/ && KIND ~ "AddrFamily" {printf "pub const %s = AddrFamily(C.%s)\n", tolower($2), $2} $2 ~ /^AF_/ && KIND ~ "AddrFamily" {printf "pub const %s = AddrFamily(C.%s)\n", tolower($2), $2}
$2 ~ /^AI_/ && KIND ~ "AddrInfoFlag" {printf "pub const %s = AddrInfoFlag(C.%s)\n", tolower($2), $2} $2 ~ /^AI_/ && KIND ~ "AddrInfoFlag" {printf "pub const %s = AddrInfoFlag(C.%s)\n", tolower($2), $2}
$2 ~ /^NI_/ && KIND ~ "NameInfoFlag" {printf "pub const %s = NameInfoFlag(C.%s)\n", tolower($2), $2} $2 ~ /^NI_/ && KIND ~ "NameInfoFlag" {printf "pub const %s = NameInfoFlag(C.%s)\n", tolower($2), $2}
$2 ~ /^MSG_/ && KIND ~ "MsgFlag" {printf "pub const %s = MsgFlag(C.%s)\n", tolower($2), $2}
KIND ~ "Any" {printf "%s\n", $0} KIND ~ "Any" {printf "%s\n", $0}
{next}' | sort -k 2 -V {next}' | sort -k 2 -V
+3
View File
@@ -24,3 +24,6 @@ pub type AddrInfoFlag = int
// Flag type for `translate_name()`. // Flag type for `translate_name()`.
// See [getnameinfo(3)](https://man7.org/linux/man-pages/man3/getnameinfo.3.html) for details. // See [getnameinfo(3)](https://man7.org/linux/man-pages/man3/getnameinfo.3.html) for details.
pub type NameInfoFlag = int pub type NameInfoFlag = int
// Type for recv, recvfrom, recvmsg, send, sendto, sendmsg flags.
pub type MsgFlag = int
+42 -2
View File
@@ -14,8 +14,10 @@ fn C.shutdown(i32, i32) i32
fn C.close(i32) i32 fn C.close(i32) i32
fn C.setsockopt(i32, i32, i32, voidptr, i32) i32 fn C.setsockopt(i32, i32, i32, voidptr, i32) i32
fn C.getsockopt(i32, i32, i32, voidptr, voidptr) i32 fn C.getsockopt(i32, i32, i32, voidptr, voidptr) i32
fn C.recv(i32, voidptr, usize, i32) i32
struct C.sockaddr_storage {} fn C.recvfrom(i32, voidptr, usize, i32, voidptr, i32) i32
fn C.send(i32, voidptr, usize, i32) i32
fn C.sendto(i32, voidptr, usize, i32, voidptr, i32) i32
pub struct Socket { pub struct Socket {
pub: pub:
@@ -111,6 +113,44 @@ pub fn (s Socket) get_option_int(level SocketLevel, option SocketOption) !int {
return result return result
} }
// receive a message from a connected socket.
pub fn (s Socket) recv(mut buf []u8, flags MsgFlag) !int {
r := C.recv(s.fd, buf.data, buf.len, flags)
if r == -1 {
return os.last_error()
}
return r
}
// receive a message from a connected socket.
pub fn (s Socket) recv_from(mut buf []u8, flags MsgFlag) !(int, SocketAddr) {
mut sock_addr_storage := &C.sockaddr_storage{}
mut sock_addr_len := sizeof(C.sockaddr_storage)
r := C.recvfrom(s.fd, buf.data, buf.len, flags, sock_addr_storage, sock_addr_len)
if r == -1 {
return os.last_error()
}
return r, unsafe { SocketAddr.from_ptr(sock_addr_storage, sock_addr_len)! }
}
// send a message on socket.
pub fn (s Socket) send(buf []u8, flags MsgFlag) !int {
r := C.send(s.fd, buf.data, buf.len, flags)
if r == -1 {
return os.last_error()
}
return r
}
// send a message on socket using the dst socket address as destination instead of the socket peer address.
pub fn (s Socket) send_to(buf []u8, dst SocketAddr, flags MsgFlag) !int {
r := C.sendto(s.fd, buf.data, buf.len, flags, dst.ptr(), dst.size())
if r == -1 {
return os.last_error()
}
return r
}
// shutdown shut downs socket send and receive operations. // shutdown shut downs socket send and receive operations.
pub fn (s Socket) shutdown(how Shutdown) ! { pub fn (s Socket) shutdown(how Shutdown) ! {
if C.shutdown(s.fd, i32(how)) == -1 { if C.shutdown(s.fd, i32(how)) == -1 {
+43 -19
View File
@@ -2,6 +2,8 @@ module netio
import encoding.binary import encoding.binary
struct C.sockaddr_storage {}
// max_unix_path_size value is used to pad the sockaddr_un struct. // max_unix_path_size value is used to pad the sockaddr_un struct.
const max_unix_path_size = $if linux { const max_unix_path_size = $if linux {
108 108
@@ -11,6 +13,13 @@ const max_unix_path_size = $if linux {
104 104
} }
pub struct SocketAddr {
mut:
data &u8 = unsafe { nil }
len int
pos int
}
// SocketAddr.ipv4 creates new AF_INET socket address. // SocketAddr.ipv4 creates new AF_INET socket address.
// addr must be set in network (big-endian) byte order. // addr must be set in network (big-endian) byte order.
pub fn SocketAddr.ipv4(addr [4]u8, port u16) SocketAddr { pub fn SocketAddr.ipv4(addr [4]u8, port u16) SocketAddr {
@@ -54,14 +63,14 @@ pub fn SocketAddr.unix(path string) !SocketAddr {
// the address itself, you need to do that manually. The benefit is that you can create the // the address itself, you need to do that manually. The benefit is that you can create the
// any kind of socket address. // any kind of socket address.
// //
// SocketAddr is a "constructor" for // SocketAddr is a builder for
// [sockaddr(3type)](https://www.man7.org/linux/man-pages/man3/sockaddr.3type.html) objects. // [sockaddr(3type)](https://www.man7.org/linux/man-pages/man3/sockaddr.3type.html) objects.
// Use this function only if you understand what you do. You must manually write the data // Use this function only if you understand what you do. Using the `push()` method you must
// for the desired socket address, ensuring the correct sizes of all types, the order of // write the data for the desired socket address, ensuring the correct sizes of all types,
// the fields in the struct, the byte order, and the total size of the struct. The sizes // the order of the fields in the struct, the byte order, and the total size of the struct.
// and byte order may vary by platform, so you'll need to keep an eye on that as well. // The sizes and byte order may vary by platform, so you'll need to keep an eye on that as
// A mistake while creating an address will crash your application. So this function is // well. A mistake while creating an address will crash your application. So this function
// marked as `unsafe`. // is marked as `unsafe`.
// //
// The example below creates a sockaddr_in struct describing the loopback IPv4-address // The example below creates a sockaddr_in struct describing the loopback IPv4-address
// 127.0.0.1 with port number 1080. Note the comment in the example. This is a fragment // 127.0.0.1 with port number 1080. Note the comment in the example. This is a fragment
@@ -118,6 +127,9 @@ pub fn SocketAddr.new(af AddrFamily, size isize) SocketAddr {
// SocketAddr.from_ptr creates new socket address by copying data from specified pointer. // SocketAddr.from_ptr creates new socket address by copying data from specified pointer.
@[unsafe] @[unsafe]
pub fn SocketAddr.from_ptr(ptr voidptr, size isize) !SocketAddr { pub fn SocketAddr.from_ptr(ptr voidptr, size isize) !SocketAddr {
if isnil(ptr) {
return error('${@METHOD}: cannot accept nil ptr')
}
data := unsafe { vcalloc(usize(size)) } data := unsafe { vcalloc(usize(size)) }
unsafe { unsafe {
vmemcpy(data, ptr, size) vmemcpy(data, ptr, size)
@@ -128,25 +140,37 @@ pub fn SocketAddr.from_ptr(ptr voidptr, size isize) !SocketAddr {
} }
} }
pub struct SocketAddr {
mut:
data &u8 = unsafe { nil }
len int
pos int
}
// family returns the socket address family. // family returns the socket address family.
// Note: It returns 0 if socket address is nil, see also `is_empty()`.
pub fn (a SocketAddr) family() AddrFamily { pub fn (a SocketAddr) family() AddrFamily {
mut f := AddrFamily(0) if isnil(a.data) {
return 0
}
mut f := 0
unsafe { vmemcpy(&f, a.data, isize(2)) } unsafe { vmemcpy(&f, a.data, isize(2)) }
return f return f
} }
// is_empty returns true if socket address is unspecified — the data pointer is nil or
// data contains only zeros. Empty address cannot be used in `bind` and `connect` calls.
pub fn (a SocketAddr) is_empty() bool {
if isnil(a.data) {
return true
}
if a.u8_array().all(|e| e == 0) {
return true
}
return false
}
// push appends the `inp` bytes into internal data buffer. // push appends the `inp` bytes into internal data buffer.
@[unsafe] @[unsafe]
pub fn (mut a SocketAddr) push(inp []u8) ! { pub fn (mut a SocketAddr) push(inp []u8) ! {
if isnil(a.data) {
return error('${@METHOD}: SocketAddr is nil')
}
if a.pos + inp.len > a.len { if a.pos + inp.len > a.len {
return error('push: data overflow') return error('${@METHOD}: data overflow')
} }
mut i := 0 mut i := 0
for a.pos + 1 < a.len { for a.pos + 1 < a.len {
@@ -180,9 +204,9 @@ pub fn (a SocketAddr) u8_array() []u8 {
// str returns the string representation of socket address. // str returns the string representation of socket address.
// Supported address families are AF_INET, AF_INET6, and AF_UNIX. // Supported address families are AF_INET, AF_INET6, and AF_UNIX.
// For others a string like 'SocketAddr(0x00000000)' will be returned. // Examples: '172.16.16.132:1080', '[fdf1:72d1:0033:0000:0000:0000:0000:0247]:25535',
// Examples: '172.16.16.132:1080', '[fdf1:72d1:0033:0000:0000:0000:0000:0247]:25535, // '/run/app.sock'. For others a string like 'SocketAddr(0x00000000)' will be returned.
// '/run/app.sock'. See also `translate_name()`. // See also `translate_name()`.
pub fn (a SocketAddr) str() string { pub fn (a SocketAddr) str() string {
match a.family() { match a.family() {
af_inet { af_inet {