1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.channel.epoll;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.buffer.ByteBufAllocator;
20 import io.netty.buffer.ByteBufUtil;
21 import io.netty.buffer.Unpooled;
22 import io.netty.channel.AbstractChannel;
23 import io.netty.channel.Channel;
24 import io.netty.channel.ChannelConfig;
25 import io.netty.channel.ChannelException;
26 import io.netty.channel.ChannelFuture;
27 import io.netty.channel.ChannelFutureListener;
28 import io.netty.channel.ChannelMetadata;
29 import io.netty.channel.ChannelOutboundBuffer;
30 import io.netty.channel.ChannelPromise;
31 import io.netty.channel.ConnectTimeoutException;
32 import io.netty.channel.EventLoop;
33 import io.netty.channel.IoEvent;
34 import io.netty.channel.IoEventLoop;
35 import io.netty.channel.IoRegistration;
36 import io.netty.channel.RecvByteBufAllocator;
37 import io.netty.channel.socket.ChannelInputShutdownEvent;
38 import io.netty.channel.socket.ChannelInputShutdownReadComplete;
39 import io.netty.channel.socket.SocketChannelConfig;
40 import io.netty.channel.unix.FileDescriptor;
41 import io.netty.channel.unix.IovArray;
42 import io.netty.channel.unix.Socket;
43 import io.netty.channel.unix.UnixChannel;
44 import io.netty.util.ReferenceCountUtil;
45 import io.netty.util.concurrent.Future;
46
47 import java.io.IOException;
48 import java.io.UncheckedIOException;
49 import java.net.InetSocketAddress;
50 import java.net.SocketAddress;
51 import java.nio.ByteBuffer;
52 import java.nio.channels.AlreadyConnectedException;
53 import java.nio.channels.ClosedChannelException;
54 import java.nio.channels.ConnectionPendingException;
55 import java.nio.channels.NotYetConnectedException;
56 import java.nio.channels.UnresolvedAddressException;
57 import java.util.concurrent.TimeUnit;
58
59 import static io.netty.channel.epoll.EpollIoOps.EPOLL_ERR_IN_MASK;
60 import static io.netty.channel.epoll.EpollIoOps.EPOLL_ERR_OUT_MASK;
61 import static io.netty.channel.epoll.EpollIoOps.EPOLL_RDHUP_MASK;
62 import static io.netty.channel.internal.ChannelUtils.WRITE_STATUS_SNDBUF_FULL;
63 import static io.netty.channel.unix.UnixChannelUtil.computeRemoteAddr;
64 import static io.netty.util.internal.ObjectUtil.checkNotNull;
65
66 abstract class AbstractEpollChannel extends AbstractChannel implements UnixChannel {
67 private static final ChannelMetadata METADATA = new ChannelMetadata(false);
68 protected final LinuxSocket socket;
69 private final EpollIoOps initial;
70
71
72
73
74
75 private ChannelPromise connectPromise;
76 private Future<?> connectTimeoutFuture;
77 private SocketAddress requestedRemoteAddress;
78 private volatile SocketAddress local;
79 private volatile SocketAddress remote;
80
81
82
83 private ChannelOutboundBuffer fastOpenOutboundBuffer;
84 private Object fastOpenData;
85 private long fastOpenBytes;
86
87 private IoRegistration registration;
88 boolean inputClosedSeenErrorOnRead;
89 private EpollIoOps ops;
90
91 protected volatile boolean active;
92
93 AbstractEpollChannel(Channel parent, LinuxSocket fd, boolean active, EpollIoOps initialOps) {
94 super(parent);
95 this.socket = checkNotNull(fd, "fd");
96 this.active = active;
97 if (active) {
98
99
100 this.local = fd.localAddress();
101 this.remote = fd.remoteAddress();
102 }
103 this.initial = initialOps;
104 this.ops = initialOps;
105 }
106
107 AbstractEpollChannel(Channel parent, LinuxSocket fd, SocketAddress remote, EpollIoOps initialOps) {
108 super(parent);
109 this.socket = checkNotNull(fd, "fd");
110 this.active = true;
111
112
113 this.remote = remote;
114 this.local = fd.localAddress();
115 this.initial = initialOps;
116 this.ops = initialOps;
117 }
118
119 static boolean isSoErrorZero(Socket fd) {
120 try {
121 return fd.getSoError() == 0;
122 } catch (IOException e) {
123 throw new ChannelException(e);
124 }
125 }
126
127 protected void setFlag(int flag) throws IOException {
128 if (ops.contains(flag)) {
129
130 return;
131 }
132 ops = ops.with(EpollIoOps.valueOf(flag));
133 if (isRegistered()) {
134 IoRegistration registration = registration();
135 registration.submit(ops);
136 } else {
137 ops = ops.with(EpollIoOps.valueOf(flag));
138 }
139 }
140
141 void clearFlag(int flag) throws IOException {
142 IoRegistration registration = registration();
143 if (!ops.contains(flag)) {
144
145 return;
146 }
147 ops = ops.without(EpollIoOps.valueOf(flag));
148 registration.submit(ops);
149 }
150
151 protected final IoRegistration registration() {
152 assert registration != null;
153 return registration;
154 }
155
156 boolean isFlagSet(int flag) {
157 return (ops.value & flag) != 0;
158 }
159
160 @Override
161 public final FileDescriptor fd() {
162 return socket;
163 }
164
165 @Override
166 public abstract EpollChannelConfig config();
167
168 @Override
169 public boolean isActive() {
170 return active;
171 }
172
173 @Override
174 public ChannelMetadata metadata() {
175 return METADATA;
176 }
177
178
179
180
181
182 final void fastOpenDataSent(ChannelOutboundBuffer in, Object data, long bytes) {
183 fastOpenOutboundBuffer = in;
184 fastOpenData = data;
185 fastOpenBytes = bytes;
186 }
187
188
189
190
191
192
193
194
195 final boolean removeFastOpenData() {
196 ChannelOutboundBuffer in = fastOpenOutboundBuffer;
197 if (in == null) {
198 return false;
199 }
200 Object data = fastOpenData;
201 long bytes = fastOpenBytes;
202 fastOpenOutboundBuffer = null;
203 fastOpenData = null;
204 fastOpenBytes = 0;
205 if (!isActive() || in.current() != data) {
206 return false;
207 }
208 in.removeBytes(bytes);
209 return true;
210 }
211
212 @Override
213 protected void doClose() throws Exception {
214 active = false;
215
216
217 inputClosedSeenErrorOnRead = true;
218 try {
219 ChannelPromise promise = connectPromise;
220 if (promise != null) {
221
222 promise.tryFailure(new ClosedChannelException());
223 connectPromise = null;
224 }
225
226 Future<?> future = connectTimeoutFuture;
227 if (future != null) {
228 future.cancel(false);
229 connectTimeoutFuture = null;
230 }
231
232 if (isRegistered()) {
233
234
235
236
237 EventLoop loop = eventLoop();
238 if (loop.inEventLoop()) {
239 doDeregister();
240 } else {
241 loop.execute(new Runnable() {
242 @Override
243 public void run() {
244 try {
245 doDeregister();
246 } catch (Throwable cause) {
247 pipeline().fireExceptionCaught(cause);
248 }
249 }
250 });
251 }
252 }
253 } finally {
254 socket.close();
255 }
256 }
257
258 void resetCachedAddresses() {
259 local = socket.localAddress();
260 remote = socket.remoteAddress();
261 }
262
263 @Override
264 protected void doDisconnect() throws Exception {
265 doClose();
266 }
267
268 @Override
269 public boolean isOpen() {
270 return socket.isOpen();
271 }
272
273 @Override
274 protected void doDeregister() throws Exception {
275 IoRegistration registration = this.registration;
276 if (registration != null) {
277 ops = initial;
278 registration.cancel();
279 }
280 }
281
282 @Override
283 protected boolean isCompatible(EventLoop loop) {
284 return loop instanceof IoEventLoop && ((IoEventLoop) loop).isCompatible(AbstractEpollUnsafe.class);
285 }
286
287 @Override
288 protected void doBeginRead() throws Exception {
289
290 final AbstractEpollUnsafe unsafe = (AbstractEpollUnsafe) unsafe();
291 unsafe.readPending = true;
292
293
294
295
296 setFlag(Native.EPOLLIN);
297 }
298
299 final boolean shouldBreakEpollInReady(ChannelConfig config) {
300 return socket.isInputShutdown() && (inputClosedSeenErrorOnRead || !isAllowHalfClosure(config));
301 }
302
303 private static boolean isAllowHalfClosure(ChannelConfig config) {
304 if (config instanceof EpollDomainSocketChannelConfig) {
305 return ((EpollDomainSocketChannelConfig) config).isAllowHalfClosure();
306 }
307 return config instanceof SocketChannelConfig &&
308 ((SocketChannelConfig) config).isAllowHalfClosure();
309 }
310
311 final void clearEpollIn() {
312
313 if (isRegistered()) {
314 final EventLoop loop = eventLoop();
315 final AbstractEpollUnsafe unsafe = (AbstractEpollUnsafe) unsafe();
316 if (loop.inEventLoop()) {
317 unsafe.clearEpollIn0();
318 } else {
319
320 loop.execute(new Runnable() {
321 @Override
322 public void run() {
323 if (!unsafe.readPending && !config().isAutoRead()) {
324
325 unsafe.clearEpollIn0();
326 }
327 }
328 });
329 }
330 } else {
331
332
333 ops = ops.without(EpollIoOps.EPOLLIN);
334 }
335 }
336
337 @Override
338 protected void doRegister(ChannelPromise promise) {
339 ((IoEventLoop) eventLoop()).register((AbstractEpollUnsafe) unsafe()).addListener(f -> {
340 if (f.isSuccess()) {
341 registration = (IoRegistration) f.getNow();
342 if (isActive()) {
343
344 submitCurrentOps();
345 }
346 promise.setSuccess();
347 } else {
348 promise.setFailure(f.cause());
349 }
350 });
351 }
352
353 @Override
354 protected abstract AbstractEpollUnsafe newUnsafe();
355
356
357
358
359 protected final ByteBuf newDirectBuffer(ByteBuf buf) {
360 return newDirectBuffer(buf, buf);
361 }
362
363
364
365
366
367
368 protected final ByteBuf newDirectBuffer(Object holder, ByteBuf buf) {
369 final int readableBytes = buf.readableBytes();
370 if (readableBytes == 0) {
371 ReferenceCountUtil.release(holder);
372 return Unpooled.EMPTY_BUFFER;
373 }
374
375 final ByteBufAllocator alloc = alloc();
376 if (alloc.isDirectBufferPooled()) {
377 return newDirectBuffer0(holder, buf, alloc, readableBytes);
378 }
379
380 final ByteBuf directBuf = ByteBufUtil.threadLocalDirectBuffer();
381 if (directBuf == null) {
382 return newDirectBuffer0(holder, buf, alloc, readableBytes);
383 }
384
385 directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
386 ReferenceCountUtil.safeRelease(holder);
387 return directBuf;
388 }
389
390 private static ByteBuf newDirectBuffer0(Object holder, ByteBuf buf, ByteBufAllocator alloc, int capacity) {
391 final ByteBuf directBuf = alloc.directBuffer(capacity);
392 directBuf.writeBytes(buf, buf.readerIndex(), capacity);
393 ReferenceCountUtil.safeRelease(holder);
394 return directBuf;
395 }
396
397 protected static void checkResolvable(InetSocketAddress addr) {
398 if (addr.isUnresolved()) {
399 throw new UnresolvedAddressException();
400 }
401 }
402
403
404
405
406 protected final int doReadBytes(ByteBuf byteBuf) throws Exception {
407 int writerIndex = byteBuf.writerIndex();
408 int localReadAmount;
409 unsafe().recvBufAllocHandle().attemptedBytesRead(byteBuf.writableBytes());
410 if (byteBuf.hasMemoryAddress()) {
411 localReadAmount = socket.recvAddress(byteBuf.memoryAddress(), writerIndex, byteBuf.capacity());
412 } else {
413 ByteBuffer buf = byteBuf.internalNioBuffer(writerIndex, byteBuf.writableBytes());
414 localReadAmount = socket.recv(buf, buf.position(), buf.limit());
415 }
416 if (localReadAmount > 0) {
417 byteBuf.writerIndex(writerIndex + localReadAmount);
418 }
419 return localReadAmount;
420 }
421
422 protected final int doWriteBytes(ChannelOutboundBuffer in, ByteBuf buf) throws Exception {
423 if (buf.hasMemoryAddress()) {
424 int localFlushedAmount = socket.sendAddress(buf.memoryAddress(), buf.readerIndex(), buf.writerIndex());
425 if (localFlushedAmount > 0) {
426 in.removeBytes(localFlushedAmount);
427 return 1;
428 }
429 } else {
430 final ByteBuffer nioBuf = buf.nioBufferCount() == 1 ?
431 buf.internalNioBuffer(buf.readerIndex(), buf.readableBytes()) : buf.nioBuffer();
432 int localFlushedAmount = socket.send(nioBuf, nioBuf.position(), nioBuf.limit());
433 if (localFlushedAmount > 0) {
434 nioBuf.position(nioBuf.position() + localFlushedAmount);
435 in.removeBytes(localFlushedAmount);
436 return 1;
437 }
438 }
439 return WRITE_STATUS_SNDBUF_FULL;
440 }
441
442
443
444
445
446 final long doWriteOrSendBytes(ByteBuf data, InetSocketAddress remoteAddress, boolean fastOpen)
447 throws IOException {
448 assert !(fastOpen && remoteAddress == null) : "fastOpen requires a remote address";
449 if (data.hasMemoryAddress()) {
450 long memoryAddress = data.memoryAddress();
451 if (remoteAddress == null) {
452 return socket.sendAddress(memoryAddress, data.readerIndex(), data.writerIndex());
453 }
454 return socket.sendToAddress(memoryAddress, data.readerIndex(), data.writerIndex(),
455 remoteAddress.getAddress(), remoteAddress.getPort(), fastOpen);
456 }
457
458 if (data.nioBufferCount() > 1) {
459 IovArray array = ((NativeArrays) registration.attachment()).cleanIovArray();
460 array.add(data, data.readerIndex(), data.readableBytes());
461 int cnt = array.count();
462 assert cnt != 0;
463
464 if (remoteAddress == null) {
465 return socket.writevAddresses(array.memoryAddress(0), cnt);
466 }
467 return socket.sendToAddresses(array.memoryAddress(0), cnt,
468 remoteAddress.getAddress(), remoteAddress.getPort(), fastOpen);
469 }
470
471 ByteBuffer nioData = data.internalNioBuffer(data.readerIndex(), data.readableBytes());
472 if (remoteAddress == null) {
473 return socket.send(nioData, nioData.position(), nioData.limit());
474 }
475 return socket.sendTo(nioData, nioData.position(), nioData.limit(),
476 remoteAddress.getAddress(), remoteAddress.getPort(), fastOpen);
477 }
478
479 protected abstract class AbstractEpollUnsafe extends AbstractUnsafe implements EpollIoHandle {
480 boolean readPending;
481 private EpollRecvByteAllocatorHandle allocHandle;
482
483 Channel channel() {
484 return AbstractEpollChannel.this;
485 }
486
487 @Override
488 public FileDescriptor fd() {
489 return AbstractEpollChannel.this.fd();
490 }
491
492 @Override
493 public void close() {
494 close(voidPromise());
495 }
496
497 @Override
498 public void handle(IoRegistration registration, IoEvent event) {
499 EpollIoEvent epollEvent = (EpollIoEvent) event;
500 int ops = epollEvent.ops().value;
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515 if ((ops & EPOLL_ERR_OUT_MASK) != 0) {
516
517 epollOutReady();
518 }
519
520
521
522
523
524
525 if ((ops & EPOLL_ERR_IN_MASK) != 0) {
526
527 epollInReady();
528 }
529
530
531
532
533 if ((ops & EPOLL_RDHUP_MASK) != 0) {
534 epollRdHupReady();
535 }
536 }
537
538
539
540
541 abstract void epollInReady();
542
543 final boolean shouldStopReading(ChannelConfig config) {
544
545
546
547
548
549
550 return !readPending && !config.isAutoRead();
551 }
552
553
554
555
556 final void epollRdHupReady() {
557
558 recvBufAllocHandle().receivedRdHup();
559
560 if (isActive()) {
561
562
563
564 epollInReady();
565 } else {
566
567 shutdownInput(false);
568 }
569
570
571 clearEpollRdHup();
572 }
573
574
575
576
577 private void clearEpollRdHup() {
578 try {
579 clearFlag(Native.EPOLLRDHUP);
580 } catch (IOException e) {
581 pipeline().fireExceptionCaught(e);
582 close(voidPromise());
583 }
584 }
585
586
587
588
589 void shutdownInput(boolean allDataRead) {
590 if (!socket.isInputShutdown()) {
591 if (isAllowHalfClosure(config())) {
592 try {
593 socket.shutdown(true, false);
594 } catch (IOException ignored) {
595
596
597 fireEventAndClose(ChannelInputShutdownEvent.INSTANCE);
598 return;
599 } catch (NotYetConnectedException ignore) {
600
601
602 }
603 if (shouldStopReading(config())) {
604 clearEpollIn0();
605 }
606 pipeline().fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
607 } else {
608 close(voidPromise());
609 return;
610 }
611 }
612
613 if (allDataRead && !inputClosedSeenErrorOnRead) {
614 inputClosedSeenErrorOnRead = true;
615 pipeline().fireUserEventTriggered(ChannelInputShutdownReadComplete.INSTANCE);
616 }
617 }
618
619 private void fireEventAndClose(Object evt) {
620 pipeline().fireUserEventTriggered(evt);
621 close(voidPromise());
622 }
623
624 @Override
625 public EpollRecvByteAllocatorHandle recvBufAllocHandle() {
626 if (allocHandle == null) {
627 allocHandle = newEpollHandle((RecvByteBufAllocator.ExtendedHandle) super.recvBufAllocHandle());
628 }
629 return allocHandle;
630 }
631
632
633
634
635
636 EpollRecvByteAllocatorHandle newEpollHandle(RecvByteBufAllocator.ExtendedHandle handle) {
637 return new EpollRecvByteAllocatorHandle(handle);
638 }
639
640 @Override
641 protected final void flush0() {
642
643
644
645 if (!isFlagSet(Native.EPOLLOUT)) {
646 super.flush0();
647 }
648 }
649
650
651
652
653 final void epollOutReady() {
654 if (connectPromise != null) {
655
656 finishConnect();
657 } else if (!socket.isOutputShutdown()) {
658
659 super.flush0();
660 }
661 }
662
663 protected final void clearEpollIn0() {
664 assert eventLoop().inEventLoop();
665 try {
666 readPending = false;
667 if (!ops.contains(EpollIoOps.EPOLLIN)) {
668 return;
669 }
670 ops = ops.without(EpollIoOps.EPOLLIN);
671 IoRegistration registration = registration();
672 registration.submit(ops);
673 } catch (UncheckedIOException e) {
674
675
676 pipeline().fireExceptionCaught(e);
677 unsafe().close(unsafe().voidPromise());
678 }
679 }
680
681 @Override
682 public void connect(
683 final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
684
685
686 if (promise.isDone() || !ensureOpen(promise)) {
687 return;
688 }
689
690 try {
691 if (connectPromise != null) {
692 throw new ConnectionPendingException();
693 }
694
695 boolean wasActive = isActive();
696 if (doConnect(remoteAddress, localAddress)) {
697 fulfillConnectPromise(promise, wasActive);
698 } else {
699 connectPromise = promise;
700 requestedRemoteAddress = remoteAddress;
701
702
703 final int connectTimeoutMillis = config().getConnectTimeoutMillis();
704 if (connectTimeoutMillis > 0) {
705 connectTimeoutFuture = eventLoop().schedule(new Runnable() {
706 @Override
707 public void run() {
708 ChannelPromise connectPromise = AbstractEpollChannel.this.connectPromise;
709 if (connectPromise != null && !connectPromise.isDone()
710 && connectPromise.tryFailure(new ConnectTimeoutException(
711 "connection timed out after " + connectTimeoutMillis + " ms: " +
712 remoteAddress))) {
713 close(voidPromise());
714 }
715 }
716 }, connectTimeoutMillis, TimeUnit.MILLISECONDS);
717 }
718
719 promise.addListener(new ChannelFutureListener() {
720 @Override
721 public void operationComplete(ChannelFuture future) {
722
723
724 if (future.isCancelled()) {
725 if (connectTimeoutFuture != null) {
726 connectTimeoutFuture.cancel(false);
727 }
728 connectPromise = null;
729 close(voidPromise());
730 }
731 }
732 });
733 }
734 } catch (Throwable t) {
735 closeIfClosed();
736 promise.tryFailure(annotateConnectException(t, remoteAddress));
737 }
738 }
739
740 private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
741 if (promise == null) {
742
743 return;
744 }
745 active = true;
746
747
748 submitCurrentOps();
749
750
751
752 boolean active = isActive();
753
754
755 boolean promiseSet = promise.trySuccess();
756
757
758
759 if (!wasActive && active) {
760 pipeline().fireChannelActive();
761 }
762
763
764
765 if (removeFastOpenData()) {
766 flush0();
767 }
768
769
770 if (!promiseSet) {
771 close(voidPromise());
772 }
773 }
774
775 private void fulfillConnectPromise(ChannelPromise promise, Throwable cause) {
776
777
778 removeFastOpenData();
779 if (promise == null) {
780
781 return;
782 }
783
784
785 promise.tryFailure(cause);
786 closeIfClosed();
787 }
788
789 private void finishConnect() {
790
791
792
793 assert eventLoop().inEventLoop();
794
795 boolean connectStillInProgress = false;
796 try {
797 boolean wasActive = isActive();
798 if (!doFinishConnect()) {
799 connectStillInProgress = true;
800 return;
801 }
802 fulfillConnectPromise(connectPromise, wasActive);
803 } catch (Throwable t) {
804 fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress));
805 } finally {
806 if (!connectStillInProgress) {
807
808
809 if (connectTimeoutFuture != null) {
810 connectTimeoutFuture.cancel(false);
811 }
812 connectPromise = null;
813 }
814 }
815 }
816
817
818
819
820 private boolean doFinishConnect() throws Exception {
821 if (socket.finishConnect()) {
822 clearFlag(Native.EPOLLOUT);
823 if (requestedRemoteAddress instanceof InetSocketAddress) {
824 remote = computeRemoteAddr((InetSocketAddress) requestedRemoteAddress, socket.remoteAddress());
825 }
826 requestedRemoteAddress = null;
827
828 return true;
829 }
830 setFlag(Native.EPOLLOUT);
831 return false;
832 }
833 }
834
835 @Override
836 protected void doBind(SocketAddress local) throws Exception {
837 if (local instanceof InetSocketAddress) {
838 checkResolvable((InetSocketAddress) local);
839 }
840 socket.bind(local);
841 this.local = socket.localAddress();
842 }
843
844
845
846
847 protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
848 if (localAddress instanceof InetSocketAddress) {
849 checkResolvable((InetSocketAddress) localAddress);
850 }
851
852 InetSocketAddress remoteSocketAddr = remoteAddress instanceof InetSocketAddress
853 ? (InetSocketAddress) remoteAddress : null;
854 if (remoteSocketAddr != null) {
855 checkResolvable(remoteSocketAddr);
856 }
857
858 if (remote != null) {
859
860
861
862 throw new AlreadyConnectedException();
863 }
864
865 if (localAddress != null) {
866 socket.bind(localAddress);
867 }
868
869 boolean connected = doConnect0(remoteAddress);
870 if (connected) {
871 remote = remoteSocketAddr == null ?
872 remoteAddress : computeRemoteAddr(remoteSocketAddr, socket.remoteAddress());
873 }
874
875
876
877 local = socket.localAddress();
878 return connected;
879 }
880
881 boolean doConnect0(SocketAddress remote) throws Exception {
882 boolean success = false;
883 try {
884 boolean connected = socket.connect(remote);
885 if (!connected) {
886 setFlag(Native.EPOLLOUT);
887 }
888 success = true;
889 return connected;
890 } finally {
891 if (!success) {
892 doClose();
893 }
894 }
895 }
896
897 final void submitCurrentOps() {
898 IoRegistration registration = registration();
899 registration.submit(ops);
900 }
901
902 @Override
903 protected SocketAddress localAddress0() {
904 return local;
905 }
906
907 @Override
908 protected SocketAddress remoteAddress0() {
909 return remote;
910 }
911 }