« Return to Thread: Grizzly 2.0: Echo sample

Re: Grizzly 2.0: Echo sample

by Oleksiy Stashok :: Rate this Message:

Reply to Author | View in Thread


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 {@link FilterChainContext} processing
>>     * @param nextAction default {@link NextAction} filter chain  
>> will execute
>>     *                   after processing this {@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@...
>> For additional commands, e-mail: dev-help@...
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@...
> For additional commands, e-mail: dev-help@...
>


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@...
For additional commands, e-mail: dev-help@...

 « Return to Thread: Grizzly 2.0: Echo sample