📄 chapter15.html
字号:
called
<A NAME="Index2657"></A><A NAME="Index2658"></A><A NAME="Index2659"></A><A NAME="Index2660"></A><I>User
Datagram Protocol</I> (UDP), which doesn’t guarantee that the packets will
be delivered and doesn’t guarantee that they will arrive in the order they
were sent. It’s called an
“<A NAME="Index2661"></A><A NAME="Index2662"></A>unreliable
protocol” (TCP is a
<A NAME="Index2663"></A><A NAME="Index2664"></A>“reliable
protocol”), which sounds bad, but because it’s much faster it can be
useful. There are some applications, such as an audio signal, in which it
isn’t so critical if a few packets are dropped here or there but speed is
vital. Or consider a time-of-day server, where it really doesn’t matter if
one of the messages is lost. Also, some applications might be able to fire off a
UDP message to a server and can then assume, if there is no response in a
reasonable period of time, that the message was lost.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The support for datagrams in Java
has the same feel as its support for TCP sockets, but there are significant
differences. With datagrams, you put a
<A NAME="Index2665"></A><A NAME="Index2666"></A><B>DatagramSocket</B> on both
the client and server, but there is no analogy to the <B>ServerSocket</B> that
waits around for a connection. That’s because there is no
“connection,” but instead a datagram just shows up. Another
fundamental difference is that with TCP sockets, once you’ve made the
connection you don’t need to worry about who’s talking to whom
anymore; you just send the data back and forth through conventional streams.
However, with datagrams, the datagram packet must know where it came from and
where it’s supposed to go. That means you must know these things for each
datagram packet that you load up and ship off.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">A <B>DatagramSocket</B> sends and
receives the packets, and the
<A NAME="Index2667"></A><A NAME="Index2668"></A><B>DatagramPacket</B> contains
the information. When you’re receiving a datagram, you need only provide a
buffer in which the data will be placed; the information about the Internet
address and port number where the information came from will be automatically
initialized when the packet arrives through the <B>DatagramSocket</B>. So the
constructor for a <B>DatagramPacket </B>to receive datagrams
is:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>DatagramPacket(buf, buf.length)</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">in which <B>buf </B>is an array of
<B>byte</B>.<B> </B>Since <B>buf</B> is an array, you might wonder why the
constructor couldn’t figure out the length of the array on its own. I
wondered this, and can only guess that it’s a throwback to C-style
programming, in which of course arrays can’t tell you how big they
are.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">You can reuse a receiving datagram;
you don’t have to make a new one each time. Every time you reuse it, the
data in the buffer is overwritten.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The maximum size of the buffer is
restricted only by the allowable datagram packet size, which limits it to
slightly less than 64Kbytes. However, in many applications you’ll want it
to be much smaller, certainly when you’re sending data. Your chosen packet
size depends on what you need for your particular application.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">When you send a datagram, the
<B>DatagramPacket</B> must contain not only the data, but also the Internet
address and port where it will be sent. So the constructor for an outgoing
<B>DatagramPacket</B> is:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>DatagramPacket(buf, length, inetAddress, port)</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">This time, <B>buf</B> (which is a
<B>byte </B>array) already contains the data that you want to send out. The
<B>length</B> might be the length of <B>buf</B>, but it can also be shorter,
indicating that you want to send only that many bytes. The other two arguments
are the Internet address where the packet is going and the destination port
within that machine.</FONT><A NAME="fnB60" HREF="#fn60">[60]</A><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">You might think that the two
constructors create two different objects: one for receiving datagrams and one
for sending them. Good OO design would suggest that these should be two
different classes, rather than one class with different behavior depending on
how you construct the object. This is probably true, but fortunately the use of
<B>DatagramPacket</B>s is simple enough that you’re not tripped up by the
problem, as you can see in the following example. This example is similar to the
<B>MultiJabberServer</B> and <B>MultiJabberClient</B> example for TCP sockets.
Multiple clients will send datagrams to a server, which will echo them back to
the same client that sent the message.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">To simplify the creation of a
<B>DatagramPacket</B> from a <B>String</B> and vice-versa, the example begins
with a utility class, <B>Dgram</B>, to do the work for you:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: Dgram.java</font>
<font color=#009900>// A utility class to convert back and forth</font>
<font color=#009900>// Between Strings and DataGramPackets.</font>
<font color=#0000ff>import</font> java.net.*;
<font color=#0000ff>public</font> <font color=#0000ff>class</font> Dgram {
<font color=#0000ff>public</font> <font color=#0000ff>static</font> DatagramPacket toDatagram(
String s, InetAddress destIA, <font color=#0000ff>int</font> destPort) {
<font color=#009900>// Deprecated in Java 1.1, but it works:</font>
<font color=#0000ff>byte</font>[] buf = <font color=#0000ff>new</font> <font color=#0000ff>byte</font>[s.length() + 1];
s.getBytes(0, s.length(), buf, 0);
<font color=#009900>// The correct Java 1.1 approach, but it's</font>
<font color=#009900>// Broken (it truncates the String):</font>
<font color=#009900>// byte[] buf = s.getBytes();</font>
<font color=#0000ff>return</font> <font color=#0000ff>new</font> DatagramPacket(buf, buf.length,
destIA, destPort);
}
<font color=#0000ff>public</font> <font color=#0000ff>static</font> String toString(DatagramPacket p){
<font color=#009900>// The Java 1.0 approach:</font>
<font color=#009900>// return new String(p.getData(), </font>
<font color=#009900>// 0, 0, p.getLength());</font>
<font color=#009900>// The Java 1.1 approach:</font>
<font color=#0000ff>return</font>
<font color=#0000ff>new</font> String(p.getData(), 0, p.getLength());
}
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The first method of <B>Dgram</B>
takes a <B>String</B>, an <B>InetAddress,</B> and a port number and builds a
<B>DatagramPacket</B> by copying the contents of the <B>String</B> into a
<B>byte</B> buffer and passing the buffer into the <B>DatagramPacket</B>
constructor. Notice the “+1” in the buffer allocation – this
was necessary to prevent truncation. The <B>getBytes( )</B> method of
<B>String</B> is a special operation that copies the <B>char</B>s of a
<B>String</B> into a <B>byte</B> buffer. This method is now deprecated; Java
1.1<A NAME="Index2669"></A> has a “better” way to do this but
it’s commented out here because it truncates the <B>String</B>. So
you’ll get a deprecation message when you compile it under Java 1.1, but
the behavior will be correct. (This bug might be fixed by the time you read
this.)</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The <B>Dgram.toString( )</B>
method shows both the Java 1.0<A NAME="Index2670"></A> approach and the Java 1.1
approach (which is different because there’s a new kind of <B>String</B>
constructor). </FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Here is the server for the datagram
demonstration:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: ChatterServer.java</font>
<font color=#009900>// A server that echoes datagrams</font>
<font color=#0000ff>import</font> java.net.*;
<font color=#0000ff>import</font> java.io.*;
<font color=#0000ff>import</font> java.util.*;
<font color=#0000ff>public</font> <font color=#0000ff>class</font> ChatterServer {
<font color=#0000ff>static</font> <font color=#0000ff>final</font> <font color=#0000ff>int</font> INPORT = 1711;
<font color=#0000ff>private</font> <font color=#0000ff>byte</font>[] buf = <font color=#0000ff>new</font> <font color=#0000ff>byte</font>[1000];
<font color=#0000ff>private</font> DatagramPacket dp =
<font color=#0000ff>new</font> DatagramPacket(buf, buf.length);
<font color=#009900>// Can listen & send on the same socket:</font>
<font color=#0000ff>private</font> DatagramSocket socket;
<font color=#0000ff>public</font> ChatterServer() {
<font color=#0000ff>try</font> {
socket = <font color=#0000ff>new</font> DatagramSocket(INPORT);
System.out.println(<font color=#004488>"Server started"</font>);
<font color=#0000ff>while</font>(<font color=#0000ff>true</font>) {
<font color=#009900>// Block until a datagram appears:</font>
socket.receive(dp);
String rcvd = Dgram.toString(dp) +
<font color=#004488>", from address: "</font> + dp.getAddress() +
<font color=#004488>", port: "</font> + dp.getPort();
System.out.println(rcvd);
String echoString =
<font color=#004488>"Echoed: "</font> + rcvd;
<font color=#009900>// Extract the address and port from the</font>
<font color=#009900>// received datagram to find out where to</font>
<font color=#009900>// send it back:</font>
DatagramPacket echo =
Dgram.toDatagram(echoString,
dp.getAddress(), dp.getPort());
socket.send(echo);
}
} <font color=#0000ff>catch</font>(SocketException e) {
System.err.println(<font color=#004488>"Can't open socket"</font>);
System.exit(1);
} <font color=#0000ff>catch</font>(IOException e) {
System.err.println(<font color=#004488>"Communication error"</font>);
e.printStackTrace();
}
}
<font color=#0000ff>public</font> <font color=#0000ff>static</font> <font color=#0000ff>void</font> main(String[] args) {
<font color=#0000ff>new</font> ChatterServer();
}
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The <B>ChatterServer</B> contains a
single <B>DatagramSocket</B> for receiving messages, instead of creating one
each time you’re ready to receive a new message. The single
<B>DatagramSocket</B> can be used repeatedly. This <B>DatagramSocket</B> has a
port number because this is the server and the client must have an exact address
where it wants to send the datagram. It is given a port number but not an
Internet address because it resides on “this” machine so it knows
what its Internet address is (in this case, the default <B>localhost</B>). In
the infinite <B>while</B> loop, the <B>socket</B> is told to
<A NAME="Index2671"></A><A NAME="Index2672"></A><B>receive( )</B>,
whereupon it blocks until a datagram shows up, and then sticks it into our
designated receiver, the <B>DatagramPacket dp</B>. The packet is converted to a
<B>String</B> along with information about the Internet address and socket where
the packet came from. This information is displayed, and then an extra string is
added to indicate that it is being echoed back from the server.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Now there’s a bit of a
quandary. As you will see, there are potentially many different Internet
addresses and port numbers that the messages might come from – that is,
the clients can reside on any machine. (In this demonstration they all reside on
the <B>localhost</B>, but the port number for each client is different.) To send
a message back to the client that originated it, you need to know that
client’s Internet address and port number. Fortunately, this information
is conveniently packaged inside the
<A NAME="Index2673"></A><A NAME="Index2674"></A><B>DatagramPacket</B> that sent
the message, so all you have to do is pull it out using
<A NAME="Index2675"></A><A NAME="Index2676"></A><B>getAddress( )</B> and
<A NAME="Index2677"></A><A NAME="Index2678"></A><B>getPort( )</B>, which
are used to build the <B>DatagramPacket</B> <B>echo</B> that is sent back
through the same socket that’s doing the receiving. In addition, when the
socket sends the datagram, it automatically adds the Internet address and port
information of <I>this</I> machine, so that when the client receives the
message, it can use <B>getAddress( )</B> and <B>getPort( )</B> to find
out where the datagram came from. In fact, the only time that
<B>getAddress( )</B> and <B>getPort( )</B> don’t tell you wher
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -