Framebuffer is a core component of the thrift NiO server. It undertakes the buffer function in NIO programming and the RPC method call.
Framebufferstate defines the read/write status of framebuffer as a buffer.
Private Enum framebufferstate {// in the midst of reading the frame size off the wire // read the frame message header. The actual size is 4 bytes, indicating the length of the frame reading_frame_size, // reading the actual frame data now, but not all the way done yet // read the frame message body reading_frame, // completely read the frame, so an invocation can now happen // read the full package read_frame_complete, // waiting to get switched to listening for write events // wait for registration to write awaiting_register_write, // started writing response data, not fully complete yet // write half package writing, // another thread wants this framebuffer to go back to reading // wait for registration to read awaiting_register_read, // We want our transport and selection key invalidated in the selector // thread // wait to close awaiting_close}
It is worth noting that when reading data in framebuffer,
1. Read the four-byte frame message header first,
2. Change the framebufferstate, from reading_frmae_size to reading_frame, and modify the buffer Length Based on the read frame length.
3. Read the frame message body again. After reading the message body, modify the status to read_frame_complete. Otherwise, bind the framebuffer to the selectionkey.
public boolean read() { if (state_ == FrameBufferState.READING_FRAME_SIZE) { // try to read the frame size completely if (!internalRead()) { return false; } // if the frame size has been read completely, then prepare to read the // actual frame. if (buffer_.remaining() == 0) { // pull out the frame size as an integer. int frameSize = buffer_.getInt(0); if (frameSize <= 0) { LOGGER.error("Read an invalid frame size of " + frameSize + ". Are you using TFramedTransport on the client side?"); return false; } // if this frame will always be too large for this server, log the // error and close the connection. if (frameSize > MAX_READ_BUFFER_BYTES) { LOGGER.error("Read a frame size of " + frameSize + ", which is bigger than the maximum allowable buffer size for ALL connections."); return false; } // if this frame will push us over the memory limit, then return. // with luck, more memory will free up the next time around. if (readBufferBytesAllocated.get() + frameSize > MAX_READ_BUFFER_BYTES) { return true; } // increment the amount of memory allocated to read buffers readBufferBytesAllocated.addAndGet(frameSize); // reallocate the readbuffer as a frame-sized buffer buffer_ = ByteBuffer.allocate(frameSize); state_ = FrameBufferState.READING_FRAME; } else { // this skips the check of READING_FRAME state below, since we can't // possibly go on to that state if there's data left to be read at // this one. return true; } } // it is possible to fall through from the READING_FRAME_SIZE section // to READING_FRAME if there's already some frame data available once // READING_FRAME_SIZE is complete. if (state_ == FrameBufferState.READING_FRAME) { if (!internalRead()) { return false; } // since we're already in the select loop here for sure, we can just // modify our selection key directly. if (buffer_.remaining() == 0) { // get rid of the read select interests selectionKey_.interestOps(0); state_ = FrameBufferState.READ_FRAME_COMPLETE; } return true; } // if we fall through to this point, then the state must be invalid. LOGGER.error("Read was called but state is invalid (" + state_ + ")"); return false; }
The internalread method actually calls socketchannel to read data. Note that the socketchannel return value is less than 0:
N returns the number of bytes read when data is available.
0. If no data exists and the data does not reach the end of the stream, 0 is returned.
-1: When the stream reaches the end,-1 is returned.
When the channel has data and is the final data, it will actually read twice. The first time it returns the number of bytes, and the second time it returns-1. This is implemented by the underlying selector.
private boolean internalRead() { try { if (trans_.read(buffer_) < 0) { return false; } return true; } catch (IOException e) { LOGGER.warn("Got an IOException in internalRead!", e); return false; } }
When reading the write buffer
1. Before writing, you must change the framebuffer status to writing. A specific example will be provided later.
2. If no data is written, false is returned.
3. If you have finished writing, you need to cancel the write event registered with selectionkey. Thrift directly changes the selectionkey registration event to read, and the common practice is to cancel the write event. For more information about registration of NiO write events, see this: http://blog.csdn.net/iter_zc/article/details/39291129
public boolean write() { if (state_ == FrameBufferState.WRITING) { try { if (trans_.write(buffer_) < 0) { return false; } } catch (IOException e) { LOGGER.warn("Got an IOException during write!", e); return false; } // we're done writing. now we need to switch back to reading. if (buffer_.remaining() == 0) { prepareRead(); } return true; } LOGGER.error("Write was called, but state is invalid (" + state_ + ")"); return false; }
Framebuffer can switch its status based on the selectionkey status, or select registered Channel Events based on its status.
public void changeSelectInterests() { if (state_ == FrameBufferState.AWAITING_REGISTER_WRITE) { // set the OP_WRITE interest selectionKey_.interestOps(SelectionKey.OP_WRITE); state_ = FrameBufferState.WRITING; } else if (state_ == FrameBufferState.AWAITING_REGISTER_READ) { prepareRead(); } else if (state_ == FrameBufferState.AWAITING_CLOSE) { close(); selectionKey_.cancel(); } else { LOGGER.error("changeSelectInterest was called, but state is invalid (" + state_ + ")"); } }
After completing the functions of framebuffer as the NiO buffer, let's take a look at the functions of it as an important component of the RPC method call model.
Framebuffer provides the invoker method. when the package is fully read, the method to be called is obtained from the message header, and the processor managed by it is used to call the actual method. Switch to the write mode to write the message body.
Specific call model to see this: http://blog.csdn.net/iter_zc/article/details/39692951
public void invoke() { TTransport inTrans = getInputTransport(); TProtocol inProt = inputProtocolFactory_.getProtocol(inTrans); TProtocol outProt = outputProtocolFactory_.getProtocol(getOutputTransport()); try { processorFactory_.getProcessor(inTrans).process(inProt, outProt); responseReady(); return; } catch (TException te) { LOGGER.warn("Exception while invoking!", te); } catch (Throwable t) { LOGGER.error("Unexpected throwable while invoking!", t); } // This will only be reached when there is a throwable. state_ = FrameBufferState.AWAITING_CLOSE; requestSelectInterestChange(); } public void responseReady() { // the read buffer is definitely no longer in use, so we will decrement // our read buffer count. we do this here as well as in close because // we'd like to free this read memory up as quickly as possible for other // clients. readBufferBytesAllocated.addAndGet(-buffer_.array().length); if (response_.len() == 0) { // go straight to reading again. this was probably an oneway method state_ = FrameBufferState.AWAITING_REGISTER_READ; buffer_ = null; } else { buffer_ = ByteBuffer.wrap(response_.get(), 0, response_.len()); // set state that we're waiting to be switched to write. we do this // asynchronously through requestSelectInterestChange() because there is // a possibility that we're not in the main thread, and thus currently // blocked in select(). (this functionality is in place for the sake of // the HsHa server.) state_ = FrameBufferState.AWAITING_REGISTER_WRITE; } requestSelectInterestChange(); }
When writing the message body responsereday () method, we can see how thrift handles the write
1. Create bytebuffer
2. Change the status to awaiting_register_write.
3. Call the requestselecinteresetchange () method to register the channel write event.
4. When selector calls the channel to be written according to the iswriteable status, it will call the write method of framebuffer. After the write method is full, it will cancel the registration of write events.
Thrift source code analysis (5) -- framebuffer Class Analysis