On Sep 3, 2008, at 17:10 , Jeanfrancois Arcand wrote:
> Salut,
>
> Oleksiy Stashok wrote:
>> Hi,
>> wanted to share the simple Grizzly 2.0 sample (echo sample) and
>> will very appreciate the feedback or better contributions :) to the
>> proposed design.
>> The complete sources are attached, though to orient better I'll
>> briefly describe what is what :)
>> Let's start from the server.
>> 1)
>> TCPNIOTransport transport =
>> TransportManager.instance().createTCPTransport();
>
> +0 on the instance() method :-) We discussed that a while ago, but I
> would think we need to have something similar to the Controller, and
> get Transport from the Controller.
>
> Now what I don't like is also (we have the same problem with 1.8
> right now) is the fact that you have the TCP named in the method
> name. I would recommend something like:
>
>
> createTransport(Transport.TCP)
>
> instead.
As for instance() I don't agree... Having static method
TransportManager.createTransport() is not flexible, as doesn't let us
to have custom TransportManager implementation.
Having something like:
TransportManager.instance().createTransport(Transport.TCP);
looks like good idea :)
>> // Add TransportFilter, which is responsible
>> // for reading and writing data to the connection
>> transport.getFilterChain().add(new TransportFilter());
>> transport.getFilterChain().add(new EchoFilter());
>
> For backward compatibility we can probably still use
> getProtocolChain(). Do you see any problem?
I just think, it's more clear to have FilterChain, which consists of
Filters, than ProtocolChain, consisting of ProtocolFilter. In first
case names look more clear and shorter.
>> try {
>> // binding transport to start listen on certain host and
>> port
>> transport.bind(HOST, PORT);
>> // start the transport
>> transport.start();
>> System.out.println("Press any key to stop the server...");
>> System.in.read();
>> } finally {
>> // stop the transport
>> transport.stop();
>> // release TransportManager resources like ThreadPool
>> TransportManager.instance().close();
>
> Hum :-) Can we find a way to avoid such extra method call:
>
> TransportManager.instance().close()
>
> Instead, can we do something like:
>
>
> Transport transport = createTransport(Transport.TCP);
> TransportManager.addListener(transport,TransportManager.CLOSE);
>
> SO when transport.close() is called, the TransportManager is
> automatically invoked. That will be less error prone IMO.
Really not sure which way is less error prone :))
Basically TransportManager could create several Transports and if we
close just one of them - it doesn't mean we need to release all the
TransportManager resources (such as ThreadPool), because we have other
Transports, which may use them.
>> }
>> }
>> here we just initialize the TCP transport, adding 2 filters:
>> TransportFilter and EchoFilter, binding the server to the specific
>> host and port and... that's it :)
>> It's similar to what we have in 1.x, except may be new term
>> TransportFilter.
>> TransportFilter is very similar to ReadFilter from Grizzly 1.x, so
>> it knows how to read/write the data depending on the transport.
>
> We need to decide if we keep the ProtocolFilter name. I would think
> ProtocolFilter defined for 1.8 can still work with 2.0
TransportFilter has wider logic. It knows how to read and write. So,
IMHO, it makes sense to call it TransportFilter?
> Each
>> transport can implement its own read/write logic, in this case
>> TransportFilter will silently forward the control the the specific
>> implementation. In our case TCPNIOTransport has own read/write
>> logic implementation, which is hidden from us, and TransportFilter
>> uses it in order to read/write the bytes on the filter chain.
>
> OK that sound interesting. Now we need to take into account NIO.2
> here. So we probably need to have TCPAIOTransport as well. Switching
> from NIO.1 to NIO.2 should be transparent IMO to the user.
Sure, as I told each Transport could have own TransportFilter logic,
so NIO.2 could have its own logic, which will be used by common
TransportFilter.
>> 2) How does the EchoFilter look like.
>> /**
>> * Handle just read operation, when some message has come and
>> ready to be
>> * processed.
>> *
>> * @param ctx Context of {_at_link FilterChainContext} processing
>> * @param nextAction default {_at_link NextAction} filter chain
>> will execute
>> * after processing this {_at_link Filter}. Could
>> be modified.
>> * @return the next action
>> * @throws java.io.IOException
>> */
>> @Override
>> public NextAction handleRead(FilterChainContext ctx, NextAction
>> nextAction)
>> throws IOException {
>> // Get the read message
>> Object message = ctx.getMessage();
>
> Hum...how do you know you have a complete/incomplete message?
For echo filter it doesn't matter, we just reply with the same
sequence of byte, which was received.
>> /* Send the same message on the connection. The filter chain
>> write
>> * will pass each filter on a filter chain (interceptWrite
>> method),
>> * before sending the message on a wire. It means each
>> filter can modify
>> * the message, before it will be sent to the recipient
>> */
>> ctx.getFilterChain().write(ctx.getConnection(), message);
>
> Here the write operation will be blocking? We probably need some
> WriteSessionListener here to get some cue about what's happenning
> with the write operation.
Yes. Connection.write(...) or FilterChain.write() are always blocking,
for async. write there is different method set:
Connection.writeAsync(...), FilterChain.writeAsync(...)
>> return nextAction;
>> }
>> common interface for all filters is Filter, which if very similar
>> to the ProtocolFilter from Grizzly 1.x, but, IMHO, it's more
>> suitable to extend FilterAdapter class, which lets you separate the
>> logic. For example in case of EchoFilter we just want to handle
>> read operation and not interested in others.
>> You can note how we write the response, using the filter chain, not
>> to the connection directly. It means that all the Filters in the
>> chain can intercept the write operation and transform the writing
>> buffer before TransportFilter will put it on a wire. In our case it
>> looks redundant, because we don't have any Filter, which could be
>> interested in transforming the response, and
>> "ctx.getConnection().write(message);" could be used instead. But as
>> common solution filterchain.write is better.
>
> Can you elaborates more here? I'm not sure I understand properly.
> How will you know a Filter is interested in write operations?
Each Filter could override interceptFilterChainWrite(...) method,
which is called on each Filter in a FilterChain, when
FilterChain.write/writeAsync is called. So each Filter (like SSL) will
be able to transform the original message before it will come to the
TransportFilter.
>
>> 3) Client
>> Connection connection = null;
>> // Create the TCP transport
>> TCPNIOTransport transport = TransportManager.instance().
>> createTCPTransport();
>
> Same as above :-) -1 for the instance() :-)
>
>
>
>> try {
>> // start the transport
>> transport.start();
>> // perform async. connect to the server
>> ConnectFuture future =
>> transport.connectAsync(EchoServer.HOST,
>> EchoServer.PORT);
>> // wait for connect operation to complete
>> connection = (TCPNIOConnection) future.get(10,
>> TimeUnit.SECONDS);
>> assert connection != null;
>> // create the message
>> ByteBuffer message = ByteBuffer.wrap("Echo
>> test".getBytes());
>> // sync. write the complete message using
>> // temporary selectors if required
>> WriteResult result = connection.write(message);
>> assert result.getWrittenSize() == message.capacity();
>
> Hum :-) If the message has not been fully written,
> result.getWrittenSize() will return the wrong value. I would instead
> change your write() signature and have something like NIO.2:
>
> write(msg, CompletionHandler);
>
> http://openjdk.java.net/projects/nio/javadoc/java/nio/channels/CompletionHandler.html
>
> And follow a similar approach for read and write operations.
There is such an approach for readAsync/writeAsync methods of
Connection and FilterChain, you can pass the CompletionHandler there.
Read() and write() methods are always sync. and blocking.
>> // allocate the buffer for receiving bytes
>> ByteBuffer receiverBuffer =
>> ByteBuffer.allocate(message.capacity());
>> ReadResult readResult = null;
>> // read the same number of bytes as we sent before
>> while (receiverBuffer.hasRemaining() &&
>> (readResult == null || readResult.getReadSize()
>> > 0)) {
>> readResult = connection.read(receiverBuffer);
>> }
>> // check the result
>> assert message.flip().equals(receiverBuffer.flip());
>> } finally {
>> // close the client connection
>> if (connection != null) {
>> connection.close();
>> }
>> // stop the transport
>> transport.stop();
>> // release TransportManager resources like ThreadPool etc.
>> TransportManager.instance().close();
>> }
>> it does similar things as server to initialize/release the
>> transport and TransportManager.
>> Also you can find, how client is getting connected to the server,
>> writes and reads data.
>> Want to note, that currently there is no async read and write
>> implementations in Grizzly 2.0 (it will be ready soon), so all
>> reads and writes are working sync., using temporary selectors if
>> required.
>
> OK this is a great start. I haven't looked at the code yet....what I
> recommend is first document the 2.0 core classes and generate the
> JavaDoc API...that will be a really good startup for review during
> our weekly meeting. Looking at the code directly will makes the task
> to harder for external people (nobody except a couple of us will
> have time to look at the implementation). So I recommend you javadoc
> the new monster :-)
Sure, I'm trying to write as much javadocs as possible. Once I'll push
Grizzly2.0 to the maven - there will be JavaDoc generated.
> Also we must have in mind that we may have to be backward compatible
> with some concept (like ProtocolFIlter/Chain). I say we might :-) We
> should dress a list and probably vote. We have to keep in mind that
> customer using 1.8 might not have time to re-write their
> application, and we don't want to loose them for another framework
> in case they don't like our 2.0 design.
I'm 100% agree, we have to have some migration guide. But not sure
keeping old names, with completely new implementation will help much.
Thank you for feedback!!!
WBR,
Alexey.
>
>
> Thanks
>
> -- Jeanfrancois
>
>
>> Will appreciate your feedback.
>> Thanks.
>> WBR,
>> Alexey.
>> ------------------------------------------------------------------------
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: dev-unsubscribe_at_grizzly.dev.java.net
>> For additional commands, e-mail: dev-help_at_grizzly.dev.java.net
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe_at_grizzly.dev.java.net
> For additional commands, e-mail: dev-help_at_grizzly.dev.java.net
>