http://www.open-open.com/lib/view/open1343002247880.html
服务器端的代码如下,相关说明就带在注释里了:
// 创建一个选择器,可用close()关闭,isOpen()表示是否处于打开状态,他不隶属于当前线程
02 Selector selector = Selector.open();
03 // 创建ServerSocketChannel,并把它绑定到指定端口上
04 ServerSocketChannel server = ServerSocketChannel.open();
05 server.bind(new InetSocketAddress("127.0.0.1", 7777));
06 // 设置为非阻塞模式, 这个非常重要
07 server.configureBlocking(false);
08 // 在选择器里面注册关注这个服务器套接字通道的accept事件
09 // ServerSocketChannel只有OP_ACCEPT可用,OP_CONNECT,OP_READ,OP_WRITE用于SocketChannel
10 server.register(selector, SelectionKey.OP_ACCEPT);
11 while (true) {
12 // 测试等待事件发生,分为直接返回的selectNow()和阻塞等待的select(),另外也可加一个参数表示阻塞超时
13 // 停止阻塞的方法有两种: 中断线程和selector.wakeup(),有事件发生时,会自动的wakeup()
14 // 方法返回为select出的事件数(参见后面的注释有说明这个值为什么可能为0).
15 // 另外务必注意一个问题是,当selector被select()阻塞时,其他的线程调用同一个selector的register也会被阻塞到select返回为止
16 // select操作会把发生关注事件的Key加入到selectionKeys中(只管加不管减)
17 if (selector.select() == 0) { //
18 continue;
19 }
20
21 // 获取发生了关注时间的Key集合,每个SelectionKey对应了注册的一个通道
22 Set keys = selector.selectedKeys();
23 // 多说一句selector.keys()返回所有的SelectionKey(包括没有发生事件的)
24 for (SelectionKey key : keys) {
25 // OP_ACCEPT 这个只有ServerSocketChannel才有可能触发
26 if (key.isAcceptable()) {
27 // 得到与客户端的套接字通道
28 SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();
29 // 同样设置为非阻塞模式
30 channel.configureBlocking(false);
31 // 同样将于客户端的通道在selector上注册,OP_READ对应可读事件(对方有写入数据),可以通过key获取关联的选择器
32 channel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(1024));
33 }
34 // OP_READ 有数据可读
35 if (key.isReadable()) {
36 SocketChannel channel = (SocketChannel) key.channel();
37 // 得到附件,就是上面SocketChannel进行register的时候的第三个参数,可为随意Object
38 ByteBuffer buffer = (ByteBuffer) key.attachment();
39 // 读数据 这里就简单写一下,实际上应该还是循环读取到读不出来为止的
40 channel.read(buffer);
41 // 改变自身关注事件,可以用位或操作|组合时间
42 key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
43 }
44 // OP_WRITE 可写状态 这个状态通常总是触发的,所以只在需要写操作时才进行关注
45 if (key.isWritable()) {
46 // 写数据掠过,可以自建buffer,也可用附件对象(看情况),注意buffer写入后需要flip
47 // ......
48 // 写完就吧写状态关注去掉,否则会一直触发写事件
49 key.interestOps(SelectionKey.OP_READ);
50 }
52 // 由于select操作只管对selectedKeys进行添加,所以key处理后我们需要从里面把key去掉
53 keys.remove(key);
54 }
55 }
还有一个状态上面没有使用,OP_CONNECT这个主要是用于客户端,对应的key的方法是isConnectable()表示已经创建好了连接。
非阻塞实现的客户端如下:
01 Selector selector = Selector.open();
02 // 创建一个套接字通道,注意这里必须使用无参形式
03 SocketChannel channel = SocketChannel.open();
04 // 设置为非阻塞模式,这个方法必须在实际连接之前调用(所以open的时候不能提供服务器地址,否则会自动连接)
05 channel.configureBlocking(false);
06 // 连接服务器,由于是非阻塞模式,这个方法会发起连接请求,并直接返回false(阻塞模式是一直等到链接成功并返回是否成功)
07 channel.connect(new InetSocketAddress("127.0.0.1", 7777));
08 // 注册关联链接状态
09 channel.register(selector, SelectionKey.OP_CONNECT);
10 while (true) {
11 // 前略 和服务器端的类似
12 // ...
13 // 获取发生了关注时间的Key集合,每个SelectionKey对应了注册的一个通道
14 Set keys = selector.selectedKeys();
15 for (SelectionKey key : keys) {
16 // OP_CONNECT 两种情况,链接成功或失败这个方法都会返回true
17 if (key.isConnectable()) {
18 // 由于非阻塞模式,connect只管发起连接请求,finishConnect()方法会阻塞到链接结束并返回是否成功
19 // 另外还有一个isConnectionPending()返回的是是否处于正在连接状态(还在三次握手中)
20 if (channel.finishConnect()) {
21 // 链接成功了可以做一些自己的处理,略
22 // ...
23 // 处理完后必须吧OP_CONNECT关注去掉,改为关注OP_READ
24 key.interestOps(SelectionKey.OP_READ);
25 }
26 }
27 // 后略 和服务器端的类似
28 // ...
29 }
30 }
虽然例子是这样的,不过服务器和客户端可以自己单方面选择是否采用非阻塞模式,用阻塞模式的客户端连接非阻塞模式的服务器端是OK的。