Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 50 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
50
Dung lượng
337,95 KB
Nội dung
stub’s method is called is that it creates a new connection. This can add significant overhead to applications that make frequent remote method calls. It can be a bottleneck that limits the number of clients a server can handle concurrently. Virtualizing the connections eliminates most of the overhead. First we will look at the Multiplexer class. public class Multiplexer { private Socket actualSocket; // The actual connection. private DataOutputStream actualOut; // actual output stream private DataInputStream actualIn; // actual input stream private Hashtable socketTable; // key is connection id private ChunkBufferPool bufferPool; ChunkBufferPool objects keep a collection of ChunkBuffer objects that are awaiting reuse. A Multiplexer object asks its ChunkBufferPool object for a ChunkBuffer object when it needs one. If the ChunkBufferPool object has any ChunkBuffer objects in its collection, it takes one out of the collection and gives it to the Multiplexer object. If the ChunkBufferPool object does not have any ChunkBuffer objects in its collection, then it cre- ates a new one. When a ChunkBuffer object is no longer needed, it is returned to the ChunkBufferPool object. This is an application of the Object Pool pattern described in Volume 1. private Queue connectionQueue = new Queue(); When a Multiplexer object receives data associated with a new vir- tual connection, it creates a Socket object and places it in a Queue object. It stays in the Queue until a call to the Multiplexer object’s accept method takes it out of the Queue object and passes it to its caller. private ConnectionIDFactory idFactory; This Multiplexer implementation delegates the creation of ConnectionID objects to a class called ConnectionIDFactory. private int connectionIDLength; Multiplexer objects transmit ConnectionID objects as a sequence of bytes. The Multiplexer object on one end of an actual connection may create ConnectionID objects that are represented as a longer sequence of bytes than the other. This may be due to environmental differences or because the programs on each end of the actual connection are using dif- ferent versions of the Multiplexer class. Multiplexer objects must exchange connection IDs without regard to which Multiplexer object created the connection ID. To accomplish this, when two Multiplexer objects begin working with each other, they 242 ■ CHAPTER SIX exchange the lengths of the connection IDs that they produce. They set the value of their connectionIDLength instance variable to the larger of the two lengths. They then force all of the connection IDs that they create to be that length. If the natural length of a connection ID is less than this, it is padded with zeros. // This array of bytes is used to read bytes directly from // the actual connection. private byte[] inputBuffer; The next three variables contain some of the parameters discussed under the “Implementation” heading. private int maxMessageLength; // Maximum number of data // bytes to put in a message. private int highWaterMark; private int lowWaterMark; Multiplexer objects communicate with each other by sending mes- sages over the actual connection. Each message begins with an int value that identifies the type of the message. If the value is a positive number, the message contains data for a virtual connection and the value is the number of data bytes in the message. The following negative values are used to indicate other types of messages. private static final int CLOSE_CONNECTION = -1; private static final int BLOCK_WRITES = -2; private static final int UNBLOCK_WRITES = -3; private static final int MESSAGE_HEADER_LENGTH = 6; The constructor for the Multiplexer class takes two arguments: The first argument is the socket for the actual connection. The second argu- ment is an object that encapsulates the parameters discussed in the “Implementation” section that control the operation of the Multiplexer object. public Multiplexer(Socket actualSocket, MultiplexerParameters parameters) throws IOException { this.actualSocket = actualSocket; maxMessageLength = parameters.maxMessageLength; highWaterMark = parameters.highWaterMark; lowWaterMark = parameters.lowWaterMark; actualSocket.setTcpNoDelay(false); // Create a DataOutputStream to write to the actual // connection int myBufferSize = MESSAGE_HEADER_LENGTH+maxMessageLength; Distributed Computing Patterns ■ 243 OutputStream out = actualSocket.getOutputStream(); BufferedOutputStream bout; bout = new BufferedOutputStream(out, myBufferSize); actualOut = new DataOutputStream(bout); // Send the buffer size we are using to the Multiplexer // object on the other side of the connection. actualOut.writeInt(myBufferSize); actualOut.flush(); // Create a DataInputStream to read from the actual // connection. Use the buffer size sent by the other // Multiplexer object. InputStream in = actualSocket.getInputStream(); int otherBufferSize = new DataInputStream(in).readInt(); BufferedInputStream bin; bin = new BufferedInputStream(in); actualIn = new DataInputStream(bin); // Create a buffer for reading from the actual // connection. inputBuffer = new byte[otherBufferSize]; // Negotiate the length of a connection ID with the // other Multiplexer object idFactory = new ConnectionIDFactory(actualSocket); actualOut.writeShort(idFactory.getByteSize()); actualOut.flush(); connectionIDLength = Math.max(idFactory.getByteSize(), actualIn.readShort()); idFactory.setByteSize(connectionIDLength); // Create a ChunkBufferPool object to // manage ChunkBuffer objects. bufferPool = new ChunkBufferPool(parameters.maxBuffers, maxMessageLength); } // constructor(Socket) /** * Return the address of the remote host that the actual * connection connects to. */ public InetAddress getRemoteAddress() { return actualSocket.getInetAddress(); } // getRemoteAddress /** * Return the local address that the actual connection is * connected to. */ public InetAddress getLocalAddress() { return actualSocket.getLocalAddress(); } // getLocalAddress() 244 ■ CHAPTER SIX /** * Create a virtual connection and return a socket object * that encapsulates it. */ public Socket createConnection() throws IOException { ConnectionID id = idFactory.createConnectionID(); MultiplexerSocket vsocket; vsocket = new MultiplexerSocket(id, actualSocket, this, bufferPool, highWaterMark, lowWaterMark); socketTable.put(id, vsocket); return vsocket; } // createConnection() /** * Write a byte to the given virtual connection. * @param connectionID The ConnectionID of the virtual * connection to write to. * @param b The byte to write to the virtual connection. */ void write(ConnectionID id, int b) throws IOException { synchronized (actualOut) { writeMessageHeader(1); id.write(actualOut); actualOut.write(b); } // synchronized } // write(ConnectionID, int) /** * Send a message to the Multiplexer sobject on * the other end of the given virtual connection telling it * to stop sending any more data messages. */ void startMoratorium(ConnectionID id) throws IOException { synchronized (actualOut) { writeMessageHeader(BLOCK_WRITES); id.write(actualOut); } // synchronized } // startMoratorium(ConnectionID) /** * Send a message to the Multiplexer object on * the other end of the given virtual connection telling it * to resume sending data messages. */ void endMoratorium(ConnectionID id) throws IOException { synchronized (actualOut) { writeMessageHeader(UNBLOCK_WRITES); id.write(actualOut); } // synchronized } // endMoratorium(ConnectionID) Distributed Computing Patterns ■ 245 /** * Send a message to the Multiplexer object on * the other end of the given virtual connection telling it * that the virtual connection is closed. */ void endConnection(ConnectionID id) throws IOException { // If this is being called in response to a // CLOSE_CONNECTION message, the ConnectionID will // already have been removed from socketTable if (socketTable.get(id)!=null) { synchronized (actualOut) { socketTable.remove(id); writeMessageHeader(CLOSE_CONNECTION); id.write(actualOut); } // synchronized } // if } // endConnection(ConnectionID) /** * Close this object and free its resources. */ void close() { // close all of the sockets that depend on this object. Enumeration ids = socketTable.keys(); while (ids.hasMoreElements()) { try { endConnection((ConnectionID)ids.nextElement()); } catch (Exception e) { } // try } // while // Close the resources that this object uses. try { actualOut.close(); actualIn.close(); actualSocket.close(); } catch (Exception e) { } // try } // close /** * Write len bytes from the specified array, * starting at index ndx to this output stream. * @param connectionID The connectionID to write to. * @param b the data. * @param ndx the start offset in the data. * @param len the number of bytes to write. */ void write(ConnectionID id, byte b[], int ndx, int len) throws IOException { synchronized (actualOut) { 246 ■ CHAPTER SIX writeMessageHeader(len); id.write(actualOut); actualOut.write(b, ndx, len); } // synchronized } // write(ConnectionID, byte[], int, int) /** * Flush output buffers associated with the given * virtual connection. */ void flush(ConnectionID id) throws IOException { // For now, there are no connectionID specific buffers. actualOut.flush(); } // flush(int) private void writeMessageHeader(int messageHeader) throws IOException { actualOut.writeInt(messageHeader); } // writeMessageHeader(int) private int readMessageHeader() throws IOException { return actualIn.readInt(); } // readMessageHeader() /** * Create a socket for a virtual connection created at the * other end of the actual connection. Put it in a queue * where it will stay until it is accepted. * @param id The ConnectionID for the new virtual * connection. * @return The queued socket */ private MultiplexerSocket queueNewConnection(ConnectionID id) throws SocketException { MultiplexerSocket ms; ms = new MultiplexerSocket(id, actualSocket, this, bufferPool, highWaterMark, lowWaterMark); socketTable.put(id, ms); connectionQueue.put(ms); return ms; } // queueNewConnection(ConnectionID) /** * Accept a virtual connection. If there are no * connections waiting to be accepted, then this method * does not return until there is a connection to * accept. * @return The socket that encapsulates the accepted * virtual connection. */ public Socket accept() throws InterruptedException { Distributed Computing Patterns ■ 247 return (Socket)connectionQueue.get(); } // accept() /** * Accept a virtual connection. If there are no * connections waiting to be accepted then this method does * not return until there is a connection to be accepted. * @param t The number of milliseconds this method should * wait for virtual connection before throwing an * InterruptedException * @return The socket that encapsulates the accepted * virtual connection. * @exception InterruptedException If the given number of * milliseconds elapse without a connection * being accepted or the current thread is * interrupted. */ public Socket accept(int t) throws InterruptedException { return (Socket)connectionQueue.get(t); } // accept() An instance of the private class, MessageDispatcher, is responsible for reading messages from an actual connection and dispatching them to the thread that will process them. private class MessageDispatcher implements Runnable { private Thread dispatcherThread; private static final String THREAD_NAME = "MultiplexerDispatcher"; MessageDispatcher() { dispatcherThread = new Thread(this, THREAD_NAME); dispatcherThread.start(); } // constructor() /** * Top-level message dispatching logic. */ public void run() { Thread myThread = Thread.currentThread(); try { while (!myThread.isInterrupted()) { int messageHeader = readMessageHeader(); if (messageHeader>0) { readDataMessage(messageHeader); } else { switch (messageHeader) { case CLOSE_CONNECTION: readCloseConnection(); break; case BLOCK_WRITES: readBlock(); break; 248 ■ CHAPTER SIX case UNBLOCK_WRITES: readUnblock(); break; } // switch } // if } // while } catch (IOException e) { // This Multiplexer object can no longer // function, so close it. close(); } // try } // run() /** * Read the body of a data message, put the data in a * ChunkBuffer object and associate the ChunkBuffer * object with the virtual connection. * @param length The number of data bytes that the * message header says the message * contains. */ private void readDataMessage(int length) throws IOException { ConnectionID id; id = ConnectionID.read(actualIn, connectionIDLength); MultiplexerSocket vsocket; vsocket = (MultiplexerSocket)socketTable.get(id); if (vsocket==null) { vsocket = queueNewConnection(id); } // if int messageLength = actualIn.readInt(); // The message length should not exceed the // promised length, but allow for the possibility // that it may be longer. while (messageLength>inputBuffer.length) { actualIn.readFully(inputBuffer, 0, inputBuffer.length); vsocket.queueBuffer(inputBuffer.length, inputBuffer); messageLength -= inputBuffer.length; } // while actualIn.readFully(inputBuffer, 0, messageLength); vsocket.queueBuffer(messageLength, inputBuffer); } // readMessageHeader() /** * Read and process a CLOSE_CONNECTION message. */ private void readCloseConnection() throws IOException { ConnectionID id; id = ConnectionID.read(actualIn, Distributed Computing Patterns ■ 249 connectionIDLength); MultiplexerSocket vsocket; vsocket =(MultiplexerSocket)socketTable.remove(id); if (vsocket!=null) { vsocket.close() } // if } // readCloseConnection() /** * Read and process a BLOCK_WRITES message. */ private void readBlock() throws IOException { ConnectionID id; id = ConnectionID.read(actualIn, connectionIDLength); MultiplexerSocket vsocket; vsocket =(MultiplexerSocket)socketTable.get(id); if (vsocket!=null) { vsocket.blockWrites(); } // if } // readBlock() /** * Read and process a UNBLOCK_WRITES message. */ private void readUnblock() throws IOException { ConnectionID id; id = ConnectionID.read(actualIn, connectionIDLength); MultiplexerSocket vsocket; vsocket =(MultiplexerSocket)socketTable.get(id); if (vsocket!=null) { vsocket.unblockWrites(); } // if } // readUnblock() } // class MessageDispatcher } // class Multiplexer An instance of the ConnectionIDFactory class is responsible for creating instances of the ConnectionID class. The information used to cre- ate a ConnectionID object depends on the actual connection that the ConnectionID is used with. A ConnectionIDFactory object encapsulates that information. class ConnectionIDFactory { // Array of information common to all ConnectionID objects // associated with the same actual connection private byte[] commonInfo; private static final int PORT_NUMBER_LENGTH = 2; static final int SERIAL_NUMBER_LENGTH = 4; private int counter = 0; private int byteSize = -1; 250 ■ CHAPTER SIX TEAMFLY Team-Fly ® /** * constructor. * @param socket The socket that encapsulates the actual * connection this object will produce * ConnectionID objects for. */ ConnectionIDFactory(Socket socket) { InetAddress inetAddress = socket.getLocalAddress(); byte[] address = inetAddress.getAddress(); // We include port number to allow for the possibility // of one Multiplexer object working with multiple // actual connections from the same host. int port = socket.getLocalPort(); // Assume only 2 significant bytes in a port number and // four bytes four bytes for a serial number commonInfo = new byte[address.length + PORT_NUMBER_LENGTH]; System.arraycopy(address, 0, commonInfo, 0, address.length); int portOffset = address.length; commonInfo[portOffset] = (byte)port; commonInfo[portOffset+1] = (byte)(port>>8); } // constructor(Socket) /** * Return the number of bytes that will be used to read or * write a ConnectionID created by this object. */ int getByteSize() { if (byteSize == -1) { return commonInfo.length+SERIAL_NUMBER_LENGTH; } // if return byteSize; } // getByteSize() /** * Set the number of bytes that will be used to read or * write a ConnectionID created by this object. */ void setByteSize(int newValue) { byteSize = newValue; } // setByteSize(int) /** * Create a new ConnectionID object. */ ConnectionID createConnectionID() { synchronized (this) { counter++; } // synchronized return new ConnectionID(commonInfo, counter); Distributed Computing Patterns ■ 251 [...]... Distributed Computing Patterns ■ 2 53 /** * Read a ConnectionID from the given InputStream * @param in The InputStream to read from * @param byteSize The number of bytes of id information to * read This is needed because each end of * an actual connection may use a different * number of bytes */ static ConnectionID read(InputStream in, int size) throws IOException { byte[] id = new byte[size]; if (in. read(id,... } // unblockWrites() } // class MultiplexerSocketImpl Here is the InputStream class that reads input by getting ChunkBuffer objects containing input bytes from a MultiplexerSocket object class ChunkedInputStream extends InputStream { // This is true after this InputStream has been closed boolean closed = false; Distributed Computing Patterns // The MultiplexerSocket object this object gets // ChunkBuffer... setMultiplexerSocket(MultiplexerSocket) /** * Returns an input stream for this socket ■ 259 C HAPTER S IX * @return a stream for reading from this socket * @exception IOException if an I/O error occurs */ protected InputStream getInputStream() throws IOException { checkClosed(); if (in= =null || in. isClosed()) { in = new ChunkedInputStream(theSocket); } // if return in; } // getInputStream() /** * Returns an output stream... setContent(byte[], int, int) /** * Retrieve some bytes of content from this object * @param bytes An array to copy the content into Distributed Computing Patterns ■ 255 * @param offset The first position to copy content into * @param length The number of bytes of content requested * @return The actual number of bytes of content retrieved */ synchronized int getContent(byte[] bytes, int offset, int length) { int... createServerSocket(int port) throws IOException { return serverFactory.createServerSocket(port); } // createServerSocket(int) /** * Use the given name to a string of the form * //host:-1/name * This string can be used to register an object with a * name registry */ public String makeRegistryString(String name) throws UnknownHostException { return serverFactory.makeRegistryString(name); } // makeRegistryString(String)... Stack (33 7) 275 Concurrency Patterns ■ 277 Session Object This pattern was previously described in [Yoder-Barcalow98] SYNOPSIS There is a possibly large number of objects that determine the state of each session that a server has with its clients Use a single object to contain all the state information needed during a session by the server Make the object accessible to all objects that need state information... createServerSocket(int port) throws IOException { if (port==-1) { if (serverSocketInstance==null) { serverSocketInstance = new RMIVirtualServerSocket(); } // if Distributed Computing Patterns ■ 269 return serverSocketInstance; } // if RMISocketFactory defaultFactory; defaultFactory = RMISocketFactory.getDefaultSocketFactory(); return defaultFactory.createServerSocket(port); } // createServerSocket(int) Instances... object */ public int hashCode() { 254 ■ C HAPTER S IX long h = 1 234 ; for (int i = id.length; i >= 0; ) h ^= id[i] * (i + 1); return (int)((h >> 32 ) ^ h); } // hashCode() } // class ConnectionID ChunkBuffer objects are used to buffer input from a virtual connection until it is read by a ChunkedInputStream object class ChunkBuffer { private byte[] buffer; private int firstFree; private int firstContent;... The Scheduling pattern describes how to enforce a policy that determines when a thread is scheduled to use a resource Proactor The Connection Multiplexing pattern uses the Proactor pattern which is described in [Schmidt97] C H A P T E R 7 Concurrency Patterns Session Object (277) Lock File (285) Static Locking Order (291) Optimistic Concurrency (297) Thread Pool (30 3) Ephemeral Cache Item (32 5) Transaction... readConnectionID(InputStream) /** * Write the bytes of identifying information in this * ConnectionID to the given OutputStream */ void write(OutputStream out) throws IOException { out.write(id); } // writeConnectionId(OutputStream) The ConnectionID class overrides the equals and hashCode methods it inherits from the Object class so that the identifying information in ConnectionID objects can be used as the key in . object. InputStream in = actualSocket.getInputStream(); int otherBufferSize = new DataInputStream (in) .readInt(); BufferedInputStream bin; bin = new BufferedInputStream (in) ; actualIn = new DataInputStream(bin); //. CLOSE_CONNECTION = -1; private static final int BLOCK_WRITES = -2; private static final int UNBLOCK_WRITES = -3; private static final int MESSAGE_HEADER_LENGTH = 6; The constructor for the Multiplexer. (messageLength>inputBuffer.length) { actualIn.readFully(inputBuffer, 0, inputBuffer.length); vsocket.queueBuffer(inputBuffer.length, inputBuffer); messageLength -= inputBuffer.length; } // while actualIn.readFully(inputBuffer,