ComputerCraft-LyqydNet
ComputerCraft-LyqydNet copied to clipboard
A set of APIs and scripts for ComputerCraft to establish and operate an in-game computer network with routing.
LyqydNet APIs:
1 - Setup
To install, simply copy the files to their appropriate directories.
Each computer that you want to be on the network should have this added to its startup (see 3.1 - 3.4 for more information):
shell.run("lyqydnet")
Routers need an additional argument:
shell.run("lyqydnet route")
Use of the "routed" program to activate routing features is deprecated.
2 - APIs
2.1 - Connection API
The connection API is the application-layer rednet interface in LyqydNet. It provides an easy way to set up client-server applications rapidly and with minimal effort. This layer abstracts away most tech- nical aspects of formatting and delivering rednet packets so that the small details are gotten out of the developer's way.
2.1.1 - connection.new()
The new function takes up to three arguments; a computer identification string, a remote socket number, and a local socket number. To create a new connection to a computer named "fileserver" running the filed program (which listens on port 21), we would use:
local conn = connection.new("fileserver", 21)
This will instantiate a new connection instance, and add the connection to the master table of connections in the connection API. To use the connection instance, you must first call its :open() method (see 2.1.2).
2.1.2 - conn:open()
The open function for a given connection can be called with one argument, an optional timeout in seconds. If no timeout is provided, the function will wait until a response is received from the server. A time- out of two to five seconds is usually sufficient to allow a connection time to initialize. You must call :open on a connection before using the :listen or :send methods on it. The open function returns the payload of the response packet from the computer being connected to.
local response = conn:open(2)
2.1.3 - conn:send()
The send function takes two arguments; a message type, and a message to send. Most communications to a server are sent using the "data" message type. Note that if you have not opened the connection (see 2.1.2), send will take no action and return false. For example,
conn:send("data", "Hello, Server!")
Please be aware that if you specify an unrecognized packet type string, the packet may fail to be sent. If you desire to use a custom packet type, you should either add your translation entries to the table in the packet API, or use the short (two character) packet type name here. See 2.2.3 for more details.
2.1.4 - conn:listen()
The listen function takes only one argument, a timeout in seconds. This function will wait for packets coming in on the connection, based on the origin, destination and local/remote port numbers. The listen function returns the entire packet (see <packetAPI>) it received.
local packetReceived = conn:listen()
2.1.5 - conn:close()
The close function takes two optional arguments; a message, and a 'quiet' value. If no message is provided, the message "disconnect" will automatically be used. If the quiet option is true, no closing packet will be send to the connection. Normally, one would use the close function in quiet mode after receiving a connection close message from a client computer. An example of a client disconnecting:
conn:close()
2.1.6 - conn:isTurtle()
The isTurtle function returns true if the remote computer is a turtle and false if it is not.
2.1.7 - conn:route()
The route function returns the host table routing number for the remote computer.
2.1.8 - conn:name()
The name function returns the name of the remote computer.
2.1.9 - connection.listen()
The listen function is used by foreground servers (the computer is only running the server) to listen for incoming packets and connections. It takes two arguments; the socket number and an optional timeout duration. The socket number provided to this call is used to check incoming connection requests, so it is imperative that they match. The listen function returns the received packet and a connection. For example, a file server might use:
local packetReceived, conn = connection.listen(21)
2.2 - Packet API
The Packet API is the basic method of handling data as it is transported across the network. Every layer of LyqydNet utilizes it to more easily handle looking up and changing various pieces of the data, and packet data type values are returned from the connection API listening functions (2.1.4, 2.1.9).
2.2.1 - packet.new()
This function intializes a new packet, given a two-letter packet type (see 2.2.3 for translation table), a computer ID destination, a string of message contents, a destination socket number (unused on RM packets), and an origin socket number (also unusued on RM packets).
2.2.2 - packet.reconstruct()
Reconstruct takes a received message string, a destination, an origin, and a distance as arguments and creates a packet value from those pieces of information.
2.2.3 - packet.types
The types table is a lookup table to convert between the two-letter type names and the human-readable type names. For example, the response packet type's entry in the table would be this:
{ SR = "response" response = "SR" }
As you can see, simply indexing the table with one type of packet name will give the matching name of the other type, so packet.types["SR"] would give the value "response".
2.3 - Net API
The Net API is the layer that the connection API is built on. This layer handles the sending and receiving of packets, as well as delivering them to the running server applications, network daemons and client programs. Full documentation is provided, but many functions of this API are intended for use only by other APIs.
2.3.1 - net.netInit()
This function takes no arguments. The netInit function is crucial to the LyqydNet system. It makes it discoverable to routers and other computers on the network. Each computer must be labeled before it can be added to the network. Computer labels are used as hostnames. This function sends out basic information about the computer in a broadcast. This function is automatically called when the lyqydnet program is run.
2.3.2 - net.daemonAdd()
This function takes three arguments; a daemon name, a function and a socket number. The function is used to create a coroutine which will be resumed every time a packet is received for the daemon on that socket. Daemons are background servers. See 2.1.1, 2.1.7, 2.1.8 for more information on socket numbers.
2.3.3 - net.daemonRemove()
This function removes a daemon from the daemon table, so that it can no longer receive packets. It takes one argument; the daemon name. If the daemon was present, this function returns true; otherwise, it returns false.
2.3.4 - net.routeFromName()
This function takes the name of a computer as an argument and returns the route number associated with it.
2.3.5 - net.routeFromCID()
This function takes the ID number of a computer as an argument and returns the route number associated with it.
2.3.6 - net.nameFromRoute()
This function takes a route number as an argument and returns the name of the computer associated with it.
2.3.7 - CIDFromRoute()
This function takes a route number as an argument and returns the ID of the computer associated with it.
2.3.8 - net.send()
This function takes a packet and stringifies it and sends it out.
2.3.9 - net.add_route()
This function takes an ID number, computer type, computer name, gateway and cost and adds a route to the route table. Do not manually call this function.
2.3.10 - net.remove_route()
This function takes a route number and removes it from the routing table and the hosts file. Do not manually call this function.
2.4 - Net File API
The netfile API is used to greatly simplify the exchange of files across the LyqydNet network.
2.4.1 - netfile.get()
The get function takes four arguments; a connection number, a remote file to get, a local file to save the remote file to, and a timeout. If the connection number refers to a connection that doesn't exist, it will return false. Otherwise, it will request the file and then return with a call to netfile.receive(). See 2.4.2 in regards to using this in a server application.
2.4.2 - netfile.receive()
The receive function takes three arguments; a connection number, a local file to save the incoming data to, and a timeout. It will return false if the connection number refers to a connection that doesn't exist, or if it times out before the file is received. Otherwise, it will return the file header (contains the name of the remote file).
Only foreground servers should use netfile.receive() (and netfile.get(), since it uses netfile.receive()) for reasons discussed in 2.3.12. Since they wait for packets without using coroutine.yield, they will not work properly (or at all) in background servers. Background servers wishing to receive files should instead catch the fileData message type (saving each line to a table) and the fileEnd message type (write the table to the file).
2.4.3 - netfile.put()
The put function takes four arguments; a connection number, a local file to send, a remote file location to save it to, and a timeout duration. It sends a packet requesting to upload the file and awaits a response. If a response allowing the upload is received before the timeout, the function calls netfile.send() to send the file and returns whatever send returns. If the local file doesn't exist, or the connection doesn't exist, or if a bad response is received, put() will return false.
2.4.4 - netfile.send()
The send function takes two arguments; a connection number and a local file to send. If the connection number refers to a connection that does not exist, or if the local file does not exist, send() will return false. Otherwise, it will send a file header packet containing the local name of the file, the file data one line per packet, and finally a file end packet. It will then return true.
3 - Programs
3.1 - lyqydnet
This program sets up the environment for lyqydnet to operate, as well as establishing the network daemon coroutine. The network daemon establishes and maintains the routing table and responds to network information packets.
shell.run("lyqydnet")
3.1.1 - lyqydnet route
This argument enables the routing functionality of the network daemon. It maintains lists of hosts and is the backbone of the network. Each router will forward packets onto other routers, using the most efficient known route to pass packets on to their destinations. Routers are used to create networks larger than the maximum rednet transmission distance by repeating packets onward.
They send the list of hosts on the network to computers joining the network. New hosts will then direct all of their traffic through the router until they discover shorter routes (peer connections cannot be made until the first peer has rebooted; this is to minimize lag).
Routers can be made to work across great distances by using the longlink instruction. Longlink routers will hold onto packets forwarded through them until the next router confirms receipt of the packet. As the chain of routers is walked past, each router in line comes online. When a router comes online, longlink routers check whether there are any packets waiting to be sent to it for forwarding. If so, all packets for it are sent onward.
This process repeats all the way across the usually-unloaded chunks to the other end of the network. Longlink is not the default behavior for routers because it causes twice the packets to be sent each time (one for data, one for confirmation). Routers that are normally in range of each other should not be longlink routers.
The routing daemon should be started immediately after the network daemon, if the computer will be a router.
shell.run("lyqydnet route")
To make a router a longlink router, start it with the longlink option:
shell.run("lyqydnet route longlink")
3.3 - netmon
Netmon is a network monitoring utility. It watches network activity over a range of frequencies specified when starting it:
netmon 0 7
4 - Examples
4.1 - Example Foreground Server
'------------------------------------------------------------------------ local connections = {}
while true do --wait for input from client connections on socket 21. packet, conn = connection.listen(21) local messType = packet.types[packet.type] --okay, we have a packet, see if the connection is one we already --know about. if connections[conn] and connections[conn].status == "open" then --we can use additional entries in our 'connections' table --(which is internal to the server app) to store information --about client state, among other things. if messType == "data" then --handle client input here; use the connection number in --'conn' to send data back. print(message) conn:send(conn, "done", "OK") --break packet tells the client we are done transmitting --and are ready for the next command. This is optional. Some --types of servers wouldn't need to use this, e.g. a chat --server. elseif messType == "close" then --client has closed the connection, so let's close out --the connection information. conn:close("disconnect", true) connections[conn].status = "closed" connections[conn].name = nil elseif messType == "instruction" then --these are local instructions. Often unused. if message == "stop" then return true end end elseif messType == "query" then --a client wants to open a connection if connections[conn] then --already have an entry for that connection number, --reset the information connections[conn].status = open connections[conn].name = connection.name(conn) else --no entry for this connection, set up a new --entry. local connect = {} connect.status = "open" connect.name = connection.name(conn) connections[conn] = connect end --either way we set up the connection, let the client know --it was successful. conn:send("response", "ok") end end
4.2 - Example background server
Note that this example is very similar to the previous example, the foreground server. The critical difference here is that we use connection.listenIdle() to get packets (which uses coroutine.yield()). We also declare the server as a function, and then use net.daemonAdd to add it to the daemon table. Please note that the daemon name, "server" is the same as the server identifier in the previous example, which is in the connection.listen() call.
local connections = {}
function serverFunction () while true do --wait for input from client connections on socket 21. conn, messType, message = connection.listenIdle(21) --okay, we have a packet, see if the connection is one we --already know about. if connections[conn] and connections[conn].status == "open" then --we can use additional entries in our 'connections' table --(which is internal to the server app) to store information --about client state, among other things. if messType == "data" then --handle client input here; use the connection number in --'conn' to send data back. connection.send(conn, "done", "OK") --break packet tells the client we are done transmitting --and are ready for the next command. elseif messType == "close" then --client has closed the connection, so let's close out --the connection information. connection.close(conn, disconnect, true) connections[conn].status = "closed" connections[conn].name = nil elseif messType == "instruction" then --these are local instructions. Often unused. if message == "stop" then return true end end elseif messType == "query" then --a client wants to open a connection if connections[conn] then --already have an entry for that connection number, --reset the information connections[conn].status = open connections[conn].name = connection.name(conn) else --no entry for this connection number, set up a new --entry. local connect = {} connect.status = "open" connect.name = connection.name(conn) table.insert(connections, conn, connect) end --either way we set up the connection, let the client know --it was successful. connection.send(conn, "response", "ok") end end end
--make sure the socket number here matches the socket number used above. net.daemonAdd("server", serverFunction, 21)
4.3 - Example Client
This client would, when run, open a command prompt. The command open, with a server name, would open a connection to a server. The command close would close the connection. The exit command would close the connection if one was open, then exit the program. All other text would be sent directly to the server application.