6.3 A Simple Transport Protocol
To make the ideas discussed so far more concrete, in this
section we will study an example transport layer in detail. The abstract
service primitives we will use are the connection-oriented primitives of Fig.
6-2. The choice of these connection-oriented primitives makes the example
similar to (but simpler than) the popular TCP protocol.
6.3.1 The Example Service Primitives
Our first problem is how to express these transport primitives
concretely. CONNECT is easy: we will just have a library procedure connect that can be called with the appropriate
parameters necessary to establish a connection. The parameters are the local
and remote TSAPs. During the call, the caller is blocked (i.e., suspended)
while the transport entity tries to set up the connection. If the connection
succeeds, the caller is unblocked and can start transmitting data.
When a process wants to be able to accept incoming calls, it
calls listen, specifying a particular TSAP to
listen to. The process then blocks until some remote process attempts to
establish a connection to its TSAP.
Note that this model is highly asymmetric. One side is
passive, executing a listen and waiting until
something happens. The other side is active and initiates the connection. An
interesting question arises of what to do if the active side begins first. One
strategy is to have the connection attempt fail if there is no listener at the
remote TSAP. Another strategy is to have the initiator block (possibly forever)
until a listener appears.
A compromise, used in our example, is to hold the connection
request at the receiving end for a certain time interval. If a process on that
host calls listen before the timer goes off, the
connection is established; otherwise, it is rejected and the caller is
unblocked and given an error return.
To release a connection, we will use a procedure disconnect. When both sides have disconnected, the
connection is released. In other words, we are using a symmetric disconnection
model.
Data transmission has precisely the same problem as connection
establishment: sending is active but receiving is passive. We will use the same
solution for data transmission as for connection establishment: an active call send that transmits data and a passive call receive that blocks until a TPDU arrives. Our concrete
service definition therefore consists of five primitives: CONNECT, LISTEN,
DISCONNECT, SEND, and RECEIVE. Each primitive corresponds exactly to a library
procedure that executes the primitive. The parameters for the service
primitives and library procedures are as follows:
connum = LISTEN(local)
connum = CONNECT(local, remote)
status = SEND(connum, buffer, bytes)
status = RECEIVE(connum, buffer, bytes)
status = DISCONNECT(connum)
The LISTEN primitive announces the caller's willingness to
accept connection requests directed at the indicated TSAP. The user of the
primitive is blocked until an attempt is made to connect to it. There is no
timeout.
The CONNECT primitive takes two parameters, a local TSAP
(i.e., transport address), local, and a remote
TSAP, remote, and tries to establish a transport
connection between the two. If it succeeds, it returns in connum a nonnegative number used to identify the
connection on subsequent calls. If it fails, the reason for failure is put in connum as a negative number. In our simple model, each
TSAP may participate in only one transport connection, so a possible reason for
failure is that one of the transport addresses is currently in use. Some other
reasons are remote host down, illegal local address, and illegal remote
address.
The SEND primitive transmits the contents of the buffer as a
message on the indicated transport connection, in several units if need be.
Possible errors, returned in status, are no
connection, illegal buffer address, or negative count.
The RECEIVE primitive indicates the caller's desire to accept
data. The size of the incoming message is placed in bytes.
If the remote process has released the connection or the buffer address is
illegal (e.g., outside the user's program), status
is set to an error code indicating the nature of the problem.
The DISCONNECT primitive terminates a transport connection.
The parameter connum tells which one. Possible
errors are connum belongs to another process or connum is not a valid connection identifier. The error
code, or 0 for success, is returned in status.
6.3.2 The Example Transport Entity
Many of the technical details (such as extensive error
checking) that would be needed in a production system have been omitted here
for the sake of simplicity.
The transport layer makes use of the network service
primitives to send and receive TPDUs. For this example, we need to choose
network service primitives to use. One choice would have been unreliable
datagram service. To keep the example simple, we have not made that choice.
With unreliable datagram service, the transport code would have been large and
complex, mostly dealing with lost and delayed packets.
Instead, we have chosen to use a connection-oriented, reliable
network service. This way we can focus on transport issues that do not occur in
the lower layers. These include connection establishment, connection release,
and credit management, among others. A simple transport service built on top of
an ATM network might look something like this.
In general, the transport entity may be part of the host's
operating system, or it may be a package of library routines running within the
user's address space. For simplicity, our example has been programmed as though
it were a library package, but the changes needed to make it part of the
operating system are minimal (primarily how user buffers are accessed).
It is worth noting, however, that in this example, the
''transport entity'' is not really a separate entity at all, but part of the
user process. In particular, when the user executes a primitive that blocks,
such as LISTEN, the entire transport entity blocks as well. While this design
is fine for a host with only a single-user process, on a host with multiple
users, it would be more natural to have the transport entity be a separate
process, distinct from all the user processes.
The interface to the network layer is via the procedures to_net and from_net (not shown).
Each has six parameters. First comes the connection identifier, which maps
one-to-one onto network virtual circuits. Next come the Q and M bits, which,
when set to 1, indicate control message and that more data from this message
follows in the next packet, respectively. After that we have the packet type,
chosen from the set of six packet types listed in Fig.
6-19. Finally, we have a pointer to the data itself, and an integer giving
the number of bytes of data.
Figure 6-19. The network layer packets used in our example.
On calls to to_net, the transport entity fills in all the parameters
for the network layer to read; on calls to from_net, the network layer dismembers an incoming packet
for the transport entity. By passing information as procedure parameters rather
than passing the actual outgoing or incoming packet itself, the transport layer
is shielded from the details of the network layer protocol. If the transport
entity should attempt to send a packet when the underlying virtual circuit's
sliding window is full, it is suspended within to_net until there is room in the window.. The management
of the packet layer window is also done by the network layer.
In addition to this transparent suspension mechanism, explicit
sleep and wakeup
procedures (not shown) are also called by the transport entity. The procedure sleep is called when the transport entity is logically
blocked waiting for an external event to happen, generally the arrival of a
packet. After sleep has been called, the
transport entity (and the user process, of course) stop executing.
The actual code of the transport entity is shown in Fig.
6-20. Each connection is always in one of seven states, as follows:
Figure 6-20. An example transport entity.
1. IDLE— Connection not established yet.
2. WAITING— CONNECT has been executed and CALL REQUEST
sent.
3. QUEUED— A CALL REQUEST has arrived; no LISTEN yet.
4. ESTABLISHED— The connection has been established.
5. SENDING— The user is waiting for permission to send a
packet.
6. RECEIVING— A RECEIVE has been done.
7. DISCONNECTING— A DISCONNECT has been done locally.
Transitions between states can occur when any of the following
events occur: a primitive is executed, a packet arrives, or the timer expires.
The procedures shown in Fig.
6-20 are of two types. Most are directly callable by user programs. Packet_arrival and clock are different, however. They are spontaneously
triggered by external events: the arrival of a packet and the clock ticking,
respectively. In effect, they are interrupt routines. We will assume that they
are never invoked while a transport entity procedure is running. Only when the
user process is sleeping or executing outside the transport entity may they be
called. This property is crucial to the correct functioning of the code.
The existence of the Q
(Qualifier) bit in the packet header allows us to avoid the overhead of a
transport protocol header. Ordinary data messages are sent as data packets with
Q = 0. Transport
protocol control messages, of which there is only one (CREDIT) in our example,
are sent as data packets with Q = 1. These control messages are detected and processed by
the receiving transport entity.
The main data structure used by the transport entity is the
array conn, which has one record for each
potential connection. The record maintains the state of the connection,
including the transport addresses at either end, the number of messages sent
and received on the connection, the current state, the user buffer pointer, the
number of bytes of the current messages sent or received so far, a bit
indicating that the remote user has issued a DISCONNECT, a timer, and a
permission counter used to enable sending of messages. Not all of these fields
are used in our simple example, but a complete transport entity would need all
of them, and perhaps more. Each conn entry is
assumed initialized to the IDLE state.
When the user calls CONNECT, the network layer is instructed
to send a CALL REQUEST packet to the remote machine, and the user is put to
sleep. When the CALL REQUEST packet arrives at the other side, the transport
entity is interrupted to run packet_arrival to check whether the local user is listening
on the specified address. If so, a CALL ACCEPTED packet is sent back and the
remote user is awakened; if not, the CALL REQUEST is queued for TIMEOUT clock ticks. If a LISTEN is done within this
period, the connection is established; otherwise, it times out and is rejected
with a CLEAR REQUEST packet lest it block forever.
Although we have eliminated the transport protocol header, we
still need a way to keep track of which packet belongs to which transport
connection, since multiple connections may exist simultaneously. The simplest
approach is to use the network layer virtual circuit number as the transport
connection number. Furthermore, the virtual circuit number can also be used as
the index into the conn array. When a packet
comes in on network layer virtual circuit k, it
belongs to transport connection k, whose state
is in the record conn[k].
For connections initiated at a host, the connection number is chosen by the
originating transport entity. For incoming calls, the network layer makes the
choice, choosing any unused virtual circuit number.
To avoid having to provide and manage buffers within the
transport entity, here we use a flow control mechanism different from the normal
sliding window.
When a user calls RECEIVE, a special credit message is sent to the transport entity on
the sending machine and is recorded in the conn
array. When SEND is called, the transport entity checks to see if a credit has
arrived on the specified connection. If so, the message is sent (in multiple
packets if need be) and the credit decremented; if not, the transport entity
puts itself to sleep until a credit arrives. This mechanism guarantees that no
message is ever sent unless the other side has already done a RECEIVE. As a
result, whenever a message arrives, there is guaranteed to be a buffer
available into which the message can be put. The scheme can easily be
generalized to allow receivers to provide multiple buffers and request multiple
messages.
You should keep the simplicity of Fig.
6-20 in mind. A realistic transport entity would normally check all
user-supplied parameters for validity, handle recovery from network layer
crashes, deal with call collisions, and support a more general transport
service including such facilities as interrupts, datagrams, and nonblocking versions
of the SEND and RECEIVE primitives.
No comments:
Post a Comment
silahkan membaca dan berkomentar