Basic4GL, Copyright (C) 2005 Tom Mulgrew
Network engine guide
2-Apr-2005
Tom Mulgrew
The Basic4GL network engine is designed primarily for writing games.
It
allows you to establish a network connection between two running Basic4GL
programs and send blocks of data (which we call "messages") back and
forth.
Basic4GL will attempt to send these messages as quickly as possible
(with some extra logic for reliability and/or ordering if requested).
Actually that's all it really does.
Which leaves it up to you to decide what sort of data to send, what it means,
and how often you send it. :)
However with a bit of planning it is possible
to write some responsive multiplayer lan or internet games without too much
fuss.
The network engine uses UDP/IP packets for communication.
Any network that
can do TCP/IP can do UDP/IP (as TCP is built on top of UDP), so your programs
can run over the Internet and TCP/IP local networks.
Basic4GL uses its own protocol for handling connection lifetime and reliable
packet delivery which is optimised towards writing responsive networked games.
It lets you choose which messages must get through and which ones don't
matter if they get lost on the way. You can also choose which messages
must arrive in the same order they were sent and which ones don't matter
so much.
This upshot of this is that a carefully designed application will
have the best chance to be able to continue smoothly if a data packet is lost in
transmission, which is important for realtime games. (Unlike TCP/IP which has to
stop for a few seconds if it hits an error).
The downside is because the
Basic4GL network engine uses its own protocol, it can only talk to other
applications using the same protocol. In other words, other Basic4GL programs,
so you cannot use Basic4GL to write an FTP client, web browser, etc. (Standard
TCP/IP support will likely be added to a later version.)
The current Basic4GL network engine currently supports:
Note: The network engine (at time of writing) is still young and lacks some features found in more mature networking engines likeautomatic bandwidth throttling.
The bulk of a program's network code usually involves:
A network message is a block of data, similar to a small disk file. Infact
Basic4GL uses the file I/O functions to read and write the contents of network
messages.
Instead of using OpenFileWrite and OpenFileRead, you use
SendMessage and ReceiveMessage, but otherwise it's just like accessing a disk
file.
Compare this program to write a simple text file:
dim file
file = OpenFileWrite ("files\test.txt") ' Open a file for output
WriteString (file, "Some text") ' Write some text
CloseFile (file) ' Close the file
With this program to send a message over a network connection:
dim msg
...
msg = SendMessage (connection) ' Create a message to send down connection
WriteString (msg, "Some text") ' Write some text
CloseFile (msg) ' Send the message
(Note: The above program is incomplete...)
All of Basic4GL's file I/O functions except "OpenFileRead" and
"OpenFileWrite" can be used with Basic4GL network messages.
These functions
are described in the File I/O section of the Basic4GL Programmer's Guide.
Two connect two computer over a network, you must do the following:
At this point both the client and the server have a "connection" with which
they can send and receive data.
Data sent down the server's connection will
be received by the client's connection and vice versa.
(Note: This can be extended to connect multiple computers together, by having one as the server and having the rest of them as clients that connect to the server. In this case the server will have multiple "connection"s, one for each client.)
Format:
NewServer (port)
Where port is the port number on which the server "listen"s for
connection requests.
NewServer() creates a server and returns a handle to
identify the server to other functions (such as AcceptConnection()).
Example:
dim server
server = NewServer (8000) ' Create a new server on port 8000
' ... Run the program
DeleteServer (server) ' Close and delete the server
Format:
DeleteServer (server)
Where server is a server handle returned from NewServer().
Shuts
down and deletes the server. Any connections accepted by the server will
automatically be disconnected and deleted.
It is good practice to close server objects (and connections) when finished
with them.
If not closed explicitly, Basic4GL will automatically close them
when the program ends.
Format:
ConnectionPending (server)
Where server is a server handle returned from NewServer().
ConnectionPending() returns true if a client has asked for a connection to
the server and is waiting for the server to accept or reject it.
The
connection can now be accepted with AcceptConnection() or rejected with
RejectConnection().
Format:
AcceptConnection (server)
Where server is a server handle returned from NewServer().
AcceptConnection() accepts a pending connection request, creates a
corresponding connection object and returns a handle for it.
If no connection
is pending, AcceptConnection() does nothing and returns 0.
Example:
const port = 8000
dim server, connection
' Create server
server = NewServer (port)
printr "Server created. Waiting for connections"
' Wait for incoming connections
while true
if ConnectionPending (server) then
printr "Connection accepted"
' Accept connection
connection = AcceptConnection (server)
' ... Do something here
Sleep (1000)
' Close connection now that we're finished
DeleteConnection (connection)
endif
wend
Format:
RejectConnection (server)
Where server is a server handle returned from NewServer().
Rejects an incoming connection request.
This returns a notification to the
connecting client that the connection has not been accepted,
and discards the request.
Format:
NewConnection (address, port)
Creates a new connection and attempts to connect to a server at the specified
address and port.
address is a text string specifying the network
name to connect to. It can either be a DNS address (e.g. "someserver.com"), a
numeric IP address (e.g. "192.168.0.1") or "localhost" (meaning connect to the
same computer).
port is the port number. It must be the same one as
the server is listening on, otherwise it wont find the server.
NewConnection() returns a handle identifying the connection that can be passed to other functions (such as SendMessage()).
Format:
DeleteConnection (connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
Deletes a network connection.
If the connection is active, it will be
closed, and a notification sent to the corresponding connection at the other end
to inform it of the close.
Basic4GL also automatically closes and deletes any outstanding network connections when the program finishes.
Format:
ConnectionConnected (connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
ConnectionConnected() returns true if the connection is still connected, or
false if the connection has been disconnected.
Connections are considered
"connected" when they are created, and remain that way until either:
Format:
ConnectionHandshaking(connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
Returns true if the connection is in the hand-shaking state.
Connections created by NewConnection() are considered to be "hand-shaking"
until the server accepts the connection (and the confirmation notification is
received).
Once the connection is established, it leaves the hand-shaking
state (ConnectionHandshaking() will then return false), and the connection is
ready to send and receive messages.
Note: Server connections created with AcceptConnection() do not have a hand-shaking phase. For these ConnectionHandshaking() will always return false. The connection is fully established as soon as it has been accepted.
Format:
ConnectionAddress(connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
Returns the IP address of the computer at the other end of the network connection, in numeric format (e.g. "192.168.0.1").
Data is passed through connections as "messages", variable length blocks of data which are transmitted and received as a single item.
Format:
SendMessage (connection, channel, reliable, smoothed)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
SendMessage() creates a message ready to be sent down connection,
and returns a handle representing the message.
You can then pass this handle
to the Write...() file I/O functions (WriteByte(), WriteString(), etc) to write
data to the message, just as you would write data to a file. Refer to
the file I/O functions in the Basic4GL Programmer's Guide
for more information.
Once the message is ready, call CloseFile() to close the message and send it.
SendMessage() has 3 options which affect message delivery:
1. Channel
Channel is a "channel number" and affects the order in which messages are
received.
Depending on network conditions messages can arrive at the
receiving end in a different order than which they were sent. Also, in some
cases a message (or part of a message) may be lost in transmission and have to
be resent, which delays the message long enough for other messages to get in
infront of it.
The Basic4GL network engine supports ordering of messages
through "channels". Every connection has 32 channels (numbered 0 through 31
inclusive). Messages sent within a single channel
are guaranteed to be received in the same order as they were sent,
with the exception of channel # 0 which is the unordered channel.
Two
messages sent down different channels are not guaranteed to be received
in the same order.
There are multiple channels to allow you to specify on which messages the ordering is important. A good choice of channels can affect network performance, especially over unreliable networks (such as an internet connection). If an ordered message is delayed, the whole channel will stall until the message is received and slotted into its correct order. However other channels will still keep receive messages. So if a game was using on ordered channel for chat messages, and a different channel for position updates, the engine can keep receiving position updates even if a chat message is lost and must be re-transmitted.
2. Reliable
Reliable is true if the message must be delivered.
Depending on network
conditions, some messages may be lost in transmission. The reliable flag
specifies whether this is acceptable for this message (reliable = false) or
whether the message must get through (in which case the network
engine will keep resending the packet until delivery is confirmed).
3. Smoothed
The amount of time a message takes to reach its destination is called the
network latency (or "lag"). This can vary greatly depending on the network
connection. Over a local network the latency can be just a few hundredths of a
second. Over a dial-up internet connection to the otherside of the world it can
be as much as a second.
Depending on network conditions the latency can
actually from message to message, meaning that messages (like position updates)
that are sent out at nice regular spaced out intervals may arrive in at the
other end in irregular clumps.
The Basic4GL network engine has a "smoothing" algorithm which compensates
this by measuring the time it takes to deliver packets, and delaying early
packets until they are considered "due".
So if un"smoothed" messages have a
network latency of 100-200ms, applying smoothing might cause the majority of
messages to have a latency of 180ms (with a few taking 180-200ms).
Be aware that this algorithm is effectively adding "lag" to faster messages, and should therefore be used with caution.
[Example here]
Format:
MessagePending (connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
MessagePending() returns true if a message has been received and can be fetched with ReceiveMessage().
Format:
MessageChannel (connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
MessageChannel() returns the channel number of the pending message. (See SendMessage() for more information).
Format:
MessageReliable (connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
MessageReliable() returns whether the pending message was sent as a reliable message (MessageReliable() = true) or as an unreliable message. (See SendMessage() for more information).
Format:
MessageSmoothed (connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
MessageSmoothed() returns whether the pending message was sent as a smoothed message (MessageSmoothed() = true) or not. (See SendMessage() for more information).
Format:
ReceiveMessage (connection)
Where connection is a connection handle returned by NewConnection() or AcceptConnection().
ReceivedMessage() fetches the current pending message from the connection and
returns a handle representing the message.
You can then pass this handle to
the Read...() file I/O functions (ReadByte(), ReadChar(), etc) to read data from
the message, just as you would read data from a file. The Seek() and EndOfFile()
functions may also be used. Refer to the file I/O functions in the
Basic4GL Programmer's
Guide for more information.
Once you have finished with the message, you should discard it with CloseFile(), in order to free up resources.
[Example here]
There are two flags which indicate the current connection state of a connection:
When a client connection is created with NewConnection(), connected
and handshaking are both set.
If the connection succeeds,
connected remains set, and handshaking is cleared.
If the
connection fails (either rejected by the server, or times out),
connected is cleared. (handshaking may remain set
though...)
Thus the code to establish a client connection might look something like this:
dim connection, address$, port
' Get connection details
print "Address?:": address$ = input$ ()
print "Port?:": port = val (input$ ())
' Attempt to connect to server
printr "Connecting..."
connection = NewConnection (address$, port)
while ConnectionConnected (connection) and ConnectionHandshaking (connection): wend
' Check if succeeded
if ConnectionConnected (connection) then
printr "Connection succeeded"
' Do something with connection
' ...
else
printr "Connection failed"
endif
' Close connection
DeleteConnection (connection)
If you attempt to use a connection while in the handshaking stage the network engine will do it's best to accomodate this. Specifically:
When a server connection is created with AcceptConnection(), connected
is set and handshaking is cleared.
The connection is considered
established and can be used immediately.
Network connections have a number of parameters which affect how they behave and perform in different network conditions. These affect timeouts, automatic resends, timing and also have an effect on the amount of bandwidth used. Often you will not need to configure these parameters as they have defaults should work in a number of different network conditions. However they are available should you need them.
Be careful when adjusting connection settings, as they can cause the network connection to fail if setup incorrectly.
Connection settings can be changed after a connection is created (with NewConnection or AcceptConnection).
Format:
SetConnectionTimeout(milliseconds)
Where millisecondsis the number of milliseconds after which
a connection times out and disconnects if no response is received from the other
side.
The default is 60000 (60 seconds).
Format:
SetConnectionHandshakeTimeout(milliseconds)
Where millisecondsis the number of milliseconds after which
a connection attempt will timeout if no reply is received from the
server.
The default is 10000 (10 seconds).
Format:
SetConnectionKeepAlive(milliseconds)
If the connection has not sent anything for this amount of time it will automatically send a "keep alive" message to let the other end know that it is still connected. This prevents the connection from timing out at the other end.
Format:
SetConnectionReliableResend(milliseconds)
This affects sending of reliable messages. When a reliable messages is sent, the connection will continually send the message until it receives confirmation from the other end that the message has been delivered. This setting controls how long the connection waits before resending the message. The default is 200 (0.2 seconds).
The lower this value is, the less delay there will be when packet loss occurs. However setting the value lower than the ping time will use up extra bandwidth, as a reliable message will be sent twice (or more) before the confirmation notification is received.
Format:
SetConnectionDuplicates(count)
Specifies the number of times each message is duplicated when sent.
The default is 1.
Setting this number higher decreases the likelyhood of
packet loss at the cost of extra bandwidth.
Format:
SetConnectionSmoothingPercentage(percentage)
The "smoothing" timing algorithm attempts to add artificial lag such
that this percentage of packets arrive on time. The defaut is 80
(percent).
Setting this number lower will decrease artificial lag but
decreases "smoothness".
Setting this number higher will increase artificial
lag and increase "smoothness".