file: job.t package: muf status: tentative
{ [ :socket socket :port 4201 ( optional ) :host "sl.tcp.com" ( optional ) :protocol :stream ( optional ) :addressFamily :internet ( optional ) | ]openSocket }
{ [ :socket socket :port 4201 ( optional ) :host "128.95.44.22" ( optional ) :protocol :stream ( optional ) :addressFamily :internet ( optional ) | ]openSocket }
{ [ :socket socket :port 4201 ( optional ) :ip0 128 :ip1 95 :ip2 44 :ip3 22 ( optional ) :protocol :stream ( optional ) :addressFamily :internet ( optional ) | ]openSocket }
{ [ :socket socket :protocol :datagram ( optional ) :addressFamily :internet ( optional ) | ]openSocket }
The ]openSocket
can open a TCP/IP connection
from the Muq server to another process, or a UDP/IP
datagram socket.
Allowing arbitrary outbound network connections can pose serious security problems! For example, it may be used to connect to NFS filesystems on your subnet and modify them or capture passphrase files, or it may be used to connect to X servers on your subnet and capture keyboard type-in (including passphrases) or issue commands like "rm *" to open shell windows. For these and other reasons, Muq provides bitmaps controlling which ports may be specified by user and root jobs: See the
--destports
and--rootdestports
commandline arguments for Muq.
By default, user jobs may open TCP or UDP connections to the following ports:
The :socket
parameter must be a socket ; It
should be either freshly created
See section makeSocket, or else one which has been
closed by See section ]closeSocket. Before making this
call, you should set the socket's
$s.standardInput
and $s.standardOutput
keys to the message streams which you wish the socket
connection to use.
The :addressFamily
parameter may be omitted, and
must currently always be :internet
if present; It is
provided for future expansion of functionality.
The :protocol
parameter may be omitted, in which
case it defaults to :stream
, indicating a TCP
connection. If :datagram
is specified, an UDP
socket is opened.
When writing to UDP sockets, you can (and usually
should) specify the destination address and port
on a datagram-by-datagram basis using
|writeStreamPacket
and the keywords
:ip0
, :ip1
, :ip2
, :ip3
and :port
. For example, to send "This is a test"
to port 9 at 128.95.44.22 you might do:
[ :ip0 128 :ip1 95 :ip2 44 :ip3 22 :port 9 'T' 'h' 'i' 's' ' ' 'i' 's' ' ' 'a' ' ' 't' 'e' 's' 't' | "txt" t my-udp-stream |writeStreamPacket pop pop ]pop
The keyval pairs may be anywhere in the block, although
grouping them at the beginning or end is recommended.
If the address or port is not specified this way, the
values from the previous datagram are used, or failing
that, those specified in the ]openSocket
.
In the current Muq implementation, you cannot count on
being able to send datagrams of more than 2000 bytes
if "\n" -> "\r\n"
conversion is enabled, or of more
than 4000 bytes if this conversion is disabled
(socket$S.nlToCrnlOnOutput
property);
similar size contraints apply to datagram reception.
Oversize datagrams are likely to be silently dropped.
Let me know if these limitations become a problem.
Note that sending datagrams larger than the path MTU (Maximum Transmission Unit) is inefficient and normally avoided by good networking code. Typical wide-area network MTU values range from 500 to 1500 bytes.
"As an experiment, this ... was run numerous times to various hosts around the world. Fifteen countries (including Antarctica) were reached and various transatlantic and transpacific links were used. ... Out of 18 runs, only 2 had a path MTU of less than 1500." -- W R Stevens, TCP/IP Illustrated Vol I 1994.
You should usually set socket$s.inputByLines
to nil
on a UDP socket, since you will want
to read complete datagrams one at a time, rather than
single lines from them.
Remember that the Internet UDP datagram service is unreliable! Datagrams can and frequently do get lost without any notification to client or server. Any code which uses UDP datagrams must be prepared to deal with this.
The :host
parameter gives the destination host
to contact, and should be a string containing
either a dotted-decimal Internet address such as
"128.95.44.22" or else a symbolic Internet address such as
"sl.tcp.com". Alternatively, the :ip0
, :ip1
,
:ip2
and :ip3
parameters may be used to
specify the destination host address using four integers.
Providing both forms of address is an error; If neither
is provided, the default localhost address of 127.0.0.1
will be used.
The :port
parameter must be an integer specifying the
unix port to which to connect. If no port is specified,
the default telnet port 23 will be used.
Example. Here's a function which prints one line from the given port on the host machine:
: print-port { $ -> } -> port ( Create a socket for network I/O: ) makeSocket -> socket ( Hook up input and output streams to it: ) makeMessageStream -> in makeMessageStream -> out in --> socket$s.standardInput out --> socket$s.standardOutput ( Open a connection to given port: ) [ :socket socket :port port | ]openSocket ( Read and print one line from socket: ) out readStreamLine pop , ( Close the socket: ) [ :socket socket | ]closeSocket ;
Here is an example of print-port
in action.
Port 13 ("daytime") supplies the current date and time.
Port 19 ("chargen") generates an endless stream
of text.
stack: 13 print-port Thu Oct 19 00:28:06 1995 stack: 19 print-port !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg stack:
Example. Here's code to communicate via a pair of UDP ports. Remember that UDP is an unreliable protocol, so any packet transmitted may be silently lost. Normally the client and server code would be run on different machines, of course!
( Create socket to listen for ) ( UDP datagrams on port 62121: ) makeSocket --> *server* makeMessageStream --> *server-input* makeMessageStream --> *server-output* *server-input* --> *server*$S.standardInput *server-output* --> *server*$S.standardOutput [ :socket *server* :port 62121 ( Local socket on which we read. ) :protocol :datagram | ]listenOnSocket ( Create socket to send ) ( udp packets to port 62121: ) makeSocket --> *client* makeMessageStream --> *client-input* makeMessageStream --> *client-output* *client-input* --> *client*$S.standardInput *client-output* --> *client*$S.standardOutput [ :socket *client* :port 62121 ( Far socket to which we send. ) :protocol :datagram | ]openSocket ( We almost always want UDP ) ( sockets to preserve datagram ) ( boundaries intact, not slice ) ( them up into lines: ) nil --> *server*$S.inputByLines nil --> *client*$S.inputByLines ( Fork off separate server ) ( and client processes, and ) ( do a request/acknowledge ) ( with retries and exponential ) ( backoff: ) nil --> *time-for-server-to-exit* makeLock --> *lock* *lock* withChildLockDo{ 1 -> millisecsToWait forkJob -> amParent amParent if ( We'll have parent job play client: ) do{ ( Send a query to server. ) ( We use |writeStreamPacket ) ( to ensure that our text ) ( goes out in exactly one ) ( datagram even though it ) ( contains a newline and does ) ( not end with one: ) "Party!\nRSVP" stringChars[ "txt" t *client-input* |writeStreamPacket pop pop ]pop ( Read an acknowledgement: ) [ *client-output* | t millisecsToWait |readAnyStreamPacket dup not if ( Timeout: Discard dummy ) ( values and try again: ) pop pop pop ]pop millisecsToWait 2 * -> millisecsToWait else ( Got acknowledgement: ) ( save it and quit loop: ) --> *client-stream* --> *client-socket* --> *client-tag* ( Delete address info ) ( from datagram packet: ) |deleteNonchars ( Save server response: ) ]join --> *server-line* ( Reset wait time before next request: ) 1000 -> millisecsToWait ( Do only one request, ) ( for this toy example: ) loopFinish fi } ( Tell server to exit: ) t --> *time-for-server-to-exit* ( Wait until it does: ) *lock* withLockDo{ } else ( We'll have child job play server: ) do{ ( Read datagram from client: ) [ *server-output* | t millisecsToWait |readAnyStreamPacket dup not if ( Timeout: Discard ) ( dummy values: ) pop pop pop ]pop else ( Got request -- save it: ) --> *server-stream* --> *server-socket* --> *server-tag* ( Remember where request came from: ) :ip0 |get -> ip0 :ip1 |get -> ip1 :ip2 |get -> ip2 :ip3 |get -> ip3 :port |get -> port ( Remove address info ) |deleteNonchars ( Record request line: ) ]join --> *client-line* ( Acknowledge request: ) [ :ip0 ip0 :ip1 ip1 :ip2 ip2 :ip3 ip3 :port port | "No thanks!" stringChars[ ]|join "txt" t *server-input* |writeStreamPacket pop pop ]pop fi ( Exit if client says to: ) *time-for-server-to-exit* if ( Exiting releases lock: ) nil endJob fi } fi } "Client line: '" , *client-line* , "'\n" , "Server line: '" , *server-line* , "'\n" , ( Close both ports: ) [ :socket *server* | ]closeSocket [ :socket *client* | ]closeSocket
See section ]rootPopenSocket.
Go to the first, previous, next, last section, table of contents.