You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@mina.apache.org by Emmanuel Lécharny <el...@gmail.com> on 2012/10/01 12:45:54 UTC
[MINA 3.0] Thoughts on TCP server
Hi guys, some thoughts about the TCP server. feel free to comment.
TCP server MINA 3
-----------------
As we are reworking the server part of MINA, we can review the current
architecture. There are a few problems we can address.
1) One vs Many selectors
In MINA 2, we do have at least 2 selectors used :
- one for the OP_ACCEPT event
- One or many for the OP_READ/OP_WRITE
I don't think that it makes a lot of sense to force such a choice. IMO,
we could perfectly start with one single selector to handle all the
events, assuming we are fast enough to process them. Otherwise, we can
also configure the server to use more selectors, if needed.
Typically, the acceptor selector will just deal with incoming new
connections, and the created channel will be registred on an IoProcessor
selector on MINA 2. We could register this channel on the acceptor selector.
In any case, if we do create more than one selector, I suggest that the
first one would always handle OP_ACCEPT events, and that's all.
The general algorithm will look likes :
signal started
while true
nbSelect = select( delay )
if nbSelect != 0
for each selectionKey do
case isAccept // Only if the selector is the first one, otherwise
we don't need to heck this case
create session
case isRead
process read
case isWrite
process write
done
if dispose // In order to stop the server
process dispose
break
done
The key is to start all the selector workers *before* accepting any
connection, otherwise we may lose some messages. One more thing : each
selector should signal that there have started before entering in the
loop, so that the caller don't have to wait a random period of time for
the selectors to be started.
2) Events processing
Now, in order to limit the number of selectors to use, we need to limit
the time it takes to process the read/write/accept events. But even if
we have many selectors, we should try to minimize the risk that one
selector is blocked by a single session blocked somewhere while doing
some heavy prcoessing, as it will block all the other sessions.
Having more than a selector is one way to mitigate this issue : as we
have many threads (one per selector), we spread the loads on as many
threads.
Another solution would be to use an executor in charge of processing the
events, with a queue between the selector and the executor, queue that
is used to process the events as fast as possible on the selector (this
is really important for UDP, as we don't want to lose messages simply
because the OS buffer is full).
The problem is that we are just not solving the problem of a rogue
service that block a thread for a long time (if we use a limited size
executor), or we may end with so many threads that it may kill the
server. But anyway, it sounds like a better solution, as incoming events
that won't require a long processing will be favored in the long term.
3) Write processing
This is a complex issue too : we may not be able to push all the data we
want into the socket, if it becoms full (or was already full). In this
case, we will have to store the data in a queue. The following algorithm
describe this situation and a proposal to solve it
if there are some data in the writeQueue
then
// We need to enqueue the data, and write the head of the queue
enqueue data
// Now, we shoudl try to write as much of the queue as we can
while ( queue not empty)
do
poll the data from the queue
nbWritten = channel.write( remaining data ) // We may have
already written some part of the head data
if nbWritten < data.remaining
then
// the socket is already full, set its OP_WRITE
interest, and don't touch the queue
selectionKey.ops = OP_WRITE
break // We can stop, the socket is full anyway
else
pop the data from the queue // We just remove the head,
and continue with the next data
done
if queue is empry
then
selectionKeys.ops = ! OP_WRITE // We remoe this flag,
it's ueless now
else
nbWritten = channel.write( remaining data ) // We may have
already written some part of the head data
if nbWritten < data.remaining
then
// the socket is already full, set its OP_WRITE interest,
and add the data in the queue
selectionKey.ops = OP_WRITE
writeQueue.add( remaining data )
4) Read/Write order
MINA 2 was processing all the reads first, then all the writes. This is
not necessarilly a good idea. We may rather process read and write on a
per session basis. It's perfectly possible that a session has to process
some reads and some write at the same time. Waiting for all the reads to
be processed create some memory load, as we will have to wait until all
the reads are done, storing all the data to be written in the mean time,
until we are done with the last session.
5) MINA events
Atm, we are processing the following events :
- messageReceived (when we have read some data from the channel)
- messageSent (when a user message has been sentcompletely)
- exceptionCaugth (when an exception has been caught)
- sessionIdle (if the session has not received or sent anything for a
period of time)
- sessionCreated (when a new session is created)
- sessionOpened
- sessionClosed (when the session has been closed)
- filterWrite
- filterClose
The sessionOpened, filterWrite and filterClose are a bit mysterious. I
don't see why we need a special event SessionOpened when we alreayd have
the sessionCreated event. That may seems we *may* create a session, but
not accept any incoming or outgoing messages for this session, until
it's initialized. I'm not sure this is necessary.
The two other events are probably some YAGNY iplementation...
--
Regards,
Cordialement,
Emmanuel Lécharny
www.iktek.com
Re: [MINA 3.0] Thoughts on TCP server
Posted by Chad Beaulac <ca...@gmail.com>.
Hi Emmanuel,
1) One Reactor is preferable. Easier to manage, easier to code, meets all the requirements.
2) Event Processing: Use non-blocking sockets. Accept, connect, read and write won't hang each other up.
I have some code that does this. I'll get it and attach to a subsequent email. It might take a couple days.
Regards,
Chad Beaulac
Objective Solutions, Inc.
www.objectivesolutions.com
chad@objectivesolutions.com
On Oct 1, 2012, at 6:45 AM, Emmanuel Lécharny wrote:
> Hi guys, some thoughts about the TCP server. feel free to comment.
>
> TCP server MINA 3
> -----------------
>
> As we are reworking the server part of MINA, we can review the current architecture. There are a few problems we can address.
>
> 1) One vs Many selectors
> In MINA 2, we do have at least 2 selectors used :
> - one for the OP_ACCEPT event
> - One or many for the OP_READ/OP_WRITE
>
> I don't think that it makes a lot of sense to force such a choice. IMO, we could perfectly start with one single selector to handle all the events, assuming we are fast enough to process them. Otherwise, we can also configure the server to use more selectors, if needed.
>
> Typically, the acceptor selector will just deal with incoming new connections, and the created channel will be registred on an IoProcessor selector on MINA 2. We could register this channel on the acceptor selector.
>
> In any case, if we do create more than one selector, I suggest that the first one would always handle OP_ACCEPT events, and that's all.
>
> The general algorithm will look likes :
>
> signal started
>
> while true
> nbSelect = select( delay )
>
> if nbSelect != 0
> for each selectionKey do
> case isAccept // Only if the selector is the first one, otherwise we don't need to heck this case
> create session
>
> case isRead
> process read
>
> case isWrite
> process write
> done
>
> if dispose // In order to stop the server
> process dispose
> break
> done
>
> The key is to start all the selector workers *before* accepting any connection, otherwise we may lose some messages. One more thing : each selector should signal that there have started before entering in the loop, so that the caller don't have to wait a random period of time for the selectors to be started.
>
> 2) Events processing
> Now, in order to limit the number of selectors to use, we need to limit the time it takes to process the read/write/accept events. But even if we have many selectors, we should try to minimize the risk that one selector is blocked by a single session blocked somewhere while doing some heavy prcoessing, as it will block all the other sessions.
>
> Having more than a selector is one way to mitigate this issue : as we have many threads (one per selector), we spread the loads on as many threads.
> Another solution would be to use an executor in charge of processing the events, with a queue between the selector and the executor, queue that is used to process the events as fast as possible on the selector (this is really important for UDP, as we don't want to lose messages simply because the OS buffer is full).
>
> The problem is that we are just not solving the problem of a rogue service that block a thread for a long time (if we use a limited size executor), or we may end with so many threads that it may kill the server. But anyway, it sounds like a better solution, as incoming events that won't require a long processing will be favored in the long term.
>
> 3) Write processing
> This is a complex issue too : we may not be able to push all the data we want into the socket, if it becoms full (or was already full). In this case, we will have to store the data in a queue. The following algorithm describe this situation and a proposal to solve it
>
> if there are some data in the writeQueue
> then
> // We need to enqueue the data, and write the head of the queue
> enqueue data
>
> // Now, we shoudl try to write as much of the queue as we can
> while ( queue not empty)
> do
> poll the data from the queue
> nbWritten = channel.write( remaining data ) // We may have already written some part of the head data
>
> if nbWritten < data.remaining
> then
> // the socket is already full, set its OP_WRITE interest, and don't touch the queue
> selectionKey.ops = OP_WRITE
> break // We can stop, the socket is full anyway
> else
> pop the data from the queue // We just remove the head, and continue with the next data
> done
>
> if queue is empry
> then
> selectionKeys.ops = ! OP_WRITE // We remoe this flag, it's ueless now
> else
> nbWritten = channel.write( remaining data ) // We may have already written some part of the head data
>
> if nbWritten < data.remaining
> then
> // the socket is already full, set its OP_WRITE interest, and add the data in the queue
> selectionKey.ops = OP_WRITE
> writeQueue.add( remaining data )
>
> 4) Read/Write order
> MINA 2 was processing all the reads first, then all the writes. This is not necessarilly a good idea. We may rather process read and write on a per session basis. It's perfectly possible that a session has to process some reads and some write at the same time. Waiting for all the reads to be processed create some memory load, as we will have to wait until all the reads are done, storing all the data to be written in the mean time, until we are done with the last session.
>
> 5) MINA events
> Atm, we are processing the following events :
> - messageReceived (when we have read some data from the channel)
> - messageSent (when a user message has been sentcompletely)
> - exceptionCaugth (when an exception has been caught)
> - sessionIdle (if the session has not received or sent anything for a period of time)
> - sessionCreated (when a new session is created)
> - sessionOpened
> - sessionClosed (when the session has been closed)
> - filterWrite
> - filterClose
>
> The sessionOpened, filterWrite and filterClose are a bit mysterious. I don't see why we need a special event SessionOpened when we alreayd have the sessionCreated event. That may seems we *may* create a session, but not accept any incoming or outgoing messages for this session, until it's initialized. I'm not sure this is necessary.
>
> The two other events are probably some YAGNY iplementation...
>
> --
> Regards,
> Cordialement,
> Emmanuel Lécharny
> www.iktek.com
>
Re: [MINA 3.0] Thoughts on TCP server
Posted by Mike van Goor <mi...@probie.nl>.
Hello Emmanuel,
I think your ideas are golden, I just want to comment on one thing.
> The sessionOpened, filterWrite and filterClose are a bit mysterious. I
> don't see why we need a special event SessionOpened when we alreayd have
> the sessionCreated event. That may seems we *may* create a session, but
> not accept any incoming or outgoing messages for this session, until
> it's initialized. I'm not sure this is necessary.
The SessionOpened event tells us if a connection has been opened.
If you connect to a http server your hand the channel to a selector and
wait for the socket connected notice to start your program. This is
especially important of the side you are connecting to does not send a
message to indicate the server is ready (220 in smtp, 200 in ftp, etc).
I created a lot of servers that don't tell you anything and wait for the
client to send the first command. In which case I wait for the
socketconnected event to send this command.
So in short, I see a very valuable reason to have the socketconnected event.
I agree on the fact that the socketconnected event has no value for a
server socket.
Just my 2 cents.
Regards,
Mike
Op 1-10-2012 12:45, Emmanuel Lécharny schreef:
> Hi guys, some thoughts about the TCP server. feel free to comment.
>
> TCP server MINA 3
> -----------------
>
> As we are reworking the server part of MINA, we can review the current
> architecture. There are a few problems we can address.
>
> 1) One vs Many selectors
> In MINA 2, we do have at least 2 selectors used :
> - one for the OP_ACCEPT event
> - One or many for the OP_READ/OP_WRITE
>
> I don't think that it makes a lot of sense to force such a choice. IMO,
> we could perfectly start with one single selector to handle all the
> events, assuming we are fast enough to process them. Otherwise, we can
> also configure the server to use more selectors, if needed.
>
> Typically, the acceptor selector will just deal with incoming new
> connections, and the created channel will be registred on an IoProcessor
> selector on MINA 2. We could register this channel on the acceptor
> selector.
>
> In any case, if we do create more than one selector, I suggest that the
> first one would always handle OP_ACCEPT events, and that's all.
>
> The general algorithm will look likes :
>
> signal started
>
> while true
> nbSelect = select( delay )
>
> if nbSelect != 0
> for each selectionKey do
> case isAccept // Only if the selector is the first one, otherwise
> we don't need to heck this case
> create session
>
> case isRead
> process read
>
> case isWrite
> process write
> done
>
> if dispose // In order to stop the server
> process dispose
> break
> done
>
> The key is to start all the selector workers *before* accepting any
> connection, otherwise we may lose some messages. One more thing : each
> selector should signal that there have started before entering in the
> loop, so that the caller don't have to wait a random period of time for
> the selectors to be started.
>
> 2) Events processing
> Now, in order to limit the number of selectors to use, we need to limit
> the time it takes to process the read/write/accept events. But even if
> we have many selectors, we should try to minimize the risk that one
> selector is blocked by a single session blocked somewhere while doing
> some heavy prcoessing, as it will block all the other sessions.
>
> Having more than a selector is one way to mitigate this issue : as we
> have many threads (one per selector), we spread the loads on as many
> threads.
> Another solution would be to use an executor in charge of processing the
> events, with a queue between the selector and the executor, queue that
> is used to process the events as fast as possible on the selector (this
> is really important for UDP, as we don't want to lose messages simply
> because the OS buffer is full).
>
> The problem is that we are just not solving the problem of a rogue
> service that block a thread for a long time (if we use a limited size
> executor), or we may end with so many threads that it may kill the
> server. But anyway, it sounds like a better solution, as incoming events
> that won't require a long processing will be favored in the long term.
>
> 3) Write processing
> This is a complex issue too : we may not be able to push all the data we
> want into the socket, if it becoms full (or was already full). In this
> case, we will have to store the data in a queue. The following algorithm
> describe this situation and a proposal to solve it
>
> if there are some data in the writeQueue
> then
> // We need to enqueue the data, and write the head of the queue
> enqueue data
>
> // Now, we shoudl try to write as much of the queue as we can
> while ( queue not empty)
> do
> poll the data from the queue
> nbWritten = channel.write( remaining data ) // We may have
> already written some part of the head data
>
> if nbWritten < data.remaining
> then
> // the socket is already full, set its OP_WRITE
> interest, and don't touch the queue
> selectionKey.ops = OP_WRITE
> break // We can stop, the socket is full anyway
> else
> pop the data from the queue // We just remove the head,
> and continue with the next data
> done
>
> if queue is empry
> then
> selectionKeys.ops = ! OP_WRITE // We remoe this flag,
> it's ueless now
> else
> nbWritten = channel.write( remaining data ) // We may have
> already written some part of the head data
>
> if nbWritten < data.remaining
> then
> // the socket is already full, set its OP_WRITE interest,
> and add the data in the queue
> selectionKey.ops = OP_WRITE
> writeQueue.add( remaining data )
>
> 4) Read/Write order
> MINA 2 was processing all the reads first, then all the writes. This is
> not necessarilly a good idea. We may rather process read and write on a
> per session basis. It's perfectly possible that a session has to process
> some reads and some write at the same time. Waiting for all the reads to
> be processed create some memory load, as we will have to wait until all
> the reads are done, storing all the data to be written in the mean time,
> until we are done with the last session.
>
> 5) MINA events
> Atm, we are processing the following events :
> - messageReceived (when we have read some data from the channel)
> - messageSent (when a user message has been sentcompletely)
> - exceptionCaugth (when an exception has been caught)
> - sessionIdle (if the session has not received or sent anything for a
> period of time)
> - sessionCreated (when a new session is created)
> - sessionOpened
> - sessionClosed (when the session has been closed)
> - filterWrite
> - filterClose
>
> The sessionOpened, filterWrite and filterClose are a bit mysterious. I
> don't see why we need a special event SessionOpened when we alreayd have
> the sessionCreated event. That may seems we *may* create a session, but
> not accept any incoming or outgoing messages for this session, until
> it's initialized. I'm not sure this is necessary.
>
> The two other events are probably some YAGNY iplementation...
>