Luiz Hamilton R L Soares wrote:
> Well, as I've said, I'm working in a LBS (Location Based System). It has
> 4.000(and growing) cars as clients. They comunicate with my system
> through a GSM modem/GPRS to
> send their location. I can send message to my clients only when they send
> a message, because it register for OP_READ and after my ReadFilter
> implementation
> finish reading, I use its SelectionKey to write a message to them.
> Now I'm looking at how to trigger an event to send a message to my
> clients without waiting a message from them.
> I've realised that SelectionKey is parked, but I don't know how to "wake
> it up" and register it to OP_WRITE
> without receiving a message from it first.
> I managed to do that using HTTP, but I did it just for a test, because
> my clients use only TCP/IP.
Here's how I'm doing it for the new Shared Shell (
http://sun.com/123)
server, which is similar -- it does binary asynchronous messages over
Grizzly's infrastructure.
My main controller manages a "WriteState" with a queue for outgoing
messages for every SelectionKey in the system (you need to be careful
about mapping anything to SelectionKey because Grizzly could close the
connection on you -- you will need to extend DefaultSelectionKeyHandler
with your own class that overrides cancel(SelectionKey) to be notified
of connections being closed).
So, when a new message generated by the server needs to be
asynchronously written out to the client, here's what happens.
1. The Message object is added to the WriteState's queue
2. The WriteState's registerWriteIntent method is called, which is a
Semaphore-type operation -- if a write task is already pending, this
method returns true and the main controller can return because the data
will (eventually) get written. If false, we know that we need to
register a Write task and wake up the selector.
3. You need to register OP_WRITE intent with the TCPSelectorHandler by
calling register(SelectionKey,OP_WRITE). I actually have overridden
this class. In my version of register(SelectionKey,int), I try to do a
thread-safe race condition check just to make sure a write task has not
already started in the case of OP_WRITE and skipping the
super.register(...) call in that case. Otherwise I delegate to the
superclass.
4. You'll want to intercept when the Selector actually tells us that
OP_WRITE can be performed. The easiest way I found to do this was by
overriding TCPSelectorHandler.onWriteInterest(SelectionKey,Context). My
method looks something like this:
public boolean onWriteInterest(SelectionKey key, Context ctx) {
// Look up and set the write-task-imminent flag on my Entry
// (these are all application-specific controller classes here)
MyEntry ent = myAppController.flagWriteReady(key);
// Disable OP_WRITE before doing any other thing
key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
// Queue a WriteTask in our app-specific Pipeline.
myAppController.submitWriteTask(ent);
// Do *not* delegate to ProtocolChain
return false;
}
5. A lot of the heavy lifting is done in my WriteTask, which is managed
in my own *Task pipeline separate from the Read pipeline that is
provided by Grizzly. I some logic in my pipeline and WorkerThread
around pooling WriteTask objects and exception handling. The logic each
time the write task is called looks something like this:
. update the WriteState to indicate that a task is running
. write out any SSL-encoded bytes left over from the last time
. SSL-encode any message bytes left over from the last time and
try to write those out
. keep pulling messages off the write queue until we fill the
cleartext buffer. Then SSL-encode all those bytes and try to
write those out
The WriteState has logic for preserving partial SSL writes or partial
writes of large messages that do not fit within the cleartext (or SSL)
buffer.
Right now, this logic is a bit too tightly-coupled to the rest of the
application to really generalize it for use in GlassFish, but it may
give you some ideas of areas to investigate/explore...
-=- D. J.