尝试《Java Network Programming 4ed》Chapter 8 Using Sockets翻译

Data is transmitted across the Internet in packets of finite size called datagrams. Each datagram contains a header and a payload. The header contains the address and port to which the packet is going, the address and port from which the packet came, a checksum to detect data corruption, and various other housekeeping information used to ensure reliable transmission. The payload contains the data itself. However, because datagrams have a finite length, it’s often necessary to split the data across multiple packets and reassemble it at the destination. It’s also possible that one or more packets may be lost or corrupted in transit and need to be retransmitted or that packets arrive out of order and need to be reordered. Keeping track of this—splitting the data into packets, generating headers, parsing the headers of incoming packets, keeping track of what packets have and haven’t been received, and so on—is a lot of work and requires a lot of intricate code. Fortunately, you don’t have to do the work yourself. Sockets allow the programmer to treat a network connection as just another stream onto which bytes can be written and from which bytes can be read. Sockets shield the programmer from low-level details of the network, such as error detection, packet sizes, packet splitting, packet retransmission, network addresses, and more.

数据打包在一种有限大小的,叫做datagram在网络上传输。每个datagram包含一个头部和payload。头部包含数据包的目的地址端口,起始地址端口,一个检测数据(是否)损坏的checksum,和其他各种用来确保可靠传输的内部辅助信息。payload包含数据本身,但是因为datagrams有限大小,数据经常有必要被切分成多个包并在目的地重新组装。也有可能一或多个数据包在传输时丢失或者损坏了需要重新传输这些包,或者到达的顺序乱了需要重新排序。跟踪这些信息——把数据切成多个包,生成头部,解析头和接收到的数据包,跟踪什么包到达了什么包没到达等等——这些工作繁重需要很多复杂的编码。幸运的是你不必自己做这些工作。socket允许程序员像给其他stream读写字节那样去对待它。socket对程序员屏蔽了低层次网络的细节比如错误检测,数据包大小,切包,重传包,网络地址等。

Using Sockets

A socket is a connection between two hosts. It can perform seven basic operations:

socket是两个主机之间的连接,它可以做7种基本操作:

• Connect to a remote machine

连接远程主机

• Send data

传输数据

• Receive data

接收数据

• Close a connection

关闭连接

• Bind to a port

绑定端口

• Listen for incoming data

监听接收数据

• Accept connections from remote machines on the bound port

在远程机器的限制端口上接受连接

Java’s Socket class, which is used by both clients and servers, has methods that correspond to the first four of these operations. The last three operations are needed only by servers, which wait for clients to connect to them. They are implemented by the ServerSocket class, which is discussed in the next chapter. Java programs normally use client sockets in the following fashion:

客户端和服务端使用的Java的socket类有相应于以上前四种操作的方法。最后三种方法只在服务端使用,(并且)需要等待客户端连接。它们都实现了ServerSocket类,这些会在下一章讨论。Java程序一般以以下方式使用客户端socket:

• The program creates a new socket with a constructor.

程序用构造器创建一个新的socket

• The socket attempts to connect to the remote host.

socket接受了了远程主机的连接

Once the connection is established, the local and remote hosts get input and output streams from the socket and use those streams to send data to each other. This connection is full-duplex. Both hosts can send and receive data simultaneously. What the data means depends on the protocol; different commands are sent to an FTP server than to an HTTP server. There will normally be some agreed-upon handshaking followed by the transmission of data from one to the other. When the transmission of data is complete, one or both sides close the connection. Some protocols, such as HTTP 1.0, require the connection to be closed after each request is serviced. Others, such as FTP and HTTP 1.1, allow multiple requests to be processed in a single connection.

一旦建立连接,本地和远程主机会从socket获取输入输出流并且利用它们来相互发送数据。连接是full-duplex。两个主机能同时收发数据。协议决定了数据,发送到FTP服务端和发送到HTTP服务端的命令是不一样的。一般在数据发送到另一端前会有一些agreed-upon握手。当数据传输完毕,两方或者一方会关闭连接。一些诸如HTTP 1.0的协议,要求在两方处理请求后就关闭连接。其他像FTP和HTTP 1.1协议,允许在单个请求处理多个请求。

Investigating Protocols with Telnet

In this chapter, you’ll see clients that use sockets to communicate with a number of wellknown Internet services such as time, dict, and more. The sockets themselves are simple enough; however, the protocols to communicate with different servers make life complex. To get a feel for how a protocol operates, you can use Telnet to connect to a server, type different commands to it, and watch its responses. By default, Telnet attempts to connect to port 23. To connect to servers on different ports, specify the port you want to connect to like this:

本章中,你会看到客服端使用socket来和多个已知的网络服务,诸如时间,dict等等来实现交流。socket本身已足够简单,但是用于会不同服务交流的协议使这一切变得复杂。为了感受下协议是怎么工作的,你可以使用Telnet来连接服务端,敲入不同指令,观察它的反应。默认telnet连上23端口。为了在不同端口连上服务端,要像这样指定你要连上的端口:

$ telnet localhost 25

This requests a connection to port 25, the SMTP port, on the local machine; SMTP is the protocol used to transfer email between servers or between a mail client and a server. If you know the commands to interact with an SMTP server, you can send email without going through a mail program. This trick can be used to forge email. For example, some years ago, the summer students at the National Solar Observatory in Sunspot, New Mexico, made it appear that the party one of the scientists was throwing after the annual volleyball match between the staff and the students was in fact a victory party for the students. (Of course, the author of this book had absolutely nothing to do with such despicable behavior. ;-) ) The interaction with the SMTP server went something like this; input the user types is shown in bold (the names have been changed to protect the gullible):

这样就在25端口,本机的SMTP端口,请求连接了。SMTP协议用来在不同服务器,或者在邮件客户端和服务端之间传输邮件。如果你知道了和邮件服务器交互的命令,你可以不必通过邮件程序来发送邮件。这个技巧可以用来伪造邮件。比如几年前,the summer students at the National Solar Observatory in Sunspot, New Mexico, made it appear that the party one of the scientists was throwing after the annual volleyball match between the staff and the students was in fact a victory party for the students. (Of course, the author of this book had absolutely nothing to do with such despicable behavior. ;-) )。The interaction with the SMTP server went something like this; input the user types is shown in bold (the names have been changed to protect the gullible):

flare% telnet localhost 25

Trying 127.0.0.1 ...
Connected to localhost.sunspot.noao.edu.
Escape character is '^]'.
220 flare.sunspot.noao.edu Sendmail 4.1/SMI-4.1 ready at
Fri, 5 Jul 93 13:13:01 MDT
HELO sunspot.noao.edu
250 flare.sunspot.noao.edu Hello localhost [127.0.0.1], pleased to meet you
MAIL FROM: bart
250 bart... Sender ok
RCPT TO: [email protected]
250 [email protected]... Recipient ok
DATA
354 Enter mail, end with "." on a line by itself
In a pitiful attempt to reingratiate myself with the students
after their inevitable defeat of the staff on the volleyball
court at 4:00 P.M., July 24, I will be throwing a victory
party for the students at my house that evening at 7:00.
Everyone is invited.
Beer and Ben-Gay will be provided so the staff may drown
their sorrows and assuage their aching muscles after their
public humiliation.
Sincerely,
Bart
.
250 Mail accepted
QUIT
221 flare.sunspot.noao.edu delivering mail

Connection closed by foreign host.

Several members of the staff asked Bart why he, a staff member, was throwing a victory party for the students. The moral of this story is that you should never trust email, especially patently ridiculous email like this, without independent verification. In the 20 years since this happened, most SMTP servers have added a little more security than shown here. They tend to require usernames and passwords, and only accept connections from clients in the local networks and other trusted mail servers. However, it’s still the case that you can use Telnet to simulate a client, see how the client and the server interact, and thus learn what your Java program needs to do. Although this session doesn’t demonstrate all the features of the SMTP protocol, it’s sufficient to enable you to deduce how a simple email client talks to a server.

Reading from Servers with Sockets

Let’s begin with a simple example. You’re going to connect to the daytime server at the National Institute for Standards and Technology (NIST) and ask it for the current time. This protocol is defined in RFC 867. Reading that, you see that the daytime server listens on port 13, and that the server sends the time in a human-readable format and closes the connection. You can test the daytime server with Telnet like this:

我们以一个简单例子开始。你要连上位于National Institute for Standards and Technology (NIST)的时间服务器查询当前时间。这个协议定义在RFC 867。阅读它你会明白时间服务器监听了13端口,服务器返回了一个人们可以阅读的格式时间并关闭连接。你这样可以用telnet来测试时间服务器:

$ telnet time.nist.gov 13

Trying 129.6.15.28...

Connected to time.nist.gov.
Escape character is '^]'.
56375 13-03-24 13:37:50 50 0 0 888.8 UTC(NIST) *

Connection closed by foreign host.

The line “56375 13-03-24 13:37:50 50 0 0 888.8 UTC(NIST)” is sent by the daytime server. When you read the Socket’s InputStream, this is what you will get. The other lines are produced either by the Unix shell or by the Telnet program. RFC 867 does not specify any particular format for the output other than that it be human readable. In this case, you can see this connection was made on March 24, 2013, at 1:37: 50 P.M., Greenwich Meantime. More specifically, the format is defined as JJJJJ YY-MM-DD HH:MM:SS TT L H msADV UTC(NIST) OTM where:
• JJJJJ is the “Modified Julian Date” (i.e., it is the number of whole days since midnight on November 17, 1858).
• YY-MM-DD is the last two digits of the year, the month, and the current day of month.
• HH:MM:SS is the time in hours, minutes, and seconds in Coordinated Universal Time (UTC, essentially Greenwich Mean Time).
• TT indicates whether the United States is currently observing on Standard Time or Daylight Savings Time: 00 means standard time; 50 means daylight savings time. Other values count down the number of days until the switchover.
• L is a one-digit code that indicates whether a leap second will be added or subtracted at midnight on the last day of the current month: 0 for no leap second, 1 to add a leap second, and 2 to subtract a leap second.
• H represents the health of the server: 0 means healthy, 1 means up to 5 seconds off, 2 means more than 5 seconds off, 3 means an unknown amount of inaccuracy, and 4 is maintenance mode.
• msADV is a number of milliseconds that NIST adds to the time it sends to roughly compensate for network delays. In the preceding code, you can see that it added 888.8 milliseconds to this result, because that’s how long it estimates it’s going to take for the response to return.
• The string UTC(NIST) is a constant, and the OTM is almost a constant (an asterisk unless something really weird has happened).
These details are all NIST specific. They are not part of the daytime standard. Although they do offer a lot of data, if you have a real programmatic need to sync with a network time server, you’re better off using the NTP protocol defined in RFC 5905 instead. I’m not sure how long this example is going to work as shown here. These servers are overloaded, and I did have intermittent problems connecting while writing this chapter. In early 2013, NIST announced, “Users of the NIST DAYTIME protocol on tcp port 13 are

also strongly encouraged to upgrade to the network time protocol, which provides greater accuracy and requires less network bandwidth. The NIST time client (nistime-32bit.exe) supports both protocols. We expect to replace the tcp version of this protocol with a udpbased version near the end of 2013.” I’ll show you how to access this service over UDP in Chapter 11. Now let’s see how to retrieve this same data programmatically using sockets. First, open a socket to time.nist.gov on port 13:

这些细节不都是NIST规范。他们不是daytime标准的一部分。尽管他们提供了很多数据,如果你有编程需要来同步网络时间服务器,你用上RFC 5905的NTP协议反而更好。我不知道下面显示的这个例子要运行多久,(因为)这些服务器是超负荷的,在我写本章的时候就已经有断断续续的问题。早在2013年,NIST宣布,鼓励在TCP 13端口使用的NIST DAYTIME协议的用户升级这个网络时间协议,(升级后的协议)提供了更高精确度,(只需要)更少的网络带宽的服务。NIST时间客户端(nistime-32bit.exe)支持两个协议。我们期待用2013年末的UDP版本协议取代TCP版本的协议。

Socket socket = new Socket("time.nist.gov", 13);
This doesn’t just create the object. It actually makes the connection across the network. If the connection times out or fails because the server isn’t listening on port 13, then the constructor throws an IOException, so you’ll usually wrap this in a try block. In Java7, Socket implements Autocloseable so you can use try-with-resources:
try (Socket socket = new Socket("time.nist.gov", 13)) {
// read from the socket...
} catch (IOException ex) {
System.err.println("Could not connect to time.nist.gov");
}
In Java 6 and earlier, you’ll want to explicitly close the socket in a finally block to
release resources the socket holds:
Socket socket = null;
try {
socket = new Socket(hostname, 13);
// read from the socket...
} catch (IOException ex) {
System.err.println(ex);
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException ex) {
Using Sockets | 241
// ignore
}
}
}
The next step is optional but highly recommended. Set a timeout on the connection using the setSoTimeout() method. Timeouts are measured in milliseconds, so this statement sets the socket to time out after 15 seconds of nonresponsiveness:
socket.setSoTimeout(15000);
Although a socket should throw a ConnectException pretty quickly if the server rejects the connection, or a NoRouteToHostException if the routers can’t figure out how to send your packets to the server, neither of these help you with the case where a misbehaving server accepts the connection and then stops talking to you without actively closing the connection. Setting a timeout on the socket means that each read from or write to the socket will take at most a certain number of milliseconds. If a server hangs while you’re connected to it, you will be notified with a SocketTimeoutException. Exactly how long a timeout to set depends on the needs of your application and how responsive you expect the server to be. Fifteen seconds is a long time for a local intranet server to respond, but it’s rather short for an overloaded public server like time.nist.gov. Once you’ve opened the socket and set its timeout, call getInputStream() to return an InputStream you can use to read bytes from the socket. In general, a server can send
any bytes at all; but in this specific case, the protocol specifies that those bytes must be ASCII:
InputStream in = socket.getInputStream();
StringBuilder time = new StringBuilder();
InputStreamReader reader = new InputStreamReader(in, "ASCII");
for (int c = reader.read(); c != -1; c = reader.read()) {
time.append((char) c);
}
System.out.println(time);
Here I’ve stored the bytes in a StringBuilder. You can, of course, use any data structure that fits your problem to hold the data that comes off the network.
Example 8-1 puts this all together in a program that also allows you to choose a different daytime server.
Example 8-1. A daytime protocol client
import java.net.*;
import java.io.*;
public class DaytimeClient {
public static void main(String[] args) {
String hostname = args.length > 0 ? args[0] : "time.nist.gov";
Socket socket = null;
try {
socket = new Socket(hostname, 13);
socket.setSoTimeout(15000);
InputStream in = socket.getInputStream();
StringBuilder time = new StringBuilder();
InputStreamReader reader = new InputStreamReader(in, "ASCII");
for (int c = reader.read(); c != -1; c = reader.read()) {
time.append((char) c);
}
System.out.println(time);
} catch (IOException ex) {
System.err.println(ex);
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException ex) {
// ignore
}
}
}
}
}
Typical output is much the same as if you connected with Telnet:
$ java DaytimeClient
56375 13-03-24 15:05:42 50 0 0 843.6 UTC(NIST) *

As far as network-specific code goes, that’s pretty much it. In most network programs like this, the real effort is in speaking the protocol and comprehending the data formats. For instance, rather than simply printing out the text the server sends you, you might
want to parse it into a java.util.Date object instead. Example 8-2 shows you how to do this. For variety, I also wrote this example taking advantage of Java 7’s AutoCloseable and try-with-resources.
Example 8-2. Construct a Date by talking to time.nist.gov
import java.net.*;
import java.text.*;
import java.util.Date;
import java.io.*;
public class Daytime {
public Date getDateFromNetwork() throws IOException, ParseException {
try (Socket socket = new Socket("time.nist.gov", 13)) {
socket.setSoTimeout(15000);
InputStream in = socket.getInputStream();
StringBuilder time = new StringBuilder();
Using Sockets | 243
InputStreamReader reader = new InputStreamReader(in, "ASCII");
for (int c = reader.read(); c != -1; c = reader.read()) {
time.append((char) c);
}
return parseDate(time.toString());
}
}
static Date parseDate(String s) throws ParseException {
String[] pieces = s.split(" ");
String dateTime = pieces[1] + " " + pieces[2] + " UTC";
DateFormat format = new SimpleDateFormat("yy-MM-dd hh:mm:ss z");
return format.parse(dateTime);
}
}
Notice, however, this class doesn’t actually do anything with the network that
Example 8-1 didn’t do. It just added a bunch of code to turn strings into dates.
When reading data from the network, it’s important to keep in mind that not all protocols
use ASCII or even text. For example, the time protocol specified in RFC 868
specifies that the time be sent as the number of seconds since midnight, January 1, 1900,
Greenwich Mean Time. However, this is not sent as an ASCII string like 2,524,521,600
or –1297728000. Rather, it is sent as a 32-bit, unsigned, big-endian binary number.
The RFC never actually comes out and says that this is the format used.
It specifies 32 bits and assumes you know that all network protocols
use big-endian numbers. The fact that the number is unsigned can be
determined only by calculating the wraparound date for signed and
unsigned integers and comparing it to the date given in the specification
(2036). To make matters worse, the specification gives an example
of a negative time that can’t actually be sent by time servers that
follow the protocol. Time is a relatively old protocol, standardized in
the early 1980s before the IETF was as careful about such issues as it
is today. Nonetheless, if you find yourself implementing a not particularly
well-specified protocol, you may have to do a significant amount
of testing against existing implementations to figure out what you need
to do. In the worst case, different implementations may behave differently.
Because the time protocol doesn’t send back text, you can’t easily use Telnet to test such
a service, and your program can’t read the server response with a Reader or any sort of
readLine() method. A Java program that connects to time servers must read the raw
bytes and interpret them appropriately. In this example, that job is complicated by Java’s
lack of a 32-bit unsigned integer type. Consequently, you have to read the bytes one at
a time and manually convert them into a long using the bitwise operators << and |.
244 | Chapter 8: Sockets for Clients
Example 8-3 demonstrates. When speaking other protocols, you may encounter data
formats even more alien to Java. For instance, a few network protocols use 64-bit fixedpoint
numbers. There’s no shortcut to handle all possible cases. You simply have to grit
your teeth and code the math you need to handle the data in whatever format the server
sends.
Example 8-3. A time protocol client
import java.net.*;
import java.text.*;
import java.util.Date;
import java.io.*;
public class Time {
private static final String HOSTNAME = "time.nist.gov";
public static void main(String[] args) throws IOException, ParseException {
Date d = Time.getDateFromNetwork();
System.out.println("It is " + d);
}
public static Date getDateFromNetwork() throws IOException, ParseException {
// The time protocol sets the epoch at 1900,
// the Java Date class at 1970. This number
// converts between them.
long differenceBetweenEpochs = 2208988800L;
// If you'd rather not use the magic number, uncomment
// the following section which calculates it directly.
/*
TimeZone gmt = TimeZone.getTimeZone("GMT");
Calendar epoch1900 = Calendar.getInstance(gmt);
epoch1900.set(1900, 01, 01, 00, 00, 00);
long epoch1900ms = epoch1900.getTime().getTime();
Calendar epoch1970 = Calendar.getInstance(gmt);
epoch1970.set(1970, 01, 01, 00, 00, 00);
long epoch1970ms = epoch1970.getTime().getTime();
long differenceInMS = epoch1970ms - epoch1900ms;
long differenceBetweenEpochs = differenceInMS/1000;
*/
Socket socket = null;
try {
socket = new Socket(HOSTNAME, 37);
socket.setSoTimeout(15000);
InputStream raw = socket.getInputStream();
long secondsSince1900 = 0;
Using Sockets | 245
for (int i = 0; i < 4; i++) {
secondsSince1900 = (secondsSince1900 << 8) | raw.read();
}
long secondsSince1970
= secondsSince1900 - differenceBetweenEpochs;
long msSince1970 = secondsSince1970 * 1000;
Date time = new Date(msSince1970);
return time;
} finally {
try {
if (socket != null) socket.close();
}
catch (IOException ex) {}
}
}
}
Here’s the output of this program from a sample run:
$ java Time
It is Sun Mar 24 12:22:17 EDT 2013

The time protocol actually specifies Greenwich Mean Time, but the toString() method
in Java’s Date class, implicitly invoked by System.out.println(), converts this into
the time zone of the local host, Eastern Daylight Time in this case.

Writing to Servers with Sockets

Writing to a server is not noticeably harder than reading from one. You simply ask the socket for an output stream as well as an input stream. Although it’s possible to send data over the socket using the output stream at the same time you’re reading data over
the input stream, most protocols are designed so that the client is either reading or writing over a socket, not both at the same time. In the most common pattern, the client sends a request. Then the server responds. The client may send another request, and
the server responds again. This continues until one side or the other is done, and closes the connection. One simple bidirectional TCP protocol is dict, defined in RFC 2229. In this protocol, the client opens a socket to port 2628 on the dict server and sends commands such as “DEFINE eng-lat gold”. This tells the server to send a definition of the word gold using its English-to-Latin dictionary. (Different servers have different dictionaries installed.) After the first definition is received, the client can ask for another. When it’s done it sends the command “quit”. You can explore dict with Telnet like this:
$ telnet dict.org 2628
Trying 216.18.20.172...
Connected to dict.org.
246 | Chapter 8: Sockets for Clients
Escape character is '^]'.
220 pan.alephnull.com dictd 1.12.0/rf on Linux 3.0.0-14-server
<auth.mime> <[email protected]>
DEFINE eng-lat gold
150 1 definitions retrieved
151 "gold" eng-lat "English-Latin Freedict dictionary"
gold [gould]
aurarius; aureus; chryseus
aurum; chrysos
.
250 ok [d/m/c = 1/0/10; 0.000r 0.000u 0.000s]
DEFINE eng-lat computer
552 no match [d/m/c = 0/0/9; 0.000r 0.000u 0.000s]
quit
221 bye [d/m/c = 0/0/0; 42.000r 0.000u 0.000s]
You can see that control response lines begin with a three-digit code. The actual definition is plain text, terminated with a period on a line by itself. If the dictionary doesn’t contain the word you asked for, it returns 552 no match. Of course, you could also find
this out, and a lot more, by reading the RFC. It’s not hard to implement this protocol in Java. First, open a socket to a dict server— _dict.org__ is a good one—on port 2628:
Socket socket = new Socket("dict.org", 2628);
Once again you’ll want to set a timeout in case the server hangs while you’re connected to it:
socket.setSoTimeout(15000);
In the dict protocol, the client speaks first, so ask for the output stream using getOutputStream():
OutputStream out = socket.getOutputStream();
The getOutputStream() method returns a raw OutputStream for writing data from your application to the other end of the socket. You usually chain this stream to a more convenient class like DataOutputStream or OutputStreamWriter before using it. For performance reasons, it’s a good idea to buffer it as well. Because the dict protocol is text based, more specifically UTF-8 based, it’s convenient to wrap this in a Writer:
Writer writer = new OutputStreamWriter(out, "UTF-8");
Now write the command over the socket:
writer.write("DEFINE eng-lat gold\r\n");
Finally, flush the output so you’ll be sure the command is sent over the network:
writer.flush();
The server should now respond with a definition. You can read that using the socket’s input stream:
InputStream in = socket.getInputStream();
BufferedReader reader = new BufferedReader(
new InputStreamReader(in, "UTF-8"));
for (String line = reader.readLine();
!line.equals(".");
line = reader.readLine()) {
System.out.println(line);
}
When you see a period on a line by itself, you know the definition is complete. You can then send the quit over the output stream:
writer.write("quit\r\n");
writer.flush();
Example 8-4 shows a complete dict client. It connects to dict.org, and translates any words the user enters on the command line into Latin. It filters out all the metadata lines that begin with response codes such as 150 or 220. However, it does specifically check for a line that begins “552 no match” in case the server doesn’t recognize the word.
Example 8-4. A network-based English-to-Latin translator
import java.io.*;
import java.net.*;
public class DictClient {
public static final String SERVER = "dict.org";
public static final int PORT = 2628;
public static final int TIMEOUT = 15000;
public static void main(String[] args) {
Socket socket = null;
try {
socket = new Socket(SERVER, PORT);
socket.setSoTimeout(TIMEOUT);
OutputStream out = socket.getOutputStream();
Writer writer = new OutputStreamWriter(out, "UTF-8");
writer = new BufferedWriter(writer);
InputStream in = socket.getInputStream();
BufferedReader reader = new BufferedReader(
new InputStreamReader(in, "UTF-8"));
for (String word : args) {
define(word, writer, reader);
}
writer.write("quit\r\n");
writer.flush();
248 | Chapter 8: Sockets for Clients
} catch (IOException ex) {
System.err.println(ex);
} finally { // dispose
if (socket != null) {
try {
socket.close();
} catch (IOException ex) {
// ignore
}
}
}
}
static void define(String word, Writer writer, BufferedReader reader)
throws IOException, UnsupportedEncodingException {
writer.write("DEFINE eng-lat " + word + "\r\n");
writer.flush();
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
if (line.startsWith("250 ")) { // OK
return;
} else if (line.startsWith("552 ")) { // no match
System.out.println("No definition found for " + word);
return;
}
else if (line.matches("\\d\\d\\d .*")) continue;
else if (line.trim().equals(".")) continue;
else System.out.println(line);
}
}
}
Here’s a sample run:
$ java DictClient gold uranium silver copper lead
gold [gould]
aurarius; aureus; chryseus
aurum; chrysos
No definition found for uranium
silver [silvər]
argenteus
argentum
copper [kɔpər]
æneus; aheneus; ærarius; chalceus
æs
lead [led]
ducere
molybdus; plumbum
Example 8-4 is line oriented. It reads a line of input from the console, sends it to the server, and waits to read a line of output it gets back.

Half-closed sockets

The close() method shuts down both input and output from the socket. On occasion, you may want to shut down only half of the connection, either input or output. The shutdownInput() and shutdownOutput() methods close only half the connection:
public void shutdownInput() throws IOException
public void shutdownOutput() throws IOException
Neither actually closes the socket. Instead, they adjust the stream connected to the socket so that it thinks it’s at the end of the stream. Further reads from the input stream after shutting down input return –1. Further writes to the socket after shutting down output
throw an IOException. Many protocols, such as finger, whois, and HTTP, begin with the client sending a request to the server, then reading the response. It would be possible to shut down the output after the client has sent the request. For example, this code fragment sends a request to an HTTP server and then shuts down the output, because it won’t need to write anything else over this socket:
try (Socket connection = new Socket("www.oreilly.com", 80)) {
Writer out = new OutputStreamWriter(
connection.getOutputStream(), "8859_1");
out.write("GET / HTTP 1.0\r\n\r\n");
out.flush();
connection.shutdownOutput();
// read the response...
} catch (IOException ex) {
ex.printStackTrace();
}
Notice that even though you shut down half or even both halves of a connection, you still need to close the socket when you’re through with it. The shutdown methods simply affect the socket’s streams. They don’t release the resources associated with the socket, such as the port it occupies. The isInputShutdown() and isOutputShutdown() methods tell you whether the input and output streams are open or closed, respectively. You can use these (rather than isConnected() and isClosed()) to more specifically ascertain whether you can read from or write to a socket:
public boolean isInputShutdown()
public boolean isOutputShutdown()

你可能感兴趣的:(尝试《Java Network Programming 4ed》Chapter 8 Using Sockets翻译)