upd5
This commit is contained in:
@@ -1,12 +1,8 @@
|
||||
# Networking Library for V
|
||||
|
||||
**netio** is a flexible networking library for V programming language.
|
||||
It heavily relies on the libc.
|
||||
|
||||
Differences with the V standard library `net` module:
|
||||
|
||||
* Provides the low-level wrappers around C API.
|
||||
* Supports any kind of sockets, socket options, address families and protocols.
|
||||
|
||||
**netio** also provides a high-level abstractions over BSD sockets: `TCPListener`,
|
||||
`TCPStream`, `UDPSocket`.
|
||||
|
||||
@@ -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_unicast_if = SocketOption(C.IP_UNICAST_IF)
|
||||
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_maxhost = NameInfoFlag(C.NI_MAXHOST)
|
||||
pub const ni_maxserv = NameInfoFlag(C.NI_MAXSERV)
|
||||
|
||||
@@ -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()!
|
||||
}
|
||||
}
|
||||
@@ -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.')
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -21,7 +21,7 @@ case $system in
|
||||
esac
|
||||
|
||||
KIND=$1
|
||||
KIND=${KIND:-"SocketType,SocketLevel,SocketOption,AddrFamily,AddrInfoFlag,NameInfoFlag"}
|
||||
KIND=${KIND:-"SocketType,SocketLevel,SocketOption,AddrFamily,AddrInfoFlag,NameInfoFlag,MsgFlag"}
|
||||
|
||||
echo module netio
|
||||
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 ~ /^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 ~ /^MSG_/ && KIND ~ "MsgFlag" {printf "pub const %s = MsgFlag(C.%s)\n", tolower($2), $2}
|
||||
KIND ~ "Any" {printf "%s\n", $0}
|
||||
{next}' | sort -k 2 -V
|
||||
|
||||
@@ -24,3 +24,6 @@ pub type AddrInfoFlag = int
|
||||
// Flag type for `translate_name()`.
|
||||
// See [getnameinfo(3)](https://man7.org/linux/man-pages/man3/getnameinfo.3.html) for details.
|
||||
pub type NameInfoFlag = int
|
||||
|
||||
// Type for recv, recvfrom, recvmsg, send, sendto, sendmsg flags.
|
||||
pub type MsgFlag = int
|
||||
|
||||
+42
-2
@@ -14,8 +14,10 @@ fn C.shutdown(i32, i32) i32
|
||||
fn C.close(i32) i32
|
||||
fn C.setsockopt(i32, i32, i32, voidptr, i32) i32
|
||||
fn C.getsockopt(i32, i32, i32, voidptr, voidptr) i32
|
||||
|
||||
struct C.sockaddr_storage {}
|
||||
fn C.recv(i32, voidptr, usize, i32) i32
|
||||
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:
|
||||
@@ -111,6 +113,44 @@ pub fn (s Socket) get_option_int(level SocketLevel, option SocketOption) !int {
|
||||
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.
|
||||
pub fn (s Socket) shutdown(how Shutdown) ! {
|
||||
if C.shutdown(s.fd, i32(how)) == -1 {
|
||||
|
||||
+43
-19
@@ -2,6 +2,8 @@ module netio
|
||||
|
||||
import encoding.binary
|
||||
|
||||
struct C.sockaddr_storage {}
|
||||
|
||||
// max_unix_path_size value is used to pad the sockaddr_un struct.
|
||||
const max_unix_path_size = $if linux {
|
||||
108
|
||||
@@ -11,6 +13,13 @@ const max_unix_path_size = $if linux {
|
||||
104
|
||||
}
|
||||
|
||||
pub struct SocketAddr {
|
||||
mut:
|
||||
data &u8 = unsafe { nil }
|
||||
len int
|
||||
pos int
|
||||
}
|
||||
|
||||
// SocketAddr.ipv4 creates new AF_INET socket address.
|
||||
// addr must be set in network (big-endian) byte order.
|
||||
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
|
||||
// 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.
|
||||
// Use this function only if you understand what you do. You must manually write the data
|
||||
// for the desired socket address, ensuring the correct sizes of all types, the order of
|
||||
// the fields in the struct, the byte order, and the total size of the struct. The sizes
|
||||
// and byte order may vary by platform, so you'll need to keep an eye on that as well.
|
||||
// A mistake while creating an address will crash your application. So this function is
|
||||
// marked as `unsafe`.
|
||||
// Use this function only if you understand what you do. Using the `push()` method you must
|
||||
// write the data for the desired socket address, ensuring the correct sizes of all types,
|
||||
// the order of the fields in the struct, the byte order, and the total size of the struct.
|
||||
// The sizes and byte order may vary by platform, so you'll need to keep an eye on that as
|
||||
// well. A mistake while creating an address will crash your application. So this function
|
||||
// is marked as `unsafe`.
|
||||
//
|
||||
// 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
|
||||
@@ -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.
|
||||
@[unsafe]
|
||||
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)) }
|
||||
unsafe {
|
||||
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.
|
||||
// Note: It returns 0 if socket address is nil, see also `is_empty()`.
|
||||
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)) }
|
||||
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.
|
||||
@[unsafe]
|
||||
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 {
|
||||
return error('push: data overflow')
|
||||
return error('${@METHOD}: data overflow')
|
||||
}
|
||||
mut i := 0
|
||||
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.
|
||||
// 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,
|
||||
// '/run/app.sock'. See also `translate_name()`.
|
||||
// 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.
|
||||
// See also `translate_name()`.
|
||||
pub fn (a SocketAddr) str() string {
|
||||
match a.family() {
|
||||
af_inet {
|
||||
|
||||
Reference in New Issue
Block a user