java socket
1.什么是nio
阻塞/非阻塞与同步/异步
1.1 阻塞io
阻塞 I/O 发起的 read 请求,线程会被挂起,一直等到内核数据准备好,并把数据从内核区域拷贝到应用程序的缓冲区中,当拷贝过程完成,read 请求调用才返回。接下来,应用程序就可以对缓冲区的数据进行数据解析。
1.2 非阻塞
非阻塞的 read 请求在数据未准备好的情况下立即返回,应用程序可以不断轮询内核,直到数据准备好,内核将数据拷贝到应用程序缓冲,并完成这次 read 调用。注意,这里最后一次 read 调用,获取数据的过程,是一个同步的过程。这里的同步指的是内核区域的数据拷贝到缓存区这个过程。
那么这时候我们需要不停的去轮询查询内核io是否已经准备好了
于是有了我们的select、poll的多路复用技术出现(NIO)
1.3 同步?异步?
其实上述的多路复用都是同步调用技术。为什么这么说呢?因为同步调用、异步调用的说法,是对于获取数据的过程而言的,前面几种最后获取数据的 read 操作调用,都是同步的,在 read 调用时,内核将数据从内核空间拷贝到应用程序空间,这个过程是在 read 函数中同步进行的,如果内核实现的拷贝效率很差,read 调用就会在这个同步过程中消耗比较长的时间。
而真正的异步是当我们发起 aio_read 之后 ,内核自动将数据从内核空间拷贝到应用程序空间,这个拷贝过程是异步的,内核自动完成的,和前面的同步操作不一样,应用程序并不需要主动发起拷贝动作。
这也就是我们所说的AIO了
2.Selector
2.1 Selector.open()
我们来看如下代码
Selector.open();
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
这里会创建一个provider
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<>() {
public SelectorProvider run() {
if(loadProviderFromProperty())
return provider;
if(loadProviderAsService())
return provider;
provider=sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
这里调用了DefaultSelectorProvider
创建SelectorProvider
在windows环境中这里创建的是WindowsSelectorProvider
public static SelectorProvider create() {
return new sun.nio.ch.WindowsSelectorProvider();
}
随后调用WindowsSelectorProvider.openSelector
public AbstractSelector openSelector() throws IOException {
return new WindowsSelectorImpl(this);
}
那么我们可以来看这个构造器做了什么事
WindowsSelectorImpl(SelectorProvider sp) throws IOException {
super(sp);
pollWrapper = new PollArrayWrapper(INIT_CAP);
wakeupPipe = Pipe.open();
wakeupSourceFd = ((SelChImpl)wakeupPipe.source()).getFDVal();
// Disable the Nagle algorithm so that the wakeup is more immediate
SinkChannelImpl sink = (SinkChannelImpl)wakeupPipe.sink();
(sink.sc).socket().setTcpNoDelay(true);
wakeupSinkFd = ((SelChImpl)sink).getFDVal();
pollWrapper.addWakeupSocket(wakeupSourceFd, 0);
}
这里调用了super(sp)
keys = ConcurrentHashMap.newKeySet();
selectedKeys = new HashSet<>();
publicKeys = Collections.unmodifiableSet(keys);
publicSelectedKeys = Util.ungrowableSet(selectedKeys);
初始化了key并把SelectorProvider
放入Selector
对象中
接下来我们继续看创建了一个PollArrayWrapper
对象,该对象相当重要java是通过该数组,把事件传输给c中去调用select函数,select参数的读写事件均来自于该数组
这个对象是个包装对象注释如下
Manipulates a native array of structs corresponding to (fd, events) pairs.
typedef struct pollfd {
SOCKET fd; // 4 bytes
short events; // 2 bytes
} pollfd_t;
构造器如下
private AllocatedNativeObject pollArray; // The fd array
long pollArrayAddress; // pollArrayAddress
@Native private static final short FD_OFFSET = 0; // fd offset in pollfd
@Native private static final short EVENT_OFFSET = 4; // events offset in pollfd
static short SIZE_POLLFD = 8; // sizeof pollfd struct
private int size; // Size of the pollArray
PollArrayWrapper(int newSize) {
int allocationSize = newSize * SIZE_POLLFD;
pollArray = new AllocatedNativeObject(allocationSize, true);
pollArrayAddress = pollArray.address();
this.size = newSize;
}
在C中其实际struct如下
typedef struct {
jint fd;
jshort events;
} pollfd;
这个之后会用到,那么接下来我们看wakeupPipe = Pipe.open();
,什么是Pipe呢,我们先来看一下java中的注释
/**
* A pair of channels that implements a unidirectional pipe.
* 一对实现单向管道的通道。
* <p> A pipe consists of a pair of channels: A writable {@link
* Pipe.SinkChannel sink} channel and a readable {@link Pipe.SourceChannel source}
* channel. Once some bytes are written to the sink channel they can be read
* from the source channel in exactly the order in which they were written.
* 管道由一对通道组成,一个可写的SinkChannel和一个可读的SourceChannel
* <p> Whether or not a thread writing bytes to a pipe will block until another
* thread reads those bytes, or some previously-written bytes, from the pipe is
* system-dependent and therefore unspecified. Many pipe implementations will
* buffer up to a certain number of bytes between the sink and source channels,
* but such buffering should not be assumed. </p>
*
*
* @author Mark Reinhold
* @author JSR-51 Expert Group
* @since 1.4
*/
可以看到Pipe这里有2个Channel那么我们继续
public static Pipe open() throws IOException {
return SelectorProvider.provider().openPipe();
}
public Pipe openPipe() throws IOException {
return new PipeImpl(this);
}
PipeImpl(final SelectorProvider sp) throws IOException {
try {
AccessController.doPrivileged(new Initializer(sp));
} catch (PrivilegedActionException x) {
throw (IOException)x.getCause();
}
}
这里AccessController.doPrivileged(new Initializer(sp));
这里使用了特权调用
为了方便查看这里贴出PipeImpl
class PipeImpl
extends Pipe
{
// Number of bytes in the secret handshake.
private static final int NUM_SECRET_BYTES = 16;
// Random object for handshake values
private static final Random RANDOM_NUMBER_GENERATOR = new SecureRandom();
// Source and sink channels
private SourceChannel source;
private SinkChannel sink;
private class Initializer
implements PrivilegedExceptionAction<Void>
{
private final SelectorProvider sp;
private IOException ioe = null;
private Initializer(SelectorProvider sp) {
this.sp = sp;
}
@Override
public Void run() throws IOException {
LoopbackConnector connector = new LoopbackConnector();
connector.run();
if (ioe instanceof ClosedByInterruptException) {
ioe = null;
Thread connThread = new Thread(connector) {
@Override
public void interrupt() {
}
};
connThread.start();
for (;;) {
try {
connThread.join();
break;
} catch (InterruptedException ex) {
}
}
Thread.currentThread().interrupt();
}
if (ioe != null)
throw new IOException("Unable to establish loopback connection", ioe);
return null;
}
private class LoopbackConnector implements Runnable {
@Override
public void run() {
ServerSocketChannel ssc = null;
SocketChannel sc1 = null;
SocketChannel sc2 = null;
try {
// Create secret with a backing array.
ByteBuffer secret = ByteBuffer.allocate(NUM_SECRET_BYTES);
ByteBuffer bb = ByteBuffer.allocate(NUM_SECRET_BYTES);
// Loopback address
InetAddress lb = InetAddress.getLoopbackAddress();
assert(lb.isLoopbackAddress());
InetSocketAddress sa = null;
for(;;) {
// Bind ServerSocketChannel to a port on the loopback
// address
if (ssc == null || !ssc.isOpen()) {
ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(lb, 0));
sa = new InetSocketAddress(lb, ssc.socket().getLocalPort());
}
// Establish connection (assume connections are eagerly
// accepted)
sc1 = SocketChannel.open(sa);
RANDOM_NUMBER_GENERATOR.nextBytes(secret.array());
do {
sc1.write(secret);
} while (secret.hasRemaining());
secret.rewind();
// Get a connection and verify it is legitimate
sc2 = ssc.accept();
do {
sc2.read(bb);
} while (bb.hasRemaining());
bb.rewind();
if (bb.equals(secret))
break;
sc2.close();
sc1.close();
}
// Create source and sink channels
source = new SourceChannelImpl(sp, sc1);
sink = new SinkChannelImpl(sp, sc2);
} catch (IOException e) {
try {
if (sc1 != null)
sc1.close();
if (sc2 != null)
sc2.