Today, I detected a bug in my project. After reading data on the NIOServer, I chose to remove the iterator obtained at first, but found that the SelectionKey obtained in selectNow() still existed, and then directly selectionkey.cancel (); Then you can only send data once. This is due to my initial contact with NIO and lack of in-depth understanding of the specific implementation of each module. I will read the source code to understand the relationship between SelectionKey, Channel and Selector to sort out relevant knowledge.Copy the code
————– split line ————–
Because the Channel is only interface, so continue to obtain the method of implementation, the final positioning to AbstractSelectableChannel class, in order to facilitate reading, and in line with the theme, I omitted some irrelevant code, the relevant implementation is as follows:
public final SelectionKey register(Selector sel, int ops,
Object att)
throws ClosedChannelException
{
synchronized (regLock) {
/* Exception status check code, omit */
SelectionKey k = findKey(sel);
if(k ! =null) {
k.interestOps(ops);
k.attach(att);
}
if (k == null) {
synchronized (keyLock) {
/* Exception status check code, omit */
k = ((AbstractSelector)sel).register(this, ops, att); addKey(k); }}returnk; }}Copy the code
So you can see that when a Channel registers with a Selector it calls the local method findKey and passes in the Selector to get the corresponding SelectionKey, and the argument is just a Selector, there’s no state associated with it, So I know that the SelectionKey corresponds to a Selector, and when I cancel it out, I’m actually unregistering this Channel from the Selector, so that it can only pass once in the same connection, Because I’ve already unread it from the Selector while I’m waiting for the next IO.
Accordingly, if the SelectionKey is not null, then the Channel is already registered with the target Selector, so just add the target state to the SelectionKey.
If the SelectionKey is null, it is created directly by the Selector’s registration method and added to the Channel.
————– split line ————–
So let’s go ahead and look at these two methods, starting with addKey
private void addKey(SelectionKey k) {
assert Thread.holdsLock(keyLock);
int i = 0;
if((keys ! =null) && (keyCount < keys.length)) {
// Find empty element of key array
for (i = 0; i < keys.length; i++)
if (keys[i] == null)
break;
} else if (keys == null) {
keys = new SelectionKey[3];
} else {
// Grow key array
int n = keys.length * 2;
SelectionKey[] ks = new SelectionKey[n];
for (i = 0; i < keys.length; i++)
ks[i] = keys[i];
keys = ks;
i = keyCount;
}
keys[i] = k;
keyCount++;
}
Copy the code
The key is an array of selectionKeys that holds the SelectionKey object generated by the Selector bound to the Channel. The default length is 3, and the default length is 2 for each capacity expansion.
FindKey is easy to guess what it does: it iterates through the keys array and compares the SelectionKey to the Selector.
private SelectionKey findKey(Selector sel) {
synchronized (keyLock) {
if (keys == null)
return null;
for (int i = 0; i < keys.length; i++)
if((keys[i] ! =null) && (keys[i].selector() == sel))
return keys[i];
return null; }}Copy the code
So if we have find and AND then we have remove
void removeKey(SelectionKey k) { // package-private
synchronized (keyLock) {
for (int i = 0; i < keys.length; i++)
if (keys[i] == k) {
keys[i] = null; keyCount--; } ((AbstractSelectionKey)k).invalidate(); }}Copy the code
conclusion
Channels and selectors are registered with a unique SelectionKey.