Java Network Programming (3rd ed) [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Java Network Programming (3rd ed) [Electronic resources] - نسخه متنی

Harold, Elliotte Rusty

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید








13.2 The DatagramPacket Class


UDP datagrams add very little to the IP
datagrams they sit on top of. Figure 13-1 shows a typical UDP
datagram. The UDP header adds only eight bytes to the IP header. The
UDP header includes source and destination port numbers, the length
of everything that follows the IP header, and an optional checksum.
Since port numbers are given as 2-byte unsigned integers, 65,536
different possible UDP ports are available per host. These
are distinct from the 65,536 different TCP ports per host. Since the
length is also a 2-byte unsigned integer, the number of bytes in a
datagram is limited to 65,536 minus the 8 bytes for the header.
However, this is redundant with the datagram length field of the IP
header, which limits datagrams to between 65,467 and 65,507 bytes.
(The exact number depends on the size of the IP header.) The checksum
field is optional and not used in or accessible from application
layer programs. If the checksum for the data fails, the native
network software silently discards the datagram; neither the sender
nor the receiver is notified. UDP is an unreliable protocol, after
all.


Figure 13-1. The structure of a UDP datagram

Although the theoretical maximum amount of data in a
UDP
datagram is 65,507 bytes, in practice there is almost always much
less. On many platforms, the actual limit is more likely to be 8,192
bytes (8K). And implementations are not required to accept datagrams
with more than 576 total bytes, including data and headers.
Consequently, you should be extremely wary of any program that
depends on sending or receiving UDP packets with more than 8K of
data. Most of the time, larger packets are simply truncated to 8K of
data. For maximum safety, the data portion of a UDP packet should be
kept to 512 bytes or less, although this limit can negatively affect
performance compared to larger packet sizes. (This is a problem for
TCP datagrams too, but the stream-based API provided by
Socket and ServerSocket
completely shields programmers from these details.)

In Java, a UDP
datagram is represented by an
instance of the DatagramPacket class:

public final class DatagramPacket extends Object

This class provides methods to get and set the source or destination
address from the IP header, to get and set the source or destination
port, to get and set the data, and to get and set the length of the
data. The remaining header fields are inaccessible from pure Java
code.


13.2.1 The Constructors


DatagramPacket uses
different constructors depending on whether the packet will be used
to send data or to receive data. This is a little unusual. Normally,
constructors are overloaded to let you provide different kinds of
information when you create an object, not to create objects of the
same class that will be used in different contexts. In this case, all
six constructors take as arguments a byte array
that holds the datagram's data and the number of
bytes in that array to use for the datagram's data.
When you want to receive a datagram, these are the only arguments you
provide; in addition, the array should be empty. When the socket
receives a datagram from the network, it stores the
datagram's data in the
DatagramPacket object's buffer
array, up to the length you specified.

The second set of DatagramPacket constructors is
used to create datagrams you will send over the network. Like the
first, these constructors require a buffer array and a length, but
they also require the InetAddress and port to
which the packet is to be sent. In this case, you will pass to the
constructor a byte array containing the data you want to send and the
destination address and port to which the packet is to be sent. The
DatagramSocket reads the destination address and
port from the packet; the address and port aren't
stored within the socket, as they are in TCP.

13.2.1.1 Constructors for receiving datagrams


These two constructors create new
DatagramPacket objects for receiving data from the
network:

public DatagramPacket(byte[] buffer, int length) 
public DatagramPacket(byte[] buffer, int offset, int length) // Java 1.2

When a socket receives a datagram, it stores the
datagram's data part in buffer
beginning at buffer[0] and continuing until the
packet is completely stored or until length bytes
have been written into the buffer. If the second
constructor is used, storage begins at
buffer[offset] instead. Otherwise, these two
constructors are identical. length must be less
than or equal to buffer.length-offset. If you try
to construct a DatagramPacket with a length that
will overflow the buffer, the constructor throws
an IllegalArgumentException. This is a
RuntimeException, so your code is not required to
catch it. It is okay to construct a DatagramPacket
with a length less than buffer.length-offset. In
this case, at most the first length bytes of
buffer will be filled when the datagram is
received. For example, this code fragment creates a new
DatagramPacket for receiving a datagram of up to
8,192 bytes:

byte[] buffer = new byte[8192];
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);

The constructor doesn't care how large the buffer is
and would happily let you create a DatagramPacket
with megabytes of data. However, the underlying native network
software is less forgiving, and most native UDP implementations
don't support more than 8,192 bytes of data per
datagram. The theoretical limit for an IPv4 datagram is 65,507 bytes
of data, and a DatagramPacket with a 65,507-byte
buffer can receive any possible IPv4 datagram without losing data.
IPv6 datagrams raise the theoretical limit to 65,536 bytes. In
practice, however, many UDP-based protocols such as DNS and TFTP use
packets with 512 bytes of data per datagram or fewer. The largest
data size in common usage is 8,192 bytes for NFS. Almost all UDP
datagrams you're likely to encounter will have 8K of
data or fewer. In fact, many operating systems don't
support UDP datagrams with more than 8K of data and either truncate,
split, or discard larger datagrams. If a large datagram is too big
and as a result the network truncates or drops it, your Java program
won't be notified of the problem. (UDP is an
unreliable protocol, after all.) Consequently, you
shouldn't create DatagramPacket
objects with more than 8,192 bytes of data.

13.2.1.2 Constructors for sending datagrams


These four constructors create new
DatagramPacket objects for sending data across the
network:

public DatagramPacket(byte[] data, int length, 
InetAddress destination, int port)
public DatagramPacket(byte[] data, int offset, int length,
InetAddress destination, int port) // Java 1.2
public DatagramPacket(byte[] data, int length,
SocketAddress destination, int port) // Java 1.4
public DatagramPacket(byte[] data, int offset, int length,
SocketAddress destination, int port) // Java 1.4

Each constructor creates a new DatagramPacket to
be sent to another host. The packet is filled with
length bytes of the data array
starting at offset or 0 if
offset is not used. If you try to construct a
DatagramPacket with a length that is greater than
data.length, the constructor throws an
IllegalArgumentException. It's
okay to construct a DatagramPacket object with an
offset and a length that will
leave extra, unused space at the end of the data
array. In this case, only length bytes of
data will be sent over the network. The
InetAddress or SocketAddress
object destination points to the host you want the
packet delivered to; the int argument
port is the port on that host.


Choosing a Datagram Size


The correct amount of data to stuff
into one packet depends on the situation. Some protocols dictate the
size of the packet. For example, rlogin
transmits each character to the remote system almost as soon as the
user types it. Therefore, packets tend to be short: a single byte of
data, plus a few bytes of headers. Other applications
aren't so picky. For example, file transfer is more
efficient with large buffers; the only requirement is that you split
files into packets no larger than the maximum allowable packet size.

Several factors are involved in choosing the optimal packet size. If
the network is highly unreliable, such as a packet radio network,
smaller packets are preferable since they're less
likely to be corrupted in transit. On the other hand, very fast and
reliable LANs should use the largest packet size possible. Eight
kilobytesthat is, 8,192 bytesis a good compromise for
many types of networks.

It's customary to convert the data to a
byte array and place it in data
before creating the
DatagramPacket, but it's not
absolutely necessary. Changing data
after the datagram has been constructed and
before it has been sent changes the data in the
datagram; the data isn't copied into a private
buffer. In some applications, you can take advantage of this. For
example, you could store data that changes over time in
data and send out the current datagram (with the
most recent data) every minute. However, it's more
important to make sure that the data doesn't change
when you don't want it to. This is especially true
if your program is multithreaded, and different threads may write
into the data buffer. If this is the case, synchronize the
data variable or copy the data into a temporary
buffer before you construct the DatagramPacket.

For instance, this code fragment creates a new
DatagramPacket filled with the data
"This is a test" in ASCII. The
packet is directed at port 7 (the echo port) of the host
www.ibiblio.org:

String s = "This is a test";
byte[] data = s.getBytes("ASCII");
try {
InetAddress ia = InetAddress.getByName("www.ibiblio.org");
int port = 7;
DatagramPacket dp = new DatagramPacket(data, data.length, ia, port);
// send the packet...
}
catch (IOException ex)
}

Most of the time, the hardest part of creating a new
DatagramPacket is translating the data into a
byte array. Since this code fragment wants to send
an ASCII string, it uses the getBytes( ) method of
java.lang.String. The
java.io.ByteArrayOutputStream class can also be
very useful for preparing data for inclusion in datagrams.


13.2.2 The get Methods


DatagramPacket has six
methods that retrieve different parts of a datagram: the actual data
plus several fields from its header. These methods are mostly used
for datagrams received from the network.

13.2.2.1 public InetAddress getAddress( )


The getAddress( ) method returns an
InetAddress object containing the address of the
remote host. If the datagram was received from the Internet, the
address returned is the address of the machine that sent it (the
source address). On the other hand, if the datagram was created
locally to be sent to a remote machine, this method returns the
address of the host to which the datagram is addressed (the
destination address). This method is most commonly used to determine
the address of the host that sent a UDP datagram, so that the
recipient can reply.

13.2.2.2 public int getPort( )


The getPort( ) method returns an integer specifying
the remote port. If this datagram was received from the Internet,
this is the port on the host that sent the packet. If the datagram
was created locally to be sent to a remote host, this is the port to
which the packet is addressed on the remote machine.

13.2.2.3 public SocketAddress getSocketAddress( ) // Java 1.4


The getSocketAddress() method returns a
SocketAddress object containing the IP address and
port of the remote host. As is the case for getInetAddress(
)
, if the datagram was received from the Internet, the
address returned is the address of the machine that sent it (the
source address). On the other hand, if the datagram was created
locally to be sent to a remote machine, this method returns the
address of the host to which the datagram is addressed (the
destination address). You typically invoke this method to determine
the address and port of the host that sent a UDP datagram before you
reply. The net effect is not noticeably different than calling
getAddress( ) and getPort( ),
but if you're using Java 1.4 this saves one method
call. Also, if you're using non-blocking I/O, the
DatagramChannel class accepts a
SocketAddress but not an
InetAddress and port.

13.2.2.4 public byte[] getData( )


The getData( ) method returns a
byte array containing the data from the datagram.
It's often necessary to convert the bytes into some
other form of data before they'll be useful to your
program. One way to do this is to change the byte array into a
String using the following
String constructor:

public String(byte[] buffer, String encoding)

The first argument, buffer, is the array of bytes
that contains the data from the datagram. The second argument
contains the name of the encoding used for this string, such as ASCII
or ISO-8859-1. Thus, given a DatagramPacket
dp received from the network, you can convert it
to a String like this:

String s = new String(dp.getData( ), "ASCII");

If the datagram does not contain text, converting it to Java data is
more difficult. One approach is to convert the
byte array returned by getData() into a ByteArrayInputStream using this
constructor:

public ByteArrayInputStream(byte[] buffer, int offset, int length)

buffer is the byte array to be
used as an InputStream. It's
important to specify the portion of the buffer
that you want to use as an InputStream using the
offset and length arguments.
When converting datagram data into InputStream
objects, offset is either 0 (Java 1.1) or given by
the DatagramPacket object's
getOffset( ) method (Java 2), and
length is given by the
DatagramPacket object's
getLength( ) method. For example:

InputStream in = new ByteArrayInputStream(packet.getData( ), 
packet.getOffset( ), packet.getLength( ));

You must specify the offset
and the length when constructing the
ByteArrayInputStream. Do not use the
ByteArrayInputStream( ) constructor that takes
only an array as an argument. The array returned by
packet.getData( ) probably has extra space in it
that was not filled with data from the network. This space will
contain whatever random values those components of the array had when
the DatagramPacket was constructed.

The ByteArrayInputStream can then be chained to a
DataInputStream:

DataInputStream din = new DataInputStream(in);

The data can then be read using the
DataInputStream's
readInt( ), readLong( ),
readChar( ), and other methods. Of course, this
assumes that the datagram's sender uses the same
data formats as Java; it's probably the case when
the sender is written in Java, and is often (though not necessarily)
the case otherwise. (Most modern computers use the same floating
point format as Java, and most network protocols specify two
complement integers in network byte order, which also matches
Java's formats.)

13.2.2.5 public int getLength( )


The getLength( ) method returns
the number of bytes of data in the datagram. This is
not necessarily the same as the length of the
array returned by getData( ), i.e.,
getData( ).length. The int
returned by getLength( ) may be less than the
length of the array returned by getData( ).

13.2.2.6 public int getOffset( ) // Java 1.2


This method simply returns the point in
the array returned by getData( ) where the data
from the datagram begins.

Example 13-1 uses all the methods covered in this
section to print the information in the
DatagramPacket. This example is a little
artificial; because the program creates a
DatagramPacket, it already knows
what's in it. More often, you'll
use these methods on a DatagramPacket received
from the network, but that will have to wait for the introduction of
the DatagramSocket class in the next section.


Example 13-1. Construct a DatagramPacket to receive data


import java.net.*;
public class DatagramExample {
public static void main(String[] args) {
String s = "This is a test.";
byte[] data = s.getBytes( );
try {
InetAddress ia = InetAddress.getByName("www.ibiblio.org");
int port = 7;
DatagramPacket dp
= new DatagramPacket(data, data.length, ia, port);
System.out.println("This packet is addressed to "
+ dp.getAddress( ) + " on port " + dp.getPort( ));
System.out.println("There are " + dp.getLength( )
+ " bytes of data in the packet");
System.out.println(
new String(dp.getData( ), dp.getOffset( ), dp.getLength( )));
}
catch (UnknownHostException e) {
System.err.println(e);
}
}
}

Here's the output:

% java DatagramExample
This packet is addressed to www.ibiblio.org/152.2.254.81 on port 7
There are 15 bytes of data in the packet
This is a test.


13.2.3 The set Methods


Most of
the time, the six constructors are sufficient for creating datagrams.
However, Java also provides several methods for changing the data,
remote address, and remote port after the datagram has been created.
These methods might be important in a situation where the time to
create and garbage collect new DatagramPacket
objects is a significant performance hit. In some situations, reusing
objects can be significantly faster than constructing new ones: for
example, in a networked twitch game like Quake that sends a datagram
for every bullet fired or every centimeter of movement. However, you
would have to use a very speedy connection for the improvement to be
noticeable relative to the slowness of the network itself.

13.2.3.1 public void setData(byte[] data)


The setData( ) method changes
the payload of the UDP datagram. You might use this method if you are
sending a large file (where large is defined as
"bigger than can comfortably fit in one
datagram") to a remote host. You could repeatedly
send the same DatagramPacket object, just changing
the data each time.

13.2.3.2 public void setData(byte[] data, int offset, int length) // Java 1.2


This overloaded variant of the setData()
method provides an alternative approach to sending a large quantity
of data. Instead of sending lots of new arrays, you can put all the
data in one array and send it a piece at a time. For instance, this
loop sends a large array in 512-byte chunks:

int offset = 0;
DatagramPacket dp = new DatagramPacket(bigarray, offset, 512);
int bytesSent = 0;
while (bytesSent < bigarray.length) {
socket.send(dp);
bytesSent += dp.getLength( );
int bytesToSend = bigarray.length - bytesSent;
int size = (bytesToSend > 512) ? 512 : bytesToSend;
dp.setData(bigarray, bytesSent, 512);
}

On the other hand, this strategy requires either a lot of confidence
that the data will in fact arrive or, alternatively, a disregard for
the consequences of its not arriving. It's
relatively difficult to attach sequence numbers or other reliability
tags to individual packets when you take this approach.

13.2.3.3 public void setAddress(InetAddress remote)


The setAddress( ) method changes
the address a datagram packet is sent to. This might allow you to
send the same datagram to many different recipients. For example:

String s = "Really Important Message";
byte[] data = s.getBytes("ASCII");
DatagramPacket dp = new DatagramPacket(data, data.length);
dp.setPort(2000);
int network = "128.238.5.";
for (int host = 1; host < 255; host++) {
try {
InetAddress remote = InetAddress.getByName(network + host);
dp.setAddress(remote);
socket.send(dp);
}
catch (IOException ex) {
// slip it; continue with the next host
}
}

Whether this is a sensible choice depends on the application. If
you're trying to send to all the stations on a
network segment, as in this fragment, you'd probably
be better off using the local broadcast address and letting the
network do the work. The local broadcast address is determined by
setting all bits of the IP address after the network and subnet IDs
to 1. For example, Polytechnic University's network
address is 128.238.0.0. Consequently, its broadcast address is
128.238.255.255. Sending a datagram to 128.238.255.255 copies it to
every host on that network (although some routers and firewalls may
block it, depending on its origin).

For more widely separated hosts, you're probably
better off using multicasting. Multicasting actually uses the same
DatagramPacket class described here. However, it
uses different IP addresses and a MulticastSocket
instead of a DatagramSocket.
We'll discuss this further in Chapter 14.

13.2.3.4 public void setPort(int port)


The setPort( ) method changes
the port a datagram is addressed to. I honestly
can't think of many uses for this method. It could
be used in a port scanner application that tried to find open ports
running particular UDP-based services such as FSP. Another
possibility might be some sort of networked game or conferencing
server where the clients that need to receive the same information
are all running on different ports as well as different hosts. In
this case, setPort( ) could be used in conjunction
with setAddress( ) to change destinations before
sending the same datagram out again.

13.2.3.5 public void setAddress(SocketAddress remote) // Java 1.4


The setSocketAddress() method changes the
address and port a datagram packet is sent to. You can use this when
replying. For example, this code fragment receives a datagram packet
and responds to the same address with a packet containing the ASCII
string "Hello there":

DatagramPacket  input = newDatagramPacket(new byte[8192], 8192);
socket.receive(input);
SocketAddress address = input.getSocketAddress( );
DatagramPacket output = new DatagramPacket("Hello there"
.getBytes("ASCII"), 11);
output.setAddress(address);
socket.send(output);

You could certainly write the same code using
InetAddress objects and ports instead of a
SocketAddress. Indeed, in Java 1.3 and earlier,
you have to. The code would be just a few lines longer:

DatagramPacket  input = newDatagramPacket(new byte[8192], 8192);
socket.receive(input);
InetAddress address = input.getAddress( );
int port = input.getPort( );
DatagramPacket output = new DatagramPacket("Hello there".getBytes("ASCII"), 11);
output.setAddress(address);
output.setPort(port);
socket.send(output);

13.2.3.6 public void setLength(int length)


The setLength( ) method changes
the number of bytes of data in the internal buffer that are
considered to be part of the datagram's data as
opposed to merely unfilled space. This method is useful when
receiving datagrams, as we'll explore later in this
chapter. When a datagram is received, its length is set to the length
of the incoming data. This means that if you try to receive another
datagram into the same DatagramPacket,
it's limited to no more than the number of bytes in
the first. That is, once you've received a 10-byte
datagram, all subsequent datagrams will be truncated to 10 bytes;
once you've received a 9-byte datagram, all
subsequent datagrams will be truncated to 9 bytes; and so on. This
method lets you reset the length of the buffer so that subsequent
datagrams aren't truncated.


/ 164