尝试《Java Network Programming 4ed》的部分翻译

来源:互联网 发布:c语言把字符a变成字符b 编辑:程序博客网 时间:2024/05/22 06:24

Buffers
In Chapter 2, I recommended that you always buffer your streams. Almost nothing has a greater impact on the performance of network programs than a big enough buffer. In the new I/O model, you’re no longer given the choice. All I/O is buffered. Indeed, the buffers are fundamental parts of the API. Instead of writing data onto output streams and reading data from input streams, you read and write data from buffers. Buffers may appear to be just an array of bytes as in buffered streams. However, native implementations can connect them directly to hardware or memory or use other, very efficien implementations.

在第二章,我推荐你(使用)stream要(用)缓存。在网络编程的性能上,几乎没有任何(因素)能比一个足够大的缓存更有巨大的影响。在new IO模型了,你别无选择。所有的IO都被缓存。事实上,缓存是基础API的一部分。写(数据)不用输出流,读(数据)不用输入流,buffer将他们取而代之。buffer看上去像是缓冲流里的字节数组。但是,本地可以将它直连硬件或内存等,做成非常有效率的实现。

From a programming perspective, the key difference between streams and channels is that streams are byte-based whereas channels are block-based. A stream is designed to provide one byte after the other, in order. Arrays of bytes can be passed for performance. However, the basic notion is to pass data one byte at a time. By contrast, a channel passes blocks of data around in buffers. Before bytes can be read from or written to a channel, the bytes have to be stored in a buffer, and the data is written or read one buffer at a time.

从编程视角看,流和channel的主要的不同点是流是基于字节的而channel是基于(数据)块的。流在顺序上被设计为字节挨着字节,出于性能的考量会传入字节数组,但是,其基本理念是每次传输数据是一个字节。相反,channel传输的是buffer里的一块数据,在字节可以被读取或写入channel前,字节必须存储在buffer,并且数据一次性从buffer读写。

The second key difference between streams and channels/buffers is that channels and buffers tend to support both reading and writing on the same object. This isn’t always true. For instance, a channel that points to a file on a CD-ROM can be read but not written. A channel connected to a socket that has shutdown input could be written but not read. If you try to write to a read-only channel or read from a write-only channel, an UnsupportedOperationException will be thrown. However, more often than not network programs can read from and write to the same channels.

流和channel的第二个关键的不同点是buffer往往支持同个对象的读写,这总是对的。比如,channel指向的一个CD-ROM的文件可读不可写。一个连接socket的,关闭了输入的channel可写不可读。如果你想试着往一个只读的channel写入数据,或者从一个只写的channel读出数据,则会抛出UnsupportedOperationException,而网络编程通常是可对同个channel读写。

Without worrying too much about the underlying details (which can vary hugely from one implementation to the next, mostly as a result of being tuned very closely to the host operating system and hardware), you can think of a buffer as a fixed-size list of elements of a particular, normally primitive data type, like an array. However, it’s not necessarily an array behind the scenes. Sometimes it is; sometimes it isn’t. There are specific subclasses of Buffer for all of Java’s primitive data types except boolean: ByteBuffer, CharBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuffer, and DoubleBuffer.

不必过多担心底层细节(which can vary hugely from one implementation to the next, mostly as a result of being tuned very closely to the host operating system and hardware),你大可认为buffer是一个像数组那样,固定元素数量,元素是原始数据类型的列表。但是,幕后未必是一个数组。有时是,有时不是。具体的Buffer的子类能对上除了boolean之外的java原始数据类型:ByteBuffer, CharBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuffer, 和 DoubleBuffer

The methods in each subclass have appropriately typed return values and argument lists. For example, the DoubleBuffer class has methods to put and get doubles. The IntBuffer class has methods to put and get ints. The common Buffer superclass only provides methods that don’t need to know the type of the data the buffer contains. (The lack of primitive-aware generics really hurts here.) Network programs use ByteBuffer almost exclusively, although occasionally one program might use a view that overlays the ByteBuffer with one of the other types. Besides its list of data, each buffer tracks four key pieces of information. All buffers have the same methods to set and get these values, regardless of the buffer’s type:

每个子类的方法,比如DoubleBuffer类有存取double的方法,IntBuffer类有存取int的方法,常用的buffer超类只提供了不需要知道buffer存储的数据类型的方法(没有使用原始数据泛型是一个伤口)网络编程几乎都现式使用了ByteBuffer,尽管有程序偶然使用了view,给ByteBuffer盖上一层其他类型(的buffer)。除了数据,buffer会记录四个主要的信息。所有的,不管是什么类型的buffer,都有相同的设置和获取这些值得方法:

Position

        The next location in the buffer that will be read from or written to. This starts counting at 0 and has a maximum value equal to the size of the buffer. It can be set or gotten with these two methods:

        buffer的下个位置将会被读出或者写入。这个变量会从0开始计起,最大数值等于buffer的大小。它可以通过如下两个方法来设置和获取:

        public final int position()

        public final Buffer position(int newPosition)

capacity

        The maximum number of elements the buffer can hold. This is set when the buffer is created and cannot be changed thereafter. It can be read with this method:

        buffer持有元素的最大数量。在创建buffer时设入并且此后不能改变。它可以通过这个方法可以读取这个变量:

        public final int capacity()

limit

         The end of accessible data in the buffer. You cannot write or read at or past this point without changing the limit, even if the buffer has more capacity. It is set and gotten with these two methods:

         buffer中最后一个可以操作的数据。即便buffer还有更多的容量,除非改变limit的值,否则你不能读取或写入(这个位置的)数据。它可以通过如下两个方法来设置和获取:

        public final int limit()

        public final Buffer limit(int newLimit)

mark

        A client-specified index in the buffer. It is set at the current position by invoking the mark() method. The current position is set to the marked position by invoking reset():

        一个"客户端指定"(译者也不明白该如何翻译)的buffer下标。调用mark()方法来把mark设为position变量的值。调用reset()方法把position变量设为mark变量的值。

        public final Buffer mark()

        public final Buffer reset()

        If the position is set below an existing mark, the mark is discarded.

        如果position的值低于已存在的mark,mark将失效。

        Unlike reading from an InputStream, reading from a buffer does not actually change the buffer’s data in any way. It’s possible to set the position either forward or backward so you can start reading from a particular place in the buffer. Similarly, a program can adjust the limit to control the end of the data that will be read. Only the capacity is fixed. The common Buffer superclass also provides a few other methods that operate by reference to these common properties. The clear() method “empties” the buffer by setting the position to zero and the limit to the capacity. This allows the buffer to be completely refilled:

        不像从InputStream读取那样,从buffer读取数据实际上无论怎样都不会改变buffer的数据。你可以将position变量设得更前或更后,你便可以读取buffer的某个特定位置。类似地,程序可以调整limit变量来控制可读取的数据的末尾位置。只有capacity变量是固定不变的。超类Buffer提供了少量其他的方法来操作(上述)这些公共属性。clear方法会把position变量设为0,limit变量设为capacity来“清空”buffer。这样使得整个buffer可以再次填充数据。

        public final Buffer clear()

        However, the clear() method does not remove the old data from the buffer. It’s still present and could be read using absolute get methods or changing the limit and position again. The rewind() method sets the position to zero, but does not change the limit:

        然而clear方法没有删除buffer的旧数据。旧数据依旧存在,用absolute get方法可以再次读取这些数据,或者改变limit和position的值。rewind()方法把position设为0,但不会改变limit变量。

        public final Buffer rewind()

        This allows the buffer to be reread.

      (上面)这个方法使buffer被允许重读。

        The flip() method sets the limit to the current position and the position to zero:

        flip()方法把limit设为当前position的值,并且把position变量设为0:

        public final Buffer flip()

        It is called when you want to drain a buffer you’ve just filled.

        当你想消耗buffer里刚填充的变量,(上面)这个方法会被调用。

        Finally, there are two methods that return information about the buffer but don’t change it. The remaining() method returns the number of elements in the buffer between the current position and the limit. The hasRemaining() method returns true if the number of remaining elements is greater than zero

       最终,还有两个方法可以返回buffer的信息而不改变buffer。remaining()方法返回在buffer的(下标在)当前position和limit之间的(剩余)元素个数。如果剩余元素超过0,hasRemaining()方法返回true

        public final int remaining()

        public final boolean hasRemaining()

-----------------------------------------------------------------------------------

An Example Client
Although the new I/O APIs aren’t specifically designed for clients, they do work for them. I’m going to begin with a client program using the new I/O APIs because it’s a little simpler. In particular, many clients can be implemented with one connection at a time, so

I can introduce channels and buffers before talking about selectors and nonblocking I/O.

尽管new I/O API不是为客户端特别设计的,但是(客户端使用它)依然有效。我将以一个客户端程序来开始(阐述)new I/O API的使用,因为它比较简单。特别地,许多的客户端的实现是一次一个连接,所以我可以在阐述selector和非阻塞IO之前由此引入channel和buffer。

A simple client for the character generator protocol defined in RFC 864 will demonstrate the basics. This protocol is designed for testing clients. The server listens for connections on port 19. When a client connects, the server sends a continuous sequence of

characters until the client disconnects. Any input from the client is ignored. The RFC does not specify which character sequence to send, but recommends that the server use a recognizable pattern. One common pattern is rotating, 72-character carriage return/linefeed delimited lines of the 95 ASCII printing characters, like this:

一个用来产生RFC 864的客户端字符生成器将会用来展示基本的用法。这个协议是用来测试客户端的,服务器会监听19端口.当有客户端连接,服务器会发送一系列连续的字符,知道客户端断开连接。客户端的任何输入都会被忽略。不会有哪个具体的RFC字符用来发送,但是服务端会被要求使用一个可识别的模式来发送他们。One common pattern is rotating, 72-character carriage return/linefeed delimited lines of the 95 ASCII printing characters, like this:

!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefgh
"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghi
#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghij
$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijk
%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijkl
&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklm

I picked this protocol for the examples in this chapter because both the protocol for transmitting the data and the algorithm to generate the data are simple enough that they won’t obscure the I/O. However, chargen can transmit a lot of data over a relatively few connections and quickly saturate a network. It’s thus a good candidate for the new I/O APIs.

我选用这个协议作为例子是因为传输数据的协议本身和算法都足够的简单,不会使得I/O难以理解。然而,chargen可以通过很少的连接提交许多数据来使得网络(带宽)迅速饱和。所以它是个很好的候选例子来阐述new IO。

Chargen is not commonly used these days, and may be blocked by local firewalls even if it’s turned on. It’s vulnerable to a “ping-pong” denialof-service attack, in which spoofed Internet packets cause two hosts to spew an unlimited amount of data at each other.

Furthermore, because it’s almost infinitely asymmetric—the server sends an unlimited amount of data in response to the smallest of client requests—it’s very easy for even a few dozen compromised hosts, much less a large botnet, to convince a chargen server to saturate its local bandwidth. An Example Client | 349 When implementing a client that takes advantage of the new I/O APIs, begin by invoking the static factory method SocketChannel.open() to create a new java.nio.channels.SocketChannel object. The argument to this method is a java.net.SocketAd dress object indicating the host and port to connect to. For example, this fragment connects the channel to rama.poly.edu on port 19:

SocketAddress rama = new InetSocketAddress("rama.poly.edu", 19);
SocketChannel client = SocketChannel.open(rama);

The channel is opened in blocking mode, so the next line of code won’t execute until the connection is established. If the connection can’t be established, an IOException is thrown. If this were a traditional client, you’d now ask for the socket’s input and/or output streams. However, it’s not. With a channel you write directly to the channel itself. Rather than writing byte arrays, you write ByteBuffer objects. You’ve got a pretty good idea that the lines of text are 74 ASCII characters long (72 printable characters followed by a carriage return/linefeed pair) so you’ll create a ByteBuffer that has a 74-byte capacity using the static allocate() method:

channel使用阻塞模式打开,所以在连接建立前下一行代码不会被执行。如果连接不被建立,IOException就会被抛出。如果这里用的是传统的客户端(代码),你现在就会使用到socket的输入输出流。然而,现在不是(这种情况),(现在)用的是(客户端的)channe来写入(服务端的)channel本身。(写入的)不是byte数组,而是ByteBuffer对象。用74个ASCALL组成的文本行(72个可打印字符,紧随其后的是2个回车/换行字符对)是个不错的注意,因此使用了allocate()方法创建了74个byte容量的ByteBuffer。

ByteBuffer buffer = ByteBuffer.allocate(74);

Pass this ByteBuffer object to the channel’s read() method. The channel fills this buffer with the data it reads from the socket. It returns the number of bytes it successfully read and stored in the buffer:

将ByteBuffer传入channel的read()方法,channel会用从socket读来的数据填充buffer,返回成功读取的字节的数目,并将其存储在buffer。

int bytesRead = client.read(buffer);

By default, this will read at least one byte or return –1 to indicate the end of the data, exactly as an InputStream does. It will often read more bytes if more bytes are available to be read. Shortly you’ll see how to put this client in nonblocking mode where it will return 0 immediately if no bytes are available, but for the moment this code blocks just like an InputStream. As you could probably guess, this method can also throw an IOException if anything goes wrong with the read. Assuming there is some data in the buffer—that is, n > 0—this data can be copied to System.out. There are ways to extract a byte array from a ByteBuffer that can then be written on a traditional OutputStream such as System.out. However, it’s more informative to stick with a pure, channel-based solution. Such a solution requires wrapping the OutputStream System.out in a channel using the Channels utility class, specifically, its newChannel() method:

默认这会读取至少一个字节或者返回-1用来指示读到数据的尾部,就像输入流一样。如果有更多可读取的字节就会读到更多。很快的你会看到在阻塞模式下如何部署客户端程序,在没有数据可以读取时迅速返回0,但是目前还是使用阻塞的代码,像InputStream一样。正如你猜想那样,当数据读取出错这个方法会抛出IOExceptio。试想buffer有些数据——就是说,n>0——这些数据会被复制到System.out。有方法可以将ByteBuffer的字节数组提取出来写入到传统的输出流,比如System.out。但是,单纯用基于channel的解决方案会更不错。这样方法需要使用Channel工具类对channel里的输出流System.out进行封装,具体就是newChannel()方法了:

WritableByteChannel output = Channels.newChannel(System.out);

You can then write the data that was read onto this output channel connected to System.out. However, before you do that, you have to flip the buffer so that the output channel starts from the beginning of the data that was read rather than the end:

你能把数据读入和System.out连接的输出channel里,然而,在你这样做之前,必须flip buffer,这样输出channel才可以从数据的开头开始读而不是结尾:

buffer.flip();output.write(buffer);

You don’t have to tell the output channel how many bytes to write. Buffers keep track of how many bytes they contain. However, in general, the output channel is not guaranteed to write all the bytes in the buffer. In this specific case, though, it’s a blocking channel and it will either do so or throw an IOException. You shouldn’t create a new buffer for each read and write. That would kill the performance. Instead, reuse the existing buffer. You’ll need to clear the buffer before reading into it again:

你不必告知输出channel有多少写入的字节,Buffer会追踪它包含的字节数量。但是,一般输出channel不会保证所有的数据都写入buffer。在特殊情况下,尽管它是阻塞channel,它要么这样做要么抛出IOException,你不应该为每次读写创建新的buffer,这样会会消耗性能。相反的要重复使用现有的buffer,在读取数据到buffer的时候要做清除操作。

buffer.clear();

This is a little different than flipping. Flipping leaves the data in the buffer intact, but prepares it for writing rather than reading. Clearing resets the buffer to a pristine state. (Actually that’s a tad simplistic. The old data is still present; it’s not overwritten, but it will be overwritten with new data read from the source as soon as possible.)

这个和flip操作有点差别。flip完整保留buffer里的数据是为了写入而不是读取。clear重置buffer是为了回到初始状态(事实上这样讲有些过于简单化了,旧数据没有被覆盖依旧存在,但是只要新的数据从源头来到时就会立即被覆盖).

Example 11-1 puts this together into a complete client. Because chargen is by design an endless protocol, you’ll need to kill the program using Ctrl-C.

例子11-1综上所述形成了一个完整的客户端程序,chargen使用了无休止的(字符打印)协议,你需要按Ctrl-C来终止运行。

Example 11-1. A channel-based chargen client

import java.nio.*;import java.nio.channels.*;import java.net.*;import java.io.IOException;public class ChargenClient {public static int DEFAULT_PORT = 19;public static void main(String[] args) {if (args.length == 0) {System.out.println("Usage: java ChargenClient host [port]");return;}int port;try {port = Integer.parseInt(args[1]);} catch (RuntimeException ex) {port = DEFAULT_PORT;}try {SocketAddress address = new InetSocketAddress(args[0], port);SocketChannel client = SocketChannel.open(address);ByteBuffer buffer = ByteBuffer.allocate(74);WritableByteChannel out = Channels.newChannel(System.out);while (client.read(buffer) != -1) {buffer.flip();out.write(buffer);buffer.clear();}} catch (IOException ex) {ex.printStackTrace();}}}


Here’s the output from a sample run:
$ java ChargenClient rama.poly.edu
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefgh
"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghi
#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghij
$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijk
%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijkl
&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklm
...
So far, this is just an alternate vision of a program that could have easily been written using streams. The really new feature comes if you want the client to do something besides copying all input to output. You can run this connection in either blocking or nonblocking mode in which read() returns immediately even if no data is available. This allows the program to do something else before it attempts to read. It doesn’t have to wait for a slow network connection. To change the blocking mode, pass true (block) or false (don’t block) to the configureBlocking() method. Let’s make this connection  nonblocking:

目前为止,这仅仅是一个使用流的程序的可替换版本,如果客户端不止用来复制输入流的数据到输出流,那你就不得不面对新的特性。你可以在阻塞和非阻塞模式下跑起这个程序来连接(服务端),如果没有数据可读read()方法会迅速返回0,(这样)程序就能在它尝试读取(数据)之前做其他事情,(而)不必等待缓慢的网络连接。传入true(阻塞)或者false(非阻塞)到configureBlocking()方法里来改变阻塞模式,我们这里设置连接为非阻塞模式:

client.configureBlocking(false);

In nonblocking mode, read() may return 0 because it doesn’t read anything. Therefore, the loop needs to be a little different:

在非阻塞模式下,read()也许会返回0,因为读不到任何东西,所以循环(的代码)会变得有点不一样:

while (true) {// Put whatever code here you want to run every pass through the loop// whether anything is read or notint n = client.read(buffer);if (n > 0) {buffer.flip();out.write(buffer);buffer.clear();} else if (n == -1) {// This shouldn't happen unless the server is misbehaving.break;}}


There’s not a lot of call for this in a one-connection client like this one. Perhaps you could check to see if the user has done something to cancel input, for example. However, as you’ll see in the next section, when a program is processing multiple connections, this enables code to run very quickly on the fast connections and more slowly on the slow ones. Each connection gets to run at its own speed without being held up behind the slowest driver on the one-lane road.

在像本例的单连接的客户端程序中并没有很多的调用,也许你需要检查下用户有没做些取消输入的操作。好吧,下一节你会看到,当程序在处理多连接的时候,建立的快的连接,代码跑的很快,而慢的连接则很慢。Each connection gets to run at its own speed without being held up behind the slowest driver on the one-lane road.

----------------------------------------------------------------------------------------------------------------

View Buffers
If you know the ByteBuffer read from a SocketChannel contains nothing but elements of one particular primitive data type, it may be worthwhile to create a view buffer. This is a new Buffer object of appropriate type (e.g., DoubleBuffer, IntBuffer, etc.), that draws its data from an underlying ByteBuffer beginning with the current position. Changes to the view buffer are reflected in the underlying buffer and vice versa. However, each buffer has its own independent limit, capacity, mark, and position. View buffers are created with one of these six methods in ByteBuffer:

如果你知道从一个SocketChannel读来的ByteBuffer只是包含了一种特定的原始数据类型,那么创建一个view buffer就很值得了。这是一个适用于某个数据类型新的Buffer对象(比如DoubleBuffer,IntBuffer),它将底层byteBuffer的当前position之后的数据做了转化。view buffer的变化会反射到底层的buffer,反之亦然。然而每个buffer有他独立的limit,capacity,mark和position变量。可以用(下面)这六个方法来创建view buffer。

public abstract ShortBuffer asShortBuffer()
public abstract CharBuffer asCharBuffer()
public abstract IntBuffer asIntBuffer()
public abstract LongBuffer asLongBuffer()
public abstract FloatBuffer asFloatBuffer()
public abstract DoubleBuffer asDoubleBuffer()

For example, consider a client for the Intgen protocol. This protocol is only going to read ints, so it may be helpful to use an IntBuffer rather than a ByteBuffer. Example 11-4 demonstrates. For variety, this client is synchronous and blocking, but it still uses channels and buffers.

比如,设想有一个使用整型数据协议的客户端。这个协议只会读整型数据,所以使用IntBuffer会比使用ByteBuffer也许会更有帮助。如例子11-4所示。来了点变化,客户端是同步阻塞的,但依旧使用channel和buffer。

Example 11-4. Intgen client

import java.nio.*;import java.nio.channels.*;import java.net.*;import java.io.IOException;public class IntgenClient {public static int DEFAULT_PORT = 1919;public static void main(String[] args) {if (args.length == 0) {System.out.println("Usage: java IntgenClient host [port]");return;}int port;try {port = Integer.parseInt(args[1]);} catch (RuntimeException ex) {port = DEFAULT_PORT;}try {SocketAddress address = new InetSocketAddress(args[0], port);SocketChannel client = SocketChannel.open(address);ByteBuffer buffer = ByteBuffer.allocate(4);IntBuffer view = buffer.asIntBuffer();for (int expected = 0; ; expected++) {client.read(buffer);int actual = view.get();buffer.clear();view.rewind();if (actual != expected) {System.err.println("Expected " + expected + "; was " + actual);break;}System.out.println(actual);}} catch(IOException ex) {ex.printStackTrace();}}}


There’s one thing to note here. Although you can fill and drain the buffers using the methods of the IntBuffer class exclusively, data must be read from and written to the channel using the original ByteBuffer of which the IntBuffer is a view. The SocketChannel class only has methods to read and write ByteBuffers. It cannot read or write any other kind of buffer. This also means you need to clear the ByteBuffer on each pass through the loop or the buffer will fill up and the program will halt. The positions and limits of the two buffers are independent and must be considered separately. Finally, if you’re working in nonblocking mode, be careful that all the data in the underlying ByteBuffer is drained before reading or writing from the overlaying view buffer. Nonblocking mode provides no guarantee that the buffer will still be aligned on an int, double, or char. boundary following a drain. It’s completely possible for a nonblocking channel to write half the bytes of an int or a double. When using nonblocking I/O, besure to check for this problem before putting more data in the view buffer.

这里有一件事需要注意下,尽管你可以显式使用IntBuffer类的方法来填充和消化buffer,但是往channel读出或者写入数据的IntBuffer必须是(能对应上)原始ByteBuffer的view buffer。SocketChannel类只有ByteBuffer的读和写的方法,它不能用来读写其他种类的buffer。这就意味着你在每次循环,buffer被填满,或者程序退出时需要清除ByteBuffer。两个buffer的position和limit变量需要分别考虑因为他们相互独立。最后如果你使用了非阻塞模式,注意ByteBuffer底层的所有数据要在从上层的view buffer读写前消化干净。非阻塞模式不能保证buffer在消化后的边界依旧是按int,double,char对齐(不知道怎么翻译),(因为)非阻塞channel是完全有可能写入半个int或者double,(所以)在使用非阻塞IO时候要确保在往view buffer存入数据前是否存在这样的问题。

--------------------------------------------------------------------------------------------------

An Example Server
Clients are well and good, but channels and buffers are really intended for server systems that need to process many simultaneous connections efficiently. Handling servers requires a third new piece in addition to the buffers and channels used for the client. Specifically, you need selectors that allow the server to find all the connections that are ready to receive output or send input. To demonstrate the basics, this example implements a simple server for the character generator protocol. When implementing a server that takes advantage of the new I/O APIs, begin by calling the static factory method ServerSocketChannel.open() method to create a new ServerSocketChannel object:

客户端的程序能很好运行,但是channel和buffer是准备打算用在服务端的系统上,用来有效处理同时(发起)的连接。服务端的处理除了客户端的buffer和channel还需要第三个新的东西(原谅我这么翻译)。明确的讲,你需要selector来允许服务器找到所有准备好接收和发送的连接。为了演示基本的用法,这个例子实现了一个面向字符产生器协议的服务端。(我们)调用了静态工厂方法ServerSocketChannel.open()来创建一个新的ServerSocketChannel对象,利用new IO API开始实现了一个服务端:

ServerSocketChannel serverChannel = ServerSocketChannel.open();

Initially, this channel is not actually listening on any port. To bind it to a port, retrieve its ServerSocket peer object with the socket() method and then use the bind() method on that peer. For example, this code fragment binds the channel to a server socket on port 19:

最初channel实际上没用监听任何端口。为了绑定一个端口,(我们)用socket()方法获取ServerSocket对象,然后调用ServerSocket的bind()方法。举个例子,下面的代码片段将服务端socket的channel端口绑定为19:

ServerSocket ss = serverChannel.socket();ss.bind(new InetSocketAddress(19));

In Java 7 and later, you can bind directly without retrieving the underlying java.net.ServerSocket:

在java7及其更新的版本,你可以直接不用获取底层的java.net.ServerSocket:来做绑定:

serverChannel.bind(new InetSocketAddress(19)); 

As with regular server sockets, binding to port 19 requires you to be root on Unix (including Linux and Mac OS X). Nonroot users can only bind to ports 1024 and higher. The server socket channel is now listening for incoming connections on port 19. To accept one, call the accept() method, which returns a SocketChannel object:

正如常规的服务端socket,绑定19端口要求在unix系统上你是root账户(包括linux和Mac OS X)。非root用户只能绑定1024或者更高的端口。服务端socket channel现在可以在19端口上监听紧接而来的连接了。调用accept()方法接收一个请求并返回一个SocketChannel对象。

SocketChannel clientChannel = serverChannel.accept();

On the server side, you’ll definitely want to make the client channel nonblocking to allow the server to process multiple simultaneous connections:

在服务端,你肯定要使客户端channel成为非阻塞的,从而允许服务端能处理多个同时的请求

clientChannel.configureBlocking(false); 

You may also want to make the ServerSocketChannel nonblocking. By default, this accept() method blocks until there’s an incoming connection, like the accept() method of ServerSocket. To change this, simply call configureBlocking(false) before calling accept():

你也许还想要让ServerSocketChannel非阻塞。默认情况下acespt是一直阻塞直到有请求过来,像ServerSocket的accept()方法。为了改变(这种境地),调用accept()前简单调用configureBlocking(false)即可。

serverChannel.configureBlocking(false);

A nonblocking accept() returns null almost immediately if there are no incoming connections. Be sure to check for that or you’ll get a nasty NullPointerException when trying to use the socket. There are now two open channels: a server channel and a client channel. Both need to be processed. Both can run indefinitely. Furthermore, processing the server channel will create more open client channels. In the traditional approach, you assign each connection a thread, and the number of threads climbs rapidly as clients connect. Instead, in the new I/O API, you create a Selector that enables the program to iterate over all the connections that are ready to be processed. To construct a new Selector, just call the static Selector.open() factory method:

如果没有请求过来,非阻塞的accept()近乎神速的返回null。用这个socket要做下检查(null)否则你会得到你不想要的NullPointerException。现在有两个打开的channel:一个服务端和一个客户端的channel。两个都要做处理,两个都能无休止跑下去。此外,处理服务端channel将会创建更多的客户端channel。在传统的方法里,你赋予每条线程一个连接,而这些连接数量随着客户端连接将会迅速攀升。在new IO API,这一做法被替代,你创建了一个Selector来让程序遍历所有准备好被处理的连接。调用静态工厂方法Selector.open()来构造一个新的Selector:

Selector selector = Selector.open();
Next, you need to register each channel with the selector that monitors it using the channel’s register() method. When registering, specify the operation you’re interested in using a named constant from the SelectionKey class. For the server socket, the only
operation of interest is OP_ACCEPT; that is, is the server socket channel ready to accept a new connection?

接下来你需要把selector注册到各个channel里,使用channel的register()方法来监听(各个channel)。注册完毕后,用SelectionKey类的常量变(再次原谅译者)量指定你感兴趣的操作。对于服务端socket,唯一的操作是OP_ACCEPT,也就是服务端socket装备接受一个新的请求

serverChannel.register(selector, SelectionKey.OP_ACCEPT);
For the client channels, you want to know something a little different—specifically, whether they’re ready to have data written onto them. For this, use the OP_WRITE key:

对于客户端channel,你要知道一点不一样(的情况),特别是这种,(客户端)是否准备好(服务端)可以写数据给他,这种情况要用OP_WRITE关键词:

SelectionKey key = clientChannel.register(selector, SelectionKey.OP_WRITE);
Both register() methods return a SelectionKey object. However, you’re only going to need to use that key for the client channels, because there can be more than one of them. Each SelectionKey has an attachment of arbitrary Object type. This is normally
used to hold an object that indicates the current state of the connection. In this case, you can store the buffer that the channel writes onto the network. Once the buffer is fully drained, you’ll refill it. Fill an array with the data that will be copied into each buffer.
Rather than writing to the end of the buffer, and then rewinding to the beginning of the buffer and writing again, it’s easier just to start with two sequential copies of the data so every line is available as a contiguous sequence in the array:

两种register()方法都返回一个SelectionKey对象。但是客户端channel你只能使用(OP_WRITE)这个关键词,因为他们(他们是谁?)不止一个。每个SelectionKey有一个任意类型的附属对象,这个对象经常是被(channel)持有以显示当前连接的状态。这样的话你就可以存储那个写入网络的buffer。一旦buffer被消化,它将被重新填充,将一个数组的数据复制到各自的buffer。(我们)不是在buffer末尾写然后倒回buffer的开头再写,而是复制两条数据所以访问每行数据时就像一条连续的数组。(译者单看这句话不明白,还要结合下面的程序片段)

byte[] rotation = new byte[95*2];for (byte i = ' '; i <= '~'; i++) {rotation[i - ' '] = i;rotation[i + 95 - ' '] = i;}

Because this array will only be read from after it’s been initialized, you can reuse it for multiple channels. However, each channel will get its own buffer filled with the contents of this array. You’ll stuff the buffer with the first 72 bytes of the rotation array, then add a carriage return/linefeed pair to break the line. Then you’ll flip the buffer so it’s ready for draining, and attach it to the channel’s key:

因为这个数组只有在初始化后才能被读取,所以你可以在多个channel重用。但是,每个channel都会获取到被这个数组数据填满的buffer。buffer会被这个旋转数组开头的72个字符所填满,然后再加上回车/换行字符对来分行。然后你就可以对buffer做下flip操作,(buffer)做好被消化的准备,并且将(buffer)附随在channel的key上

ByteBuffer buffer = ByteBuffer.allocate(74);buffer.put(rotation, 0, 72);buffer.put((byte) '\r');buffer.put((byte) '\n');buffer.flip();key2.attach(buffer);

To check whether anything is ready to be acted on, call the selector’s select() method. For a long-running server, this normally goes in an infinite loop:

调用selector的select()方法检查是否一切都做好执行的准备。对于一个长期运行的服务端,一般运行了一个死循环:

while (true) {selector.select ();// process selected keys...}

Assuming the selector does find a ready channel, its selectedKeys() method returns a java.util.Set containing one SelectionKey object for each ready channel. Otherwise, it returns an empty set. In either case, you can loop through this with a java.util.Iterator:

设想selector找到了一个准备好的channel,(selector的)selectedKeys()方法返回了一个包含每个准备好channel的SelectionKey对象的java.util.Set,否则返回空集合。在这两种情况下,你都可以用java.util.Iterator来循环遍历。

Set<SelectionKey> readyKeys = selector.selectedKeys();Iterator iterator = readyKeys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();// Remove key from set so we don't process it twiceiterator.remove();// operate on the channel...}

Removing the key from the set tells the Selector that you’ve dealt with it, and the Selector doesn’t need to keep giving it back every time you call select(). The Selector will add the channel back into the ready set when select() is called again if the channel becomes ready again. It’s really important to remove the key from the ready set here, though. If the ready channel is the server channel, the program accepts a new socket channel and adds it to the selector. If the ready channel is a socket channel, the program writes as much of the buffer as it can onto the channel. If no channels are ready, the selector waits for one. One thread, the main thread, processes multiple simultaneous connections. In this case, it’s easy to tell whether a client or a server channel has been selected because the server channel will only be ready for accepting and the client channels will only be ready for writing. Both of these are I/O operations, and both can throw IOExceptions for a variety of reasons, so you’ll want to wrap this all in a try block:

从集合移除key告诉了selector你已经处理过它了,selector没有必要每次调用select()就返回它。当select()方法被再次调用,如果channel再次准备好,selector会把这个channel加会到这个集合。然而这里把key从这个集合移除掉是相当重要的。如果准备好的channel是服务端channel,程序会接收一个新的socket channel并把它加入到selector。如果准备好的channel是个socket channel,程序会给这个(channel的)buffer写入尽可能多的数据。如果没有channel,selector会等待。一个主线程处理多个同时的请求。这种情况下很容易分辨客户端和服务端channel是否被selected因为服务端channel只准备接受请求,而客户端channel只准备写请求。两者是IO操作,都会处于各种原因抛出IOExceptions,所以你需要在代码块捕获他们:

try {if (key.isAcceptable()) {ServerSocketChannel server = (ServerSocketChannel) key.channel();SocketChannel connection = server.accept();connection.configureBlocking(false);connection.register(selector, SelectionKey.OP_WRITE);// set up the buffer for the client...} else if (key.isWritable()) {SocketChannel client = (SocketChannel) key.channel();// write data to client...}}

Writing the data onto the channel is easy. Retrieve the key’s attachment, cast it to Byte Buffer, and call hasRemaining() to check whether there’s any unwritten data left in the buffer. If there is, write it. Otherwise, refill the buffer with the next line of data from the rotation array and write that.

往channel写入数据是很简单的,提取key的附属对象,转成ByteBuffer,调用hasRemaining()检查buffer是否还有剩余的未写入的数据,如果有,那就写入,如果没有,就重新给buffer填充旋转数组的下一行数据。

ByteBuffer buffer = (ByteBuffer) key.attachment();if (!buffer.hasRemaining()) {// Refill the buffer with the next line// Figure out where the last line startedbuffer.rewind();int first = buffer.get();// Increment to the next characterbuffer.rewind();int position = first - ' ' + 1;buffer.put(rotation, position, 72);buffer.put((byte) '\r');buffer.put((byte) '\n');buffer.flip();}client.write(buffer);

The algorithm that figures out where to grab the next line of data relies on the characters being stored in the rotation array in ASCII order. buffer.get() reads the first byte of data from the buffer. From this number you subtract the space character (32) because that’s the first character in the rotation array. This tells you which index in the array the buffer currently starts at. You add 1 to find the start of the next line and refill the buffer. In the chargen protocol, the server never closes the connection. It waits for the client to break the socket. When this happens, an exception will be thrown. Cancel the key and close the corresponding channel:

这个算法计算出存储在旋转数组哪个地方获取下一行数据并按ASCLL字符排序。buffer.get()读出buffer的第一个字节,用这个字符(可以转成数字,即first变量)减去空格符,因为它是旋转数组的第一个字符。它告诉了你buffer是在数组的哪个下标开始。加上1得到下一行的开始(下标)并重新填充buffer。在chargen的协议中,服务端从来不会关闭连接,他会等待客服端断开。一旦断开,异常会被抛出。取消那个key并关闭相关的channel。

catch (IOException ex) {key.cancel();try {key.channel().close();} catch (IOException cex) {// ignore}}
Example 11-2 puts this all together in a complete chargen server that processes multiple
connections efficiently in a single thread.
Example 11-2. A nonblocking chargen server
import java.nio.*;import java.nio.channels.*;import java.net.*;import java.util.*;import java.io.IOException;public class ChargenServer {public static int DEFAULT_PORT = 19;public static void main(String[] args) {int port;try {port = Integer.parseInt(args[0]);} catch (RuntimeException ex) {port = DEFAULT_PORT;}System.out.println("Listening for connections on port " + port);byte[] rotation = new byte[95*2];for (byte i = ' '; i <= '~'; i++) {rotation[i -' '] = i;rotation[i + 95 - ' '] = i;}ServerSocketChannel serverChannel;Selector selector;try {serverChannel = ServerSocketChannel.open();ServerSocket ss = serverChannel.socket();InetSocketAddress address = new InetSocketAddress(port);ss.bind(address);serverChannel.configureBlocking(false);selector = Selector.open();serverChannel.register(selector, SelectionKey.OP_ACCEPT);} catch (IOException ex) {ex.printStackTrace();return;}while (true) {An Example Server | 357try {selector.select();} catch (IOException ex) {ex.printStackTrace();break;}Set<SelectionKey> readyKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = readyKeys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();iterator.remove();try {if (key.isAcceptable()) {ServerSocketChannel server = (ServerSocketChannel) key.channel();SocketChannel client = server.accept();System.out.println("Accepted connection from " + client);client.configureBlocking(false);SelectionKey key2 = client.register(selector, SelectionKey.OP_WRITE);ByteBuffer buffer = ByteBuffer.allocate(74);buffer.put(rotation, 0, 72);buffer.put((byte) '\r');buffer.put((byte) '\n');buffer.flip();key2.attach(buffer);} else if (key.isWritable()) {SocketChannel client = (SocketChannel) key.channel();ByteBuffer buffer = (ByteBuffer) key.attachment();if (!buffer.hasRemaining()) {// Refill the buffer with the next linebuffer.rewind();// Get the old first characterint first = buffer.get();// Get ready to change the data in the bufferbuffer.rewind();// Find the new first characters position in rotationint position = first - ' ' + 1;// copy the data from rotation into the bufferbuffer.put(rotation, position, 72);// Store a line break at the end of the bufferbuffer.put((byte) '\r');buffer.put((byte) '\n');// Prepare the buffer for writingbuffer.flip();}client.write(buffer);}} catch (IOException ex) {key.cancel();try {358 | Chapter 11: Nonblocking I/Okey.channel().close();}catch (IOException cex) {}}}}}}

This example only uses one thread. There are situations where you might still want to use multiple threads, especially if different operations have different priorities. For instance, you might want to accept new connections in one high-priority thread and service existing connections in a lower-priority thread. However, you’re no longer required to have a 1:1 ratio between threads and connections, which improves the scalability of servers written in Java. It may also be important to use multiple threads for maximum performance. Multiple threads allow the server to take advantage of multiple CPUs. Even with a single CPU, it’s often a good idea to separate the accepting thread from the processing threads. The thread pools discussed in Chapter 3 are still relevant even with the new I/O model. The thread that accepts the connections can add the connections it’s accepted into the queue for processing by the threads in the pool. This is still faster than doing the same thing without selectors because select() ensures you’re never wasting any time on connections that aren’t ready to receive data. On the other hand, the synchronization issues here are tricky, so don’t attempt this until profiling proves there is a bottleneck.

这个例子只用了单线程。你想使用多线程的话方案也有,特别是那种有不同优先级的操作。比如,你想在一个高优先级的线程接收一个新的请求,在了一个低优先级的线程里存放那些存在的,正在服务的连接。然而,1:1的线程数和连接数的比例将不再需要,这提高了用java编写的服务端的可扩展性。使用多线程提高性能也会变得很重要,(因为)多线程允许服务端使用多CPU。即便是用了单CPU,把接收请求和处理请求的线程分开也是很好的。第3章讨论的线程池在new IO的模型里依旧是发挥重大作用。接收请求的线程可以添加它收到的请求到队列里去等待线程池的处理。这依旧比没有使用selector,做相同事情的(程序)快,因为为select()方法确保了你不会在只能连接请求,不能准备接收数据(一事)上面浪费时间。另一方面,同步的问题很微妙,所以性能若没有达到瓶颈不要企图这样做。

---------------------------------------------------------------------------------------------

Creating Buffers
The buffer class hierarchy is based on inheritance but not really on polymorphism, at least not at the top level. You normally need to know whether you’re dealing with an IntBuffer or a ByteBuffer or a CharBuffer or something else. You write code to one
of these subclasses, not to the common Buffer superclass.

buffer类的层级结构是基于继承而不都是多态(译者不明白这个意思),至少顶层是这样的。你一般不必知道你使用的是IntBuffer,还是ByteBuffer,还是CharBuffer,还是其他buffer。你是在这些子类上编程而不是超类buffer。

Each typed buffer class has several factory methods that create implementation-specific subclasses of that type in various ways. Empty buffers are normally created by allocate methods. Buffers that are prefilled with data are created by wrap methods. The allocate methods are often useful for input, and the wrap methods are normally used for output.

每个typed buffer类都有几个由子类类型指定不同实现的静态工厂方法。空buffe一般通过allocate方法创建。其他wrap方法给buffer重新填充数据,allocate方法经常在输入变的很有用,而wrap方法对输出有用。

Allocation

The basic allocate() method simply returns a new, empty buffer with a specified fixed capacity. For example, these lines create byte and int buffers, each with a size of 100:

基本的allocate()方法简单的返回一个新的,空的,指定固定容量的buffer。比如,下面几行代码创建了字节和整型buffer,每个的容量为100:

ByteBuffer buffer1 = ByteBuffer.allocate(100);IntBuffer buffer2 = IntBuffer.allocate(100);

The cursor is positioned at the beginning of the buffer (i.e., the position is 0). A buffer created by allocate() will be implemented on top of a Java array, which can be accessed by the array() and arrayOffset() methods. For example, you could read a large chunk of data into a buffer using a channel and then retrieve the array from the buffer to pass to other methods:

光标卫浴buffer开头(position为0)。allocate()创建的buffer是在java数组上实现的,可以array()用arrayOffset()来访问(数组)。比如,你可以使用channel完buffer读入大块的数据,从buffer获取数组再传到其他方法:

byte[] data1 = buffer1.array();int[] data2 = buffer2.array();
The array() method does expose the buffer’s private data, so use it with caution. Changes to the backing array are reflected in the buffer and vice versa. The normal pattern here is to fill the buffer with data, retrieve its backing array, and then operate on
the array. This isn’t a problem as long as you don’t write to the buffer after you’ve started working with the array.

array()方法将buffer的原始数据暴露在外所以要小心使用。对幕后数组的改变建放射到buffer,反之亦然。一般的程序化做法是给buffer填充数据,获取幕后数组并操作它。一旦你开始用到了数组,只要你不要往buffer写入数据就不会出现问题。

Direct allocation

The ByteBuffer class (but not the other buffer classes) has an additional allocateDirect() method that may not create a backing array for the buffer. The VM may implement a directly allocated ByteBuffer using direct memory access to the buffer on an Ethernet card, kernel memory, or something else. It’s not required, but it’s allowed, and this can improve performance for I/O operations. From an API perspective, allocateDirect() is used exactly like allocate():

ByteBuffer类(不是其他buffer类)有另外一个allocateDirect()方法不会为buffer创建幕后数组。The VM may implement a directly allocated ByteBuffer using direct memory access to the buffer on an Ethernet card, kernel memory, or something else.这不是必须的,当时可以允许,它提高了IO操作的性能。从API的视角看,使用allocateDirect()就像使用allocate():

ByteBuffer buffer = ByteBuffer.allocateDirect(100);
Invoking array() and arrayOffset() on a direct buffer will throw an UnsupportedO perationException. Direct buffers may be faster on some virtual machines, especially if the buffer is large (roughly a megabyte or more). However, direct buffers are more expensive to create than indirect buffers, so they should only be allocated when the buffer is expected to be around for a while. The details are highly VM dependent. As is generally true for most performance advice, you probably shouldn’t even consider using direct buffers until measurements prove performance is an issue.

Wrapping

If you already have an array of data that you want to output, you’ll normally wrap a buffer around it, rather than allocating a new buffer and copying its components into the buffer one at a time. For example:

如果你已经有了你想要输出的数组数据,你一般要将它包装成buffer,而不是分配一个新的buffer并一次性(把数组)复制到这个buffer,比如:

byte[] data = "Some data".getBytes("UTF-8");ByteBuffer buffer1 = ByteBuffer.wrap(data);char[] text = "Some text".toCharArray();CharBuffer buffer2 = CharBuffer.wrap(text);
Here, the buffer contains a reference to the array, which serves as its backing array. Buffers created by wrapping are never direct. Again, changes to the array are reflected in the buffer and vice versa, so don’t wrap the array until you’re finished with it.

这里,buffer包含了一个作为幕后数组的数组引用。Buffers created by wrapping are never direct.再次(提醒),数组的变化会放映到buffer,反之亦然。所以在你不想要用它之前不要封装数组。

0 0
原创粉丝点击