![]() |
Magic Internet Kit
|
Like the previous chapter, Serial Communications in Depth, this chapter dives below the level of the Magic Internet Kit framework and talks about the guts that MIK takes care of for you. Regardless, this information is still important for understanding what's going on under the hood of the Magic Internet Kit.
In general, programming to the MIK framework is preferable to writing code directly for the low-level APIs. The Magic Internet Kit was designed to be both lightweight and modular, so your application only has to use the parts that it wants. In this case, selecting only TCP/IP communications support when creating your application will ensure that only the proper TCP support classes are included, so there will be minimal overhead even though you get the fancy MIK interfaces.
The Serial and TCP/IP In Depth chapters are included with the Magic Internet Kit documentation since they may lend insight into the design choices made in the implementation of the MIK framework. Additionally, if you're trying to debug a comms problem, the information presented here can be extremely valuable. For these reasons, you should read the chapters that apply to your application even if you are using the high-level MIK framework.
The job of the data link server is to throw bits back and forth with a remote host. It can be as smart or a stupid as it needs to be, but a good link server would implement some type of error correction. This server then talks above itself to the next higher level in the stack: IP.
Having a data link server between IP and the hardware allows IP to be generic and run over whatever server the programmer desires. This modularity keeps IP flexible. Examples of common data link servers are dial-up Point-to-Point Protocol (PPP) and Ethernet.
In the Magic Internet Kit, PPPLinkServer implements the core PPP protocol and is used by classes like DialupPPPMeans and MagicBusPPPMeans. Ethernet is implemented in the EtherServer class and is used by the XircomEthernet driver class and its matching XircomMeans class. Both of the link servers inherit from the DataLinkServer class, which is the abstract class that defines the APIs that all Magic Cap data link servers should implement.
In MIK, the IPSwitch and IPDaemon classes implement IP. Applications will never talk to these classes directly; they are only used by the link servers and the transport protocols.
Transmission Control Protocol is a protocol that sits on top of IP and provides a mechanism for reliable, stream-based transport. IP is an unreliable service, i.e. packets can be lost or duplicated, but TCP manages that problem and requests packet resends and throws away duplicate packets. IP is also packet-based, whereas TCP provides a stream-based interface similar to using direct serial communications. For these reasons, TCP is an extremely important protocol and is the transport service of choice for many applications.
TCPStream is the Magic Internet Kit class that implements TCP. These objects are used by the application layer, and as such will be described in much greater detail shortly.
UDPPDUServer is the packet server class that MIK clients talk to, and clients must mix in the PDUClientInterface class and implement its upcall, HandlePDU, to receive incoming packets. Note that PDU is an acronym for Packet Datagram Unit.
linkServer = FindLinkByClass(iIPSwitch, linkClass);
How to start a link server depends somewhat on what type of link server it is. A proper Magic Cap link server would inherit from the CommunicationStream class and implement the Connect method for establishing the connection. Once the hardware is physically connected to the remote host, and the remote host is set up to start the type of data link server that you want, e.g. the terminal server is in PPP mode, it's time to create a new link server and start it up. Here's the code:
/* Connect the hardare, assuming that it supports Connect(). */ Connect(hardware, ticket); /* Set up paramters for new link server object. */ SetUpParameters(&linkParameters.header, newDataLinkServerObjects); linkParameters.serialServer = hardware; /* Create the new link server! */ linkServer = NewTransient(linkClass, &linkParameters.header); /* If a source IP number is known, let the link server know. */ SetDataLinkSourceIP(linkServer, mySourceIP); /* Increment the usage count of the link server. */ SetUseCount(linkServer, UseCount(linkServer)+1); /* Fire up the link server! */ SetEnabled(linkServer, true); StartDataLink(linkServer); AttachLink(iIPSwitch, linkServer, DataLinkSourceIP(linkServer));
Now that the link server object is all ready to go, the last three method calls will start it up and connect it to IP. Note that these methods might fail, and therefore the code above should be surrounded by exception handling code for whatever exceptions might get thrown. For the PPPLinkServer class, a commHardwareError exception will get thrown if PPP negotiation fails. Also keep in mind that connecting the hardware using Connect may throw its own exceptions, so that code needs to be shielded as well in the same way that the equivalent serial connection would. For a more detailed example, see the UsesTCP class.
As usual, destroying is easier than creating. The only caveat is that the application should check to make sure that it does not destroy a link server that is being used by other streams within the application. This is easily handled by using the UseCount attribute of the server to keep track of the number of users. Here's an example of closing down a link server:
linkServer = FindLinkByClass(iIPSwitch, LinkClass(self)); if (HasObject(linkServer)) { ulong useCount = UseCount(linkServer) - 1; SetUseCount(linkServer, useCount); if (useCount <= 0) { DetachLink(iIPSwitch, linkServer); Destroy(linkServer); } }
/* means is a DialupIPMeans object, ticket is a TransferTicket */ ReplaceTextWithLiteral(HostName(means), "192.216.22.142"); SetPort(means, 80); SetMeans(ticket, means); /* Create the TCPStream object with default parameters */ stream = NewTransient(TCPStream_, nil); SetStream(ticket, stream); /* Connect the stream to the remote host */ Connect(stream, ticket);
Parameters can also be passed to NewTransient when creating the TCPStream object. The only interesting parameter is the source port, which may need to be set if required to be within a certain range. This is usually never the case, though, and therefore should be set to zero so that IP will assign a port dynamically.
To listen for an incoming connection, create the data link server as usual, but replace the connect code with the following:
NewTCPStreamParameters parameters; SetUpParameters(¶meters.header, 0); parameters.destinationIPAddress = 0; parameters.destinationPort = 0; /* port to listen to */ parameters.sourcePort = 80; stream = NewTransient(TCPStream_, (Parameters*)¶meters); Listen(stream);
operation CountReadPending(): Unsigned, noFail;
This method is used to find out how many bytes are in the stream's
local buffer and are therefore available for immediate reading.
Reading is a synchronous operation, so code that does not want to
block while reading should always call CountReadPending first to check
how many bytes are available.
operation Read(buffer: Pointer; count: Unsigned): Unsigned, noFail;
This method is used to read a number of bytes from a stream into a
buffer. If the number of bytes requested by Read are not available
yet, for example the server has not sent them, Read will block until
either all the bytes are received or an error has occurred. The return
value is the number of bytes that were read, and comparing this value
to the number of bytes requested lets the application know if an error
occurred; if fewer bytes were returned than requested, then an error
occurred but Read still returned as much data as it could get.One useful tactic when waiting for data is to block on a one character read and then read any other pending data. For example, the following code is used in the CujoTerm template application:
count = Read(stream, &buffer, 1); if (count != 1) { Fail(serverAborted); } else /* count == 1, everything is peachy keen */ { Unsigned count = CountReadPending(stream); if (count != 0) { if (count > 254) count = 254; Read(stream, &buffer[1], count); } /* Remember that we already read one character up above! */ count++; HandleBytes(client, (Pointer)buffer, count); }
operation Write(buffer: Pointer; count: Unsigned), noFail;
Write, as one can imagine, is used to write data to a stream. With TCP
streams, write will immediately return after placing the bytes into
TCP's outgoing buffer. If the TCP stream was closed for some reason,
or some other error occurred, Write will instead throw a serverAborted
exception. For this reason, all calls to Write should be prepared to
catch serverAborted exceptions and handle the error case.
While DNS is very useful for resolving host names, also keep in mind that resolving a name takes time; a UDP packet requesting the name resolution must be sent a known name resolver, and then the response packet must be received. Fortunately, DNS caches names once they have been resolved, but the initial lookup still takes a second or two. Additionally, adding the DNS resolver to your Magic Cap application increases the memory footprint by about ten kilobytes.
To tell DNS which name resolution servers to use, add the 32-bit IP address of each server to the list in the DNSServers attribute of the ConnectableMeans object. This list is a FixedList object, so entries can be added with the AddUniqueElem method. If the means object is in the application's object instance file, entries can also be added there.
RegisterDNS(iDNSResolver, 0x12345678);
Much like the IPSwitch class, the DNS resolver referred to by the iDNSResolver indexical does not need to be created or destroyed by the application. This class will automatically create itself at package installation time.
ReplaceTextWithLiteral(hostName, "www.genmagic.com"); address = GetHostByName(iDNSResolver, hostName); SPrintF(addressString, "%d.%d.%d.%d", (address>>24), (address>>16)&0xff, (address>>8)&0xff, address&0xff); ReplaceTextWithLiteral(hostName, addressString);
Keep in mind that TCPStream's Connect method expects the host name to be a dotted text IP address, so the last two lines of code convert the 32-bit value into that format and put the result back into the hostName text object.
http://www.genmagic.com/Develop/MagicCap/index.html