Context: I've recently started using java.nio
for my project which leverages Android's VpnService
. In my implementation, I've wrapped the FileDescriptor
that is returned by the establish()
method of the VpnService
into a java.nio.FileChannel
as shown below.
private val outboundNetworkChannel = FileInputStream(fd).channel
After that, I've a kotlin coroutine which reads from the FileChannel
indefinitely and processes the outbound IPv4 / IPv6 packets.
Issue: The below mentioned snippet works, but I see a lot of empty reads happening from the FileChannel
which in turn spins the while
loop unnecessarily.
fun reader() = scope.launch(handler) {
while (isActive) {
val pkt = read()
if(pkt !== DUMMY){
// Send the read IPv4/IPv6 packet for processing
}
}
}
private suspend fun read(): IPDatagram =
withContext(Dispatchers.IO) {
val bytes = ByteBufferPool.acquire()
outboundChannel.read(bytes) // Returns a lot of empty reads with return value as 0
return@withContext marshal(bytes) // Read IPv4/IPv6 headers and wrap the packet
}
What I'm looking for: For a fact, I know that FileChannel
is a blocking channel and in this case since the channel is backed by a network interface, it might not have packets ready to be read. Is there a better approach with / without FileChannel
which would lead to a more efficient implementation without wasting precious CPU cycles? I'm open to new ideas as well :)
I managed to figure this out after digging through the Android Docs for VpnService
. By default, when a VPN connection is established using VpnService.Builder
the fd
is in non-blocking mode. Starting API level 21, one can setBlocking(true)
.
As stated in the docs for public VpnService.Builder setBlocking (boolean blocking)
Sets the VPN interface's file descriptor to be in blocking/non-blocking mode. By default, the file descriptor returned by establish() is non-blocking.