开篇
回想研究生期间在H3C做项目的时候第一次接触epoll的异步事件,心血来潮看了下java的NIO的实现,希望同样感兴趣的人一起看看。Netty是java NIO的集大成者,一定要看看。
java NIO server demo
socket server端工作标准流程
- 创建socket: 创建ServerSocketChannel,通过ServerSocketChannel.open()方法。
- 绑定socket:ServerSocketChannel绑定端口,通过serverSocketChannel.bind()方法。
- 前置准备: 创建selector对象,通过Selector.open()方法。
- 前置准备: 注册Channel到selector并绑定事件,通过serverSocketChannel.register()。
- 监听端口号: 通过listen()方法开始进入监听。
- 处理事件: while循环中等待select操作返回区分连接还是数据进行不同处理。
public class NIOServer {
private Selector selector;
public void initServer(int port) throws IOException {
// 获得一个ServerSocketChannel通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设置通道为非阻塞
serverSocketChannel.configureBlocking(false);
// 将该通道对应的ServerSocket绑定到port端口
serverSocketChannel.bind(new InetSocketAddress(port));
// 获得一个通道管理器
this.selector = Selector.open();
// 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,
// 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
public void listen() throws IOException {
System.out.println("服务端启动成功!");
// 轮询访问selector
while (true) {
// 当注册的事件到达时,方法返回;否则,该方法会一直阻塞
selector.select();
// 获得selector中选中的项的迭代器,选中的项为注册的事件
Iterator<SelectionKey> ite = this.selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
// 删除已选的key,以防重复处理
ite.remove();
if (key.isAcceptable()) {// 客户端请求连接事件
ServerSocketChannel server = (ServerSocketChannel) key.channel();
// 获得和客户端连接的通道
SocketChannel channel = server.accept();
// 设置成非阻塞
channel.configureBlocking(false);
// 在这里可以给客户端发送信息哦
channel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息")
.getBytes("utf-8")));
// 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
channel.register(this.selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {// 获得了可读的事件
read(key);
}
}
}
}
public void read(SelectionKey key) throws IOException {
// 服务器可读取消息:得到事件发生的Socket通道
SocketChannel channel = (SocketChannel) key.channel();
// 创建读取的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(512);
channel.read(buffer);
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("服务端收到信息:" + msg);
ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes("utf-8"));
channel.write(outBuffer);// 将消息回送给客户端
}
public static void main(String[] args) throws IOException {
NIOServer server = new NIOServer();
server.initServer(8000);
server.listen();
}
}
ServerSocketChannel和Selector初始化过程
在java NIO Server的标准过程中,有两个核心的操作需要深入分析一下,分别是ServerSocketChannel.open() 和 Selector.open()两个过程,这里针对这两个对象的初始化流程进行下细致的分解。
// ServerSocketChannel的初始化过程
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// Selector的初始化过程
selector = Selector.open();
通用逻辑抽取
- ServerSocketChannel.open()=SelectorProvider.provider().openServerSocketChannel()
- Selector.open()=SelectorProvider.provider().openSelector()
两者有共同点在于都调用了SelectorProvider.provider()方法,所以先把相同部分进行分析。
public abstract class ServerSocketChannel extends AbstractSelectableChannel
implements NetworkChannel
{
protected ServerSocketChannel(SelectorProvider provider) {
super(provider);
}
public static ServerSocketChannel open() throws IOException {
return SelectorProvider.provider().openServerSocketChannel();
}
}
public abstract class Selector implements Closeable {
protected Selector() { }
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
}
SelectorProvider对象创建
- SelectorProvider.provider()方法会在内部创建唯一的SelectorProvider对象,通过锁来保证创建唯一对象。
- SelectorProvider对象通过DefaultSelectorProvider.create()方法进行创建。
- DefaultSelectorProvider.create()方法内部根据实际系统创建不同的对象,以linux环境中EPollSelectorProvider对象为例继续分析。
public abstract class SelectorProvider {
private static final Object lock = new Object();
private static SelectorProvider provider = null;
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
}
public class DefaultSelectorProvider {
public static SelectorProvider create() {
String osname = AccessController.doPrivileged(
new GetPropertyAction("os.name"));
if ("SunOS".equals(osname)) {
return new sun.nio.ch.DevPollSelectorProvider();
}
// use EPollSelectorProvider for Linux kernels >= 2.6
if ("Linux".equals(osname)) {
String osversion = AccessController.doPrivileged(
new GetPropertyAction("os.version"));
String[] vers = osversion.split("\\.", 0);
if (vers.length >= 2) {
try {
int major = Integer.parseInt(vers[0]);
int minor = Integer.parseInt(vers[1]);
if (major > 2 || (major == 2 && minor >= 6)) {
return new sun.nio.ch.EPollSelectorProvider();
}
} catch (NumberFormatException x) {
// format not recognized
}
}
}
return new sun.nio.ch.PollSelectorProvider();
}
}
EPollSelectorProvider的操作过程
- SelectorProvider.provider().openServerSocketChannel()调用EPollSelectorProvider的openServerSocketChannel()方法返回EPollSelectorImpl对象。
- SelectorProvider.provider().openSelector()调用EPollSelectorProvider的openSelector()方法返回ServerSocketChannelImpl对象。
继续分析ServerSocketChannelImpl对象和EPollSelectorImpl对象。
public class EPollSelectorProvider extends SelectorProviderImpl
{
public AbstractSelector openSelector() throws IOException {
return new EPollSelectorImpl(this);
}
public Channel inheritedChannel() throws IOException {
return InheritedChannel.getChannel();
}
}
public abstract class SelectorProviderImpl extends SelectorProvider
{
public DatagramChannel openDatagramChannel() throws IOException {
return new DatagramChannelImpl(this);
}
public DatagramChannel openDatagramChannel(ProtocolFamily family) throws IOException {
return new DatagramChannelImpl(this, family);
}
public Pipe openPipe() throws IOException {
return new PipeImpl(this);
}
public abstract AbstractSelector openSelector() throws IOException;
public ServerSocketChannel openServerSocketChannel() throws IOException {
return new ServerSocketChannelImpl(this);
}
public SocketChannel openSocketChannel() throws IOException {
return new SocketChannelImpl(this);
}
}
EPollSelectorImpl对象
- EPollSelectorImpl构造函数创建内部通信的socket对IOUtil.makePipe(false)。
- EPollSelectorImpl的doSelect方法负责返回事件到来的fds。
- EPollSelectorImpl的fdToKey的map保存fd和SelectionKey的映射。
class EPollSelectorImpl extends SelectorImpl
{
// File descriptors used for interrupt
protected int fd0;
protected int fd1;
// The poll object
EPollArrayWrapper pollWrapper;
// Maps from file descriptors to keys
private Map<Integer,SelectionKeyImpl> fdToKey;
// True if this Selector has been closed
private volatile boolean closed = false;
// Lock for interrupt triggering and clearing
private Object interruptLock = new Object();
private boolean interruptTriggered = false;
EPollSelectorImpl(SelectorProvider sp) {
super(sp);
long pipeFds = IOUtil.makePipe(false);
fd0 = (int) (pipeFds >>> 32);
fd1 = (int) pipeFds;
pollWrapper = new EPollArrayWrapper();
pollWrapper.initInterrupt(fd0, fd1);
fdToKey = new HashMap<Integer,SelectionKeyImpl>();
}
protected int doSelect(long timeout) throws IOException
{
if (closed)
throw new ClosedSelectorException();
processDeregisterQueue();
try {
begin();
// 等待事件到来,收集事件到来的socket的fd并用来处理
pollWrapper.poll(timeout);
} finally {
end();
}
processDeregisterQueue();
// 更新需要写入的keys
int numKeysUpdated = updateSelectedKeys();
if (pollWrapper.interrupted()) {
// Clear the wakeup pipe
pollWrapper.putEventOps(pollWrapper.interruptedIndex(), 0);
synchronized (interruptLock) {
pollWrapper.clearInterrupted();
IOUtil.drain(fd0);
interruptTriggered = false;
}
}
return numKeysUpdated;
}
private int updateSelectedKeys() {
int entries = pollWrapper.updated;
int numKeysUpdated = 0;
for (int i=0; i<entries; i++) {
int nextFD = pollWrapper.getDescriptor(i);
SelectionKeyImpl ski = fdToKey.get(Integer.valueOf(nextFD));
// ski is null in the case of an interrupt
if (ski != null) {
int rOps = pollWrapper.getEventOps(i);
if (selectedKeys.contains(ski)) {
if (ski.channel.translateAndSetReadyOps(rOps, ski)) {
numKeysUpdated++;
}
} else {
ski.channel.translateAndSetReadyOps(rOps, ski);
if ((ski.nioReadyOps() & ski.nioInterestOps()) != 0) {
// selectedKeys保存ski也就是事件到的socket连接
// ski的对象数据结构需要好好研究一下
selectedKeys.add(ski);
numKeysUpdated++;
}
}
}
}
return numKeysUpdated;
}
}
ServerSocketChannelImpl对象
- ServerSocketChannelImpl extends ServerSocketChannel
- ServerSocketChannel extends AbstractSelectableChannel
- ServerSocketChannelImpl对象提供bind()&accept()方法
- ServerSocketChannelImpl的accept方法内部创建新连接的SocketChannelImpl对象返回
class ServerSocketChannelImpl extends ServerSocketChannel implements SelChImpl
{
private final Object stateLock = new Object();
private SocketAddress localAddress;
ServerSocket socket;
ServerSocketChannelImpl(SelectorProvider sp) throws IOException {
super(sp);
this.fd = Net.serverSocket(true);
this.fdVal = IOUtil.fdVal(fd);
this.state = ST_INUSE;
}
ServerSocketChannelImpl(SelectorProvider sp,
FileDescriptor fd,
boolean bound) throws IOException
{
super(sp);
this.fd = fd;
this.fdVal = IOUtil.fdVal(fd);
this.state = ST_INUSE;
if (bound)
localAddress = Net.localAddress(fd);
}
@Override
public ServerSocketChannel bind(SocketAddress local, int backlog) throws IOException {
// 省略相关代码
}
public SocketChannel accept() throws IOException {
// 省略相关代码
}
}
public abstract class AbstractSelectableChannel extends SelectableChannel
{
protected AbstractSelectableChannel(SelectorProvider provider) {
this.provider = provider;
}
public final SelectionKey register(Selector sel, int ops,
Object att)
throws ClosedChannelException
{
synchronized (regLock) {
if (!isOpen())
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
if (blocking)
throw new IllegalBlockingModeException();
SelectionKey k = findKey(sel);
if (k != null) {
k.interestOps(ops);
k.attach(att);
}
if (k == null) {
// New registration
synchronized (keyLock) {
if (!isOpen())
throw new ClosedChannelException();
k = ((AbstractSelector)sel).register(this, ops, att);
addKey(k);
}
}
return k;
}
}
}
select过程
select执行过程
- 执行selector.select()操作时实际是调用了子类实现的doSelect()方法。
- 进一步跟进子类的doSelect()方法。
abstract class SelectorImpl extends AbstractSelector
{
// 保存事件到来的keys
protected Set<SelectionKey> selectedKeys;
protected HashSet<SelectionKey> keys;
private Set<SelectionKey> publicKeys; // Immutable
private Set<SelectionKey> publicSelectedKeys; // Removal allowed, but not addition
protected abstract int doSelect(long timeout) throws IOException;
private int lockAndDoSelect(long timeout) throws IOException {
synchronized (this) {
if (!isOpen())
throw new ClosedSelectorException();
synchronized (publicKeys) {
synchronized (publicSelectedKeys) {
return doSelect(timeout);
}
}
}
}
public int select(long timeout) throws IOException
{
if (timeout < 0)
throw new IllegalArgumentException("Negative timeout");
return lockAndDoSelect((timeout == 0) ? -1 : timeout);
}
public int select() throws IOException {
return select(0);
}
public Set<SelectionKey> selectedKeys() {
if (!isOpen() && !Util.atBugLevel("1.4"))
throw new ClosedSelectorException();
return publicSelectedKeys;
}
}
- pollWrapper.poll(timeout)以超时等待的形式等待epoll的消息通知。
- 通过updateSelectedKeys方法收集有事件到达的fds保存到selectedKeys。
class EPollSelectorImpl extends SelectorImpl
{
protected int doSelect(long timeout) throws IOException
{
if (closed)
throw new ClosedSelectorException();
processDeregisterQueue();
try {
begin();
// 等待事件到来,收集事件到来的socket的fd并用来处理
pollWrapper.poll(timeout);
} finally {
end();
}
processDeregisterQueue();
// 更新需要写入的keys
int numKeysUpdated = updateSelectedKeys();
if (pollWrapper.interrupted()) {
// Clear the wakeup pipe
pollWrapper.putEventOps(pollWrapper.interruptedIndex(), 0);
synchronized (interruptLock) {
pollWrapper.clearInterrupted();
IOUtil.drain(fd0);
interruptTriggered = false;
}
}
return numKeysUpdated;
}
private int updateSelectedKeys() {
int entries = pollWrapper.updated;
int numKeysUpdated = 0;
for (int i=0; i<entries; i++) {
int nextFD = pollWrapper.getDescriptor(i);
SelectionKeyImpl ski = fdToKey.get(Integer.valueOf(nextFD));
// ski is null in the case of an interrupt
if (ski != null) {
int rOps = pollWrapper.getEventOps(i);
if (selectedKeys.contains(ski)) {
if (ski.channel.translateAndSetReadyOps(rOps, ski)) {
numKeysUpdated++;
}
} else {
ski.channel.translateAndSetReadyOps(rOps, ski);
if ((ski.nioReadyOps() & ski.nioInterestOps()) != 0) {
// selectedKeys保存ski也就是事件到的socket连接
// ski的对象数据结构需要好好研究一下
selectedKeys.add(ski);
numKeysUpdated++;
}
}
}
}
return numKeysUpdated;
}
}
accept过程
- accept的过程很简单就是accept新socket并创建SocketChannelImpl返回即可。
- SocketChannelImpl对象后面需要注册到Selector当中所以需要进一步分析。
public SocketChannel accept() throws IOException {
// 省略相关代码
try {
// 省略相关代码
// 新accept的socket放在newfd当中
n = accept0(this.fd, newfd, isaa);
}
}
IOUtil.configureBlocking(newfd, true);
InetSocketAddress isa = isaa[0];
// 通过SocketChannelImpl包装newfd对象
sc = new SocketChannelImpl(provider(), newfd, isa);
// 省略相关代码
return sc;
}
}
SocketChannelImpl对象
- SocketChannelImpl可以理解为普通Socket的封装,包括read/write等方法
- SocketChannelImpl extends SocketChannel extends AbstractSelectableChannel
- AbstractSelectableChannel提供register到selector对象的方法
class SocketChannelImpl extends SocketChannel implements SelChImpl
{
SocketChannelImpl(SelectorProvider sp) throws IOException {
super(sp);
this.fd = Net.socket(true);
this.fdVal = IOUtil.fdVal(fd);
this.state = ST_UNCONNECTED;
}
SocketChannelImpl(SelectorProvider sp,
FileDescriptor fd,
boolean bound)
throws IOException
{
super(sp);
this.fd = fd;
this.fdVal = IOUtil.fdVal(fd);
this.state = ST_UNCONNECTED;
if (bound)
this.localAddress = Net.localAddress(fd);
}
SocketChannelImpl(SelectorProvider sp,
FileDescriptor fd, InetSocketAddress remote)
throws IOException
{
super(sp);
this.fd = fd;
this.fdVal = IOUtil.fdVal(fd);
this.state = ST_CONNECTED;
this.localAddress = Net.localAddress(fd);
this.remoteAddress = remote;
}
public long read(ByteBuffer[] dsts, int offset, int length)
throws IOException
{
// 读数据的逻辑
}
public int write(ByteBuffer buf) throws IOException {
// 写数据的逻辑
}
}
register过程
- register过程并没有调用epollCtl方法添加fd到selector当中
- register过程真正是保存fd到待绑定的列表当中
- 在SelectorImpl中执行pollWrapper.poll(timeout)方法先把fd列表执行epollCtl添加selector当中,在通过epollWait获取事件到来
public abstract class AbstractSelectableChannel extends SelectableChannel
{
public final SelectionKey register(Selector sel, int ops,
Object att)
throws ClosedChannelException
{
synchronized (regLock) {
if (!isOpen())
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
if (blocking)
throw new IllegalBlockingModeException();
SelectionKey k = findKey(sel);
if (k != null) {
k.interestOps(ops);
k.attach(att);
}
if (k == null) {
// New registration
synchronized (keyLock) {
if (!isOpen())
throw new ClosedChannelException();
k = ((AbstractSelector)sel).register(this, ops, att);
addKey(k);
}
}
return k;
}
}
}
abstract class SelectorImpl extends AbstractSelector
{
protected final SelectionKey register(AbstractSelectableChannel ch,
int ops,
Object attachment)
{
if (!(ch instanceof SelChImpl))
throw new IllegalSelectorException();
SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);
k.attach(attachment);
synchronized (publicKeys) {
implRegister(k);
}
k.interestOps(ops);
return k;
}
}
abstract class AbstractPollSelectorImpl extends SelectorImpl
{
protected void implRegister(SelectionKeyImpl ski) {
synchronized (closeLock) {
if (closed)
throw new ClosedSelectorException();
// Check to see if the array is large enough
if (channelArray.length == totalChannels) {
// Make a larger array
int newSize = pollWrapper.totalChannels * 2;
SelectionKeyImpl temp[] = new SelectionKeyImpl[newSize];
// Copy over
for (int i=channelOffset; i<totalChannels; i++)
temp[i] = channelArray[i];
channelArray = temp;
// Grow the NativeObject poll array
pollWrapper.grow(newSize);
}
channelArray[totalChannels] = ski;
ski.setIndex(totalChannels);
// 核心的将channel添加到pollWrapper当中
pollWrapper.addEntry(ski.channel);
totalChannels++;
keys.add(ski);
}
}
}
class EPollArrayWrapper {
int poll(long timeout) throws IOException {
updateRegistrations();
updated = epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd);
for (int i=0; i<updated; i++) {
if (getDescriptor(i) == incomingInterruptFD) {
interruptedIndex = i;
interrupted = true;
break;
}
}
return updated;
}
void updateRegistrations() {
synchronized (updateList) {
Updator u = null;
while ((u = updateList.poll()) != null) {
SelChImpl ch = u.channel;
if (!ch.isOpen())
continue;
// if the events are 0 then file descriptor is put into "idle
// set" to prevent it being polled
if (u.events == 0) {
boolean added = idleSet.add(u.channel);
// if added to idle set then remove from epoll if registered
if (added && (u.opcode == EPOLL_CTL_MOD))
epollCtl(epfd, EPOLL_CTL_DEL, ch.getFDVal(), 0);
} else {
// events are specified. If file descriptor was in idle set
// it must be re-registered (by converting opcode to ADD)
boolean idle = false;
if (!idleSet.isEmpty())
idle = idleSet.remove(u.channel);
int opcode = (idle) ? EPOLL_CTL_ADD : u.opcode;
epollCtl(epfd, opcode, ch.getFDVal(), u.events);
}
}
}
}
}