net(3) | AFNIX Module | net(3) |
net - standard networking module
The Standard Networkingmodule is an original implementation of networking facilities for the Internet Protocol. The module features standard TCP and UDP sockets for point to point communication as well as multicast socket. Numerous functions and objects for address manipulation are also included in this module. This module is also designed to support IP version 6 with certain platforms.
IP address
The IP based communication uses a standard address to reference a particular
peer. With IP version 4, the standard dot notation is with 4 bytes. With IP
version 6, the standard semicolon notation is with 16 bytes. The current
implementation supports both versions.
127.0.0.1 # ipv4 localhost 0:0:0:0:0:0:0:1 # ipv6 localhost
IP address architecture and behavior are described in various documents as listed in the bibliography.
Domain name system
The translation between a host name and an IP address is performed by a
resolverwhich uses the Domain Name Systemor DNS. Access to the DNS is
automatic with the implementation. Depending on the machine resolver
configuration, a particular domain name translation might result in an IP
version 4 or IP version 6 address. Most of the time, an IP version 4 address
is returned. The mapping between an IP address and a host name returns the
associated canonical namefor that IP address. This is the reverse of the
preceding operation.
The Address class
The Addressclass allows manipulation of IP address. The constructor takes a
string as its arguments. The argument string can be either an IP address or
a host name which can be qualified or not. When the address is constructed
with a host name, the IP address resolution is done immediately.
Name to address translation
The most common operation is to translate a host name to its equivalent IP
address. Once the Addressobject is constructed, the get-addressmethod
returns a string representation of the internal IP address. The following
example prints the IP address of the localhost, that is 127.0.0.1with IP
version 4.
# load network module interp:library "afnix-net" # get the localhost address const addr (afnix:net:Address "localhost") # print the ip address println (addr:get-address)
As another example, the get-host-namefunction returns the host name of the running machine. The previous example can be used to query its IP address.
Address to name translation
The reverse operation of name translation maps an IP address to a canonical
name. It shall be noted that the reverse lookup is not done automatically,
unless the reverse flagis set in the constructoor. The
get-canonical-namemethod of the Addressclass returns such name. Example
XNET001.alsis a demonstration program which prints the address original
name, the IP address and the canonical name. Fell free to use it with your
favorite site to check the equivalence between the original name and the
canonical name.
# print the ip address information of the arguments # usage: axi XNET001.als [hosts ...] # get the network module interp:library "afnix-net" # print the ip address const ip-address-info (host) {
try {
const addr (afnix:net:Address host true)
println "host name : " (addr:get-name)
println " ip address : " (addr:get-address)
println " canonical name : " (
addr:get-canonical-name)
# get aliases
const size (addr:get-alias-size)
loop (trans i 0) (< i size) (i:++) {
println " alias address : " (
addr:get-alias-address i)
println " alias name : " (
addr:get-alias-name i)
}
} (errorln "error: " what:reason) } # get the hosts for (s) (interp:argv) (ip-address-info s) zsh> axi net-0001.als localhost host name : localhost ip address : 127.0.0.1 canonical name : localhost
Address operations
The Addressclass provides several methods and operators that ease the address
manipulation in a protocol indepedant way. For example, the ==operator
compares two addresses. The ++operator can also be used to get the next IP
address.
Transport layers
The two transport layer protocols supported by the Internet protocol is the
TCP, a full-duplex oriented protocol, and UDP, a datagram protocol. TCP is a
reliable protocol while UDP is not. By reliable, we mean that the protocol
provides automatically some mechanisms for error recovery, message delivery,
acknowledgment of reception, etc... The use of TCP vs. UDP is dictated
mostly by the reliability concerns, while UDP reduces the traffic
congestion.
Service port
In the client-server model, a connection is established between two hosts. The
connections is made via the IP address and the port number. For a given
service, a port identifies that service at a particular address. This means
that multiple services can exist at the same address. More precisely, the
transport layer protocol is also used to distinguish a particular service.
The network module provides a simple mechanism to retrieve the port number,
given its name and protocol. The function get-tcp-serviceand
get-udp-servicereturns the port number for a given service by name. For
example, the daytimeserver is located at port number 13.
assert 13 (afnix:net:get-tcp-service "daytime") assert 13 (afnix:net:get-udp-service "daytime")
Host and peer
With the client server model, the only information needed to identify a
particular client or server is the address and the port number. When a
client connects to a server, it specify the port number the server is
operating. The client uses a random port number for itself. When a server is
created, the port number is used to bind the server to that particular port.
If the port is already in use, that binding will fail. From a reporting
point of view, a connection is therefore identified by the running host
address and port, and the peer address and port. For a client, the peer is
the server. For a server, the peer is the client.
TCP client socket
The TcpClientclass creates an TCP client object by address and port. The
address can be either a string or an Addressobject. During the object
construction, the connection is established with the server. Once the
connection is established, the client can use the readand writemethod to
communicate with the server. The TcpClientclass is derived from the
Socketclass which is derived from the InputStreamand
OutputStreamclasses.
Day time client
The simplest example is a client socket which communicates with the daytime
server. The server is normally running on all machines and is located at
port 13.
# get the network module interp:library "afnix-net" # get the daytime server port const port (afnix:net:get-tcp-service "daytime") # create a tcp client socket const s (afnix:net:TcpClient "localhost" port) # read the data - the server close the connection while (s:valid-p) (println (s:readln))
Example 3201.alsin the example directory prints the day time of the local host without argument or the day time of the argument. Feel free to use it with www.afnix.org. If the server you are trying to contact does not have a day time server, an exception will be raised and the program terminates.
zsh> axi 3201.als www.afnix.org
HTTP request example
Another example which illustrates the use of the TcpClientobject is a simple
client which download a web page. At this stage we are not concern with the
URL but rather the mechanics involved. The request is made by opening a TCP
client socket on port 80 (the HTTP server port) and sending a request by
writing some HTTP commands. When the commands have been sent, the data sent
by the server are read and printed on the standard output. Note that this
example is not concerned by error detection.
# fetch an html page by host and page # usage: axi 3203.als [host] [page] # get the network module interp:library "afnix-net" interp:library "afnix-sys" # connect to the http server and issue a request const send-http-request (host page) {
# create a client sock on port 80
const s (afnix:net:TcpClient host 80)
const saddr (s:get-socket-address)
# format the request
s:writeln "GET " page " HTTP/1.1"
s:writeln "Host: " (saddr:get-canonical-name)
s:writeln "Connection: close"
s:writeln "User-Agent: afnix tcp client example"
s:newline
# write the result
while (s:valid-p) (println (s:readln)) } # get the argument if (!= (interp:argv:length) 2) (afnix:sys:exit 1) const host (interp:argv:get 0) const page (interp:argv:get 1) # send request send-http-request host page
UDP client socket
UDP client socket is similar to TCP client socket. However, due to the
unreliable nature of UDP, UDP clients are somehow more difficult to manage.
Since there is no flow control, it becomes more difficult to assess whether
or not a datagram has reached its destination. The same apply for a server,
where a reply datagram might be lost. The UdpClientclass is the class which
creates a UDP client object. Its usage is similar to the TcpClient.
The time client
The UDP time server normally runs on port 37 is the best place to enable it. A
UDP client is created with the UdpClientclass. Once the object is created,
the client sends an empty datagram to the server. The server send a reply
datagram with 4 bytes, in network byte order, corresponding to the date as
of January 1st 1900. Example 3204.alsprints date information after
contacting the local host time server or the host specified as the first
argument.
# get the libraries interp:library "afnix-net" interp:library "afnix-sys" # get the daytime server port const port (afnix:net:get-udp-service "time") # create a client socket and read the data const print-time (host) {
# create a udp client socket
const s (afnix:net:UdpClient host port)
# send an empty datagram
s:write
# read the 4 bytes data and adjust to epoch
const buf (s:read 4)
const val (- (buf:get-quad) 2208988800)
# format the date
const time (afnix:sys:Time val)
println (time:format-date) ' ' (time:format-time) } # check for one argument or use localhost const host (if (== (interp:argv:length) 0)
"localhost" (interp:argv:get 0)) print-time host
This example calls for several comments. First the writemethod without argument sends an empty datagram. It is the datagram which trigger the server. The readmethod reads 4 bytes from the reply datagram and places them in a Bufferobject. Since the bytes are in network byte order, the conversion into an integer value is done with the get-quadmethod. Finally, in order to use the Timeclass those epoch is January 1st 1970, the constant 2208988800is subtracted from the result. Remember that the time server sends the date in reference to January 1st 1900. More information about the time server can be found in RFC738.
More on reliability
The previous example has some inherent problems due to the unreliability of
UDP. If the first datagram is lost, the readmethod will block indefinitely.
Another scenario which causes the readmethod to block is the loss of the
server reply datagram. Both problem can generally be fixed by checking the
socket with a timeout using the valid-pmethod. With one argument, the method
timeout and return false. In this case, a new datagram can be send to the
server. Example 3205.alsillustrates this point. We print below the extract
of code.
# create a client socket and read the data const print-time (host) {
# create a udp client socket
const s (afnix:net:UdpClient host port)
# send an empty datagram until the socket is valid
s:write
# retransmit datagram each second
while (not (s:valid-p 1000)) (s:write)
# read the 4 bytes data and adjust to epoch
const buf (s:read 4)
const val (- (buf:get-quad) 2208988800)
# format the date
const time (afnix:sys:Time val)
println (time:format-date) ' ' (time:format-time) }
Note that this solution is a naive one. In the case of multiple datagrams, a sequence number must be placed because there is no clue about the lost datagram. A simple rule of thumb is to use TCP as soon as reliability is a concern, but this choice might not so easy.
Error detection
Since UDP is not reliable, there is no simple solution to detect when a
datagram has been lost. Even worse, if the server is not running, it is not
easy to detect that the client datagram has been lost. In such situation,
the client might indefinitely send datagram without getting an answer. One
solution to this problem is again to count the number of datagram
re-transmit and eventually give up after a certain time.
Socket class
The Socketclass is the base class for both TcpClientand UdpClient. The class
provides methods to query the socket port and address as well as the peer
port and address. Note at this point that the UDP socket is a connected
socket. Therefore, these methods will work fine. The get-socket-addressand
get-socket-portreturns respectively the address and port of the connected
socket. The get-peer-addressand get-peer-portreturns respectively the
address and port of the connected socket's peer. Example 3206.alsillustrates
the use of these methods.
# create a client socket and read the data const print-socket-info (host) {
# create a tcp client socket
const s (afnix:net:TcpClient host port)
# print socket address and port
const saddr (s:get-socket-address)
const sport (s:get-socket-port)
println "socket ip address : " (
saddr:get-address)
println "socket canonical name : " (
saddr:get-canonical-name)
println "socket port : " sport
# print peer address and port
const paddr (s:get-peer-address)
const pport (s:get-peer-port)
println "peer ip address : " (
paddr:get-address)
println "peer canonical name : " (
paddr:get-canonical-name)
println "peer port : " pport }
Socket predicates
The Socketclass is associated with the socket-ppredicate. The respective
client objects have the tcp-client-ppredicate and udp-client-ppredicate.
TCP server socket
The TcpServerclass creates an TCP server object. There are several
constructors for the TCP server. In its simplest form, without port, a TCP
server is created on the localhostwith an ephemeral port number (i.e port 0
during the call). With a port number, the TCP server is created on the
localhost. For a multi-homed host, the address to use to run the server can
be specified as the first argument. The address can be either a string or an
Addressobject. In both cases, the port is specified as the second argument.
Finally, a third argument called the backlogcan be specified to set the
number of acceptable incoming connection. That is the maximum number of
pending connection while processing a connection. The following example
shows various ways to create a TCP server.
trans s (afnix:net:TcpServer) trans s (afnix:net:TcpServer 8000) trans s (afnix:net:TcpServer 8000 5) trans s (afnix:net:TcpServer "localhost" 8000) trans s (afnix:net:TcpServer "localhost" 8000 5) trans s (afnix:net:TcpServer (
Address "localhost") 8000) trans s (afnix:net:TcpServer (
Address "localhost") 8000 5)
Echo server example
A simple echo servercan be built and tested with the standard
telnetapplication. The application will echo all lines that are typed with
the telnetclient. The server is bound on the port 8000, since ports 0 to
1024 are privileged ports.
# get the network module interp:library "afnix-net" # create a tcp server on port 8000 const srv (afnix:net:TcpServer 8000) # wait for a connection const s (srv:accept) # echo the line until the end while (s:valid-p) (s:writeln (s:readln))
The telnetsession is then quite simple. The line hello worldis echoed by the server.
zsh> telnet localhost 8000 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. hello world ^D
The accept method
The previous example illustrates the mechanics of a server. When the server is
created, the server is ready to accept connection. The acceptmethod blocks
until a client connect with the server. When the connection is established,
the acceptmethod returns a socket object which can be used to read and write
data.
Multiple connections
One problem with the previous example is that the server accepts only one
connection. In order to accept multiple connection, the acceptmethod must be
placed in a loop, and the server operation in a thread (There are some
situations where a new process might be more appropriate than a thread).
Example 3302.alsillustrates such point.
# get the network module interp:library "afnix-net" # this function echo a line from the client const echo-server (s) {
while (s:valid-p) (s:writeln (s:readln)) } # create a tcp server on port 8000 const srv (afnix:net:TcpServer 8000) # wait for a connection while true {
trans s (srv:accept)
launch (echo-server s) }
UDP server socket
The UdpServerclass is similar to the TcpServerobject, except that there is no
backlog parameters. In its simplest form, the UDP server is created on the
localhostwith an ephemeral port (i.e port 0). With a port number, the server
is created on the localhost. For a multi-homed host, the address used to run
the server can be specified as the first argument. The address can be either
a string or an Addressobject. In both cases, the port is specified as the
second argument.
trans s (afnix:net:UdpServer) trans s (afnix:net:UdpServer 8000) trans s (afnix:net:UdpServer "localhost" 8000) trans s (afnix:net:UdpServer (
Address "localhost") 8000)
Echo server example
The echo servercan be revisited to work with udp datagram. The only difference
is the use of the acceptmethod. For a UDP server, the method return a
Datagramobject which can be used to read and write data.
# get the network module interp:library "afnix-net" # create a udp server on port 8000 const srv (afnix:net:UdpServer 8000) # wait for a connection while true {
trans dg (srv:accept)
dg:writeln (dg:readln) }
Datagram object
With a UDP server, the acceptmethod returns a Datagramobject. Because a UDP is
connection-less, the server has no idea from whom the datagram is coming
until that one has been received. When a datagram arrives, the
Datagramobject is constructed with the peer address being the source
address. Standard i/o methods can be used to read or write. When a write
method is used, the data are sent back to the peer in a form of another
datagram.
# wait for a datagram trans dg (s:accept) # assert datagram type assert true (datagram-p dg) # get contents length println "datagram buffer size : " (dg:get-buffer-length) # read a line from this datagram trans line (dg:readln) # send it back to the sender s:writeln line
Input data buffer
For a datagram, and generally speaking, for a UDP socket, all input operations
are buffered. This means that when a datagram is received, the acceptmethod
places all data in an input buffer. This means that a read operation does
not necessarily flush the whole buffer but rather consumes only the
requested character. For example, if one datagram contains the string hello
world. A call to readlnwill return the entire string. A call to read will
return only the character 'h'. Subsequent call will return the next
available characters. A call like read 5will return a buffer with 5
characters. Subsequent calls will return the remaining string. In any case,
the get-buffer-lengthwill return the number of available characters in the
buffer. A call to valid-pwill return true if there are some characters in
the buffer or if a new datagram has arrived. Care should be taken with the
readmethod. For example if there is only 4 characters in the input buffer
and a call to read for 10 characters is made, the method will block until a
new datagram is received which can fill the remaining 6 characters. Such
situation can be avoided by using the get-buffer-lengthand the
valid-pmethods. Note also that a timeout can be specified with the
valid-pmethod.
Low level socket methods
Some folks always prefer to do everything by themselves. Most of the time for
good reasons. If this is your case, you might have to use the low level
socket methods. Instead of using a client or server class, the
implementation let's you create a TcpSocketor UdpSocket. Once this done, the
bind, connectand other methods can be used to create the desired
connection.
A socket client
A simple TCP socket client is created with the TcpSocketclass. Then the
connectmethod is called to establish the connection.
# create an address and a tcp socket const addr (afnix:net:Address "localhost") const sid (afnix:net:TcpSocket) # connect the socket sid:connect 13 addr
Once the socket is connected, normal read and write operations can be performed. After the socket is created, it is possible to set some options. A typical one is NO-DELAYwhich disable the Naggle algorithm.
# create an address and a tcp socket const addr (afnix:net:Address "localhost") const sid (afnix:net:TcpSocket) # disable the naggle algorithm sid:set-option sid:NO-DELAY true # connect the socket sid:connect 13 addr
Address
The Addressclass is the Internet address manipulation class. The class can be
used to perform the conversion between a host name and an IP address. The
opposite is also possible. Finally, the class supports both IP version 4 and
IP version 6 address formats.
Predicate
Inheritance
Constructors
Operators
Methods
Functions
Socket
The Socketclass is a base class for the AFNIX network services. The class is
automatically constructed by a derived class and provide some common methods
for all socket objects.
Predicate
Inheritance
Constants
Methods
TcpSocket
The TcpSocketclass is a base class for all tcp socket objects. The class is
derived from the Socketclass and provides some specific tcp methods. If a
TcpSocketis created, the user is responsible to connect it to the proper
address and port.
Predicate
Inheritance
Constructors
Methods
TcpClient
The TcpClientclass creates a tcp client by host and port. The host argument
can be either a name or an address object. The port argument is the server
port to contact. The TcpClientclass is derived from the TcpSocketclass. This
class has no specific methods.
Predicate
Inheritance
Constructors
TcpServer
The TcpServerclass creates a tcp server by port. An optional host argument can
be either a name or an address object. The port argument is the server port
to bind. The TcpServerclass is derived from the TcpSocketclass. This class
has no specific methods. With one argument, the server bind the port
argument on the local host. The backlog can be specified as the last
argument. The host name can also be specified as the first argument, the
port as second argument and eventually the backlog. Note that the host can
be either a string or an address object.
Predicate
Inheritance
Constructors
Datagram
The Datagramclass is a socket class used by udp socket. A datagram is
constructed by the UdpSocketacceptmethod. The purpose of a datagram is to
store the peer information so one can reply to the sender. The datagram also
stores in a buffer the data sent by the peer. This class does not have any
constructor nor any specific method.
Predicate
Inheritance
UdpSocket
The UdpSocketclass is a base class for all udp socket objects. The class is
derived from the Socketclass and provides some specific udp methods.
Predicate
Inheritance
Constructors
Methods
UdpClient
The UdpClientclass creates a udp client by host and port. The host argument
can be either a name or an address object. The port argument is the server
port to contact. The UdpClientclass is derived from the UdpSocketclass. This
class has no specific methods.
Predicate
Inheritance
Constructors
UdpServer
The UdpServerclass creates a udp server by port. An optional host argument can
be either a name or an address object. The port argument is the server port
to bind. The UdpServerclass is derived from the UdpSocketclass. This class
has no specific methods. With one argument, the server bind the port
argument on the local host. The host name can also be specified as the first
argument, the port as second argument. Note that the host can be either a
string or an address object.
Predicate
Inheritance
Constructors
Multicast
The Multicastclass creates a udp multicast socket by port. An optional host
argument can be either a name or an address object. The port argument is the
server port to bind. The Multicastclass is derived from the UdpSocketclass.
This class has no specific methods. With one argument, the server bind the
port argument on the local host. The host name can also be specified as the
first argument, the port as second argument. Note that the host can be
either a string or an address object. This class is similar to the
UdpServerclass, except that the socket join the multicast group at
construction and leave it at destruction.
Predicate
Inheritance
Constructors
2018-07-20 | AFNIX |