非阻塞通信
1.前言:对于用ServerSocket和Socket编写的Server和Client程序->运行过程常会阻塞->如ServerSocket#accept->如果没有client连接,调用线程就会一直阻塞->直到有client连接才返回->Socket#read->如果输入流没有数据->调用线程会等到读入了足够的数据才从read返回->
->如果采用阻塞通信->Server如果需要与多Client通信->必须分配多个工作线程->线程池或者per client per thread->不过每个工作线程都有可能长时间处于阻塞状态->
->JDK1.4->引入了非阻塞通信->即Server接收Client连接,Client建立与Server的连接以及Server和Client收发数据的操作都可按照非阻塞的方式->Server只需创建一个线程->可完成与多Client工作->
->nio包实现->ServerSocketChannel,SocketChannel,Selector,SelectionKey,ByteBuffer等->
2.线程阻塞->放弃CPU,暂停运行->只有等待导致阻塞的原因消除->或者被其他线程中断->则退出阻塞状态->并抛出InterruptedException.
线程阻塞原因:
1.Thread.sleep(long n)->线程放弃CPU->睡眠n毫秒,然后恢复运行->
2.线程执行一段同步代码->由于无法获得同步锁->阻塞->直到获得同步锁->恢复运行->
3.线程执行了一个对象的wait->进入阻塞->等待其他线程执行了该对象的notify()或notifyAll唤醒
4.线程执行io操作或远程通信->会因为等待相关的资源而进入阻塞状态->如System.in.read()->
远程通信,Client可能进入阻塞状态->
1.Socket#connect->阻塞->直到连接成功
2.Socket#read->没有足够的数据->阻塞->读到了足够的数据/输入流末尾/异常->才会返回或者异常中断-> read()->输入流中只需要一个字节就足够->read(byte[] buff)->只要输入流中的字节数目与buff数组的长度相同即足够->readLine->只要输入流中有一行字符串就足够(BufferedReader)
3.Socket#write->阻塞->直到输出了所有的数据或者出现异常->才返回或者异常中断
4.Socket#setSoLinger->设置关闭Socket延迟时间->Socket#close->进入阻塞->直到底层Socket发送完所有剩余数据或者到了SoLinger的超时时间->返回
Server也可能进入阻塞状态->
1.ServerSocket#accept->直到接收到了Client连接才返回
2.#read->如果输入流没有足够的数据->阻塞
3.#write->阻塞->直到输出了所有数据或出现异常->
->无论是Client还是Server->通过Socket输入输出流读写数据->都可能进入阻塞状态->阻塞IO->如果执行输入输出操作时->不发生阻塞->非阻塞IO.
3.多线程处理阻塞通信的局限:
1.主线程负责接收客户连接->每当接收到连接->交给线程池的空闲工作线程
2.主线程在接收客户连接以及工作线程执行io操作时,都有可能进入阻塞状态
3.局限:
1.Java虚拟机为每个线程分配独立的堆栈空间->工作线程数目越多->系统开销越大->增加了Java虚拟机调度线程的负担->增加了线程之间同步的复杂性->提高线程死锁可能性.
2.工作线程的许多操作浪费在阻塞io上->Java虚拟机需要频繁转让CPU的使用权->使进入阻塞状态的线程放弃CPU->再把CPU让给可运行状态的线程->
4.->工作线程不是越多越好->保持适量的工作线程->会提高Server的并发性能->当超过某个极限时->超过了系统的负载->会降低并发性能->使许多Client无法快速得到Server响应.
4.非阻塞通信的基本思想:
假如同时做两件事情:洗衣服和做饭
洗衣服步骤
1.打开洗衣机->放入衣服->放水->放洗衣液->开始按钮
2.等待洗衣完毕(阻塞)
3.取出衣服->晾衣服
做法步骤
1.电饭锅->放水->放米->开始按钮
2.等待饭熟(阻塞)
4.关闭->
->为了完成两件事->可以请两个人同时做其中的一件事->相当于采用多线程来同时完成任务->
->还可以让一个人同时完成两件事情->这个人善于利用一件事的空闲时间去做另一件事情->一刻也不闲着->
打开洗衣机->放入衣服->开始
打开电饭锅->放米->开始
while(一直等待,直到有衣服洗完或者饭煮熟的事件发生)
{
if(衣服洗完)
{
晾衣服->关洗衣机
}
if(饭熟)
{
关电饭锅
}
}
这个人不断监控洗衣机和电饭锅的状态->如果发生了某事件->则去处理->处理完毕后继续监控->直到所有任务都完成->
->运用到服务器程序->
while(一直等待,直到有接收连接就绪事件,读就绪事件或者写就绪事件)// 阻塞
{
if(有客户连接)
{
接收客户连接 // 非阻塞
}
if(某Socket的输入流有可读数据)
{
从输入流读数据 // 非阻塞
}
if(某Socket的输入流可以写数据)
{
向输出流写数据 // 非阻塞
}
}
->采用轮询->某一种操作就绪->就执行该操作->否则就查看是否还有其他就绪的操作可以执行->线程不会因为某一个操作还没有就绪就进入阻塞状态->一直傻等操作就绪->
->为了使轮询操作顺利就行->接收客户的连接/从输入流读数据/向输出流写数据->以非阻塞方式运行->非阻塞->当线程执行这些方法->如果操作为就绪,则立即返回->而不会一直等到操作就绪->
->线程接收Client连接时,如果没有客户连接->立即返回
->线程从输入流读数据时,如果输入流没有数据就立即返回或者如果输入流还没有足够的数据,就读取现有数据->返回
->while条件的操作是以阻塞进行->如果未发生任何事件则进入阻塞状态->至少有一个事件发生时->执行循环体内操作->通常会包含在特定条件下退出循环的操作->
landon理解:1.Java的NIO机制其实是采用了事件机制(Reactor/Observer)->
2.Selector轮询channels
3.NIO机制围绕Selecor+Channel
注意:selector事件到来时(只是判断是否可读/可写)->具体的读写还是由阻塞和非阻塞决定->如阻塞模式下,如果输入流不足r字节则进入阻塞状态,而非阻塞模式下则奉行能读到多少就读到多少的原则->立即返回->
->同理写也是一样->selector只是通知可写->但是能写多少数据也是有阻塞和非阻塞决定->如阻塞模式->如果底层网络的输出缓冲区不能容纳r个字节则会进入阻塞状态->而非阻塞模式下->奉行能输出多少就输出多少的原则->立即返回
->而对于accept->阻塞模式->没有client连接时,线程会一直阻塞下去->而非阻塞时->没有客户端连接->方法立刻返回null->
(1.个人理解selector只是通知acceptable->即有连接请求加到了连接队列->但是具体接收连接是由accept决定->如果是阻塞模式的话,假如此时连接队列因为一些原因变空,则accept的时候就会阻塞->)
总结:为了使轮询操作顺利就行->必须采用非阻塞方式->
5.java.nio主要类
1.ServerSocketChannel->ServerSocket替代类->支持阻塞通信与非阻塞通信->
2.SocketChannel->Socket替代类->支持阻塞通信与非阻塞通信->
3.Selector->为ServerSocketChannel监控接收连接就绪事件->为SocketChannel监控连接就绪/读就绪/写就绪事件->
4.SelectionKey->表示ServerSocketChannel和SocketChannel向Selctor注册事件的句柄->当一个SelectionKey对象位于Selecor对象的selected-keys集合中->表述与这个SelectionKey对象相关的事件就发生了->
->ServerSocketChannel和SocketChannel都是SelectableChannel的子类->SelectableChannel及其子类都可委托Selector来监控它们可能发生的事件->委托过程->注册事件过程->
->SelectionKey key = serverSocketChannel.register(selector,SelectioinKey.OP_ACCEPT)->serverSocketChannel向Selector接收连接就绪事件->
6.缓冲区Buffer
数据输入和输出往往是比较耗时的操作. 缓冲区从两个方面提高 I/O 操作的效率:
1. 减少实际的物理读写次数;
2.缓存区在创建时被分配内存, 这块内存区域一直被重用, 这可以减少动态分配和回收内存区域的次数.
-> 旧I/O 类库(对应 java.nio包) 中的 BufferedInputStream, BufferedOutputStream, BufferedReader 和 BufferedWriter 在其实现中都运用了缓冲区. java.nio 包公开了 Buffer API, 使得Java 程序可以直接控制和运用缓冲区.
a.public abstract class Buffer
b.public abstract class ByteBuffer extends Buffer->
allocate->HeapByteBuffer
allocateDirect->DirectByteBuffer
c.public abstract class MappedByteBuffer extends ByteBuffer
d.DirectByteBuffer extends MappedByteBuffer implements DirectBuffer
3.缓冲区属性
1.容量(capacity): 表示该缓冲区可以保存多少数据.
2.极限(limit): 表示缓冲区的当前终点, 不能对缓冲区中超过极限的区域进行读写操作. 极限是可以修改的, 这有利于缓冲区的重用. 例如, 假定容量100 的缓冲区已经填满了数据, 接着程序在重用缓冲区时, 仅仅将 10 个新的数据写入缓冲区中从位置0 到10 的区域, 这时可以将极限设为 10, 这样就不能读取先前的数据了. 极限是一个非负整数, 不应该大于容量.
3.位置(position): 表示缓冲区中下一个读写单元的位置, 每次读写缓冲区的数据时, 都会改变该值, 为下一次读写数据作准备. 位置是一个非负整数, 不应该大于极限.
->容量 ≥ 极限 ≥ 位置 ≥ 0
注-landon:其实还有一个属性mark.->临时存放的位置下标.
->#mark:mark设为position #reset:position设置mark.
->正常:mark<= positon -> 如果position < mark,mark会被抛弃( #position:if (mark > position) mark = -1)
4.缓冲区方法:
1.clear(): 把极限设为容量, 再把位置设为 0;
position = 0;limit = capacity;mark = -1;
2.flip(): 把极限设为位置, 再把位置设为 0;
limit = position;position = 0;mark = -1;
3.rewind(): 不改变极限, 把位置设为 0.
position = 0;mark = -1;
注-landon:其他方法:
1.public final Buffer position(int newPosition)
2.public final Buffer limit(int newLimit)
3. public final int remaining()
->return limit - position
4.compact:
->从当前位置position 到极限limit 的内容复制到 0 到 limit-position 的区域->
->1.System.arraycopy(this.hb, ix(position()), this.hb, ix(0), remaining())
2. position(remaining())
3. limit(capacity())
5.Buffer 类是一个抽象类,->不能被实例化.->共有 8 个具体的缓冲区类,->其中最基本的缓冲区是 ByteBuffer, 它存放的数据单元是字节(byte[]). ByteBuffer 类并没有提供公开的构造方法, 但是提供了两个获得 ByteBuffer 实例的静态工厂方法->
1.allocate(int capacity): 返回一个 ByteBuffer 对象, 参数capacity 指定缓冲区的容量.
->HeapByteBuffer
2.directAllocate(int capacity):返回一个 ByteBuffer 对象, 参数capacity 指定缓冲区的容量.->该方法返回的缓冲区称为直接缓冲区, 它与当前操作系统能够更好地耦合,->因此能进一步提高 I/O 操作的速度. 但是直接分配缓冲区的系统开销很大, 因此只有在缓冲区较大并且长期存在, 或者需要经常重用时, 才使用这种缓冲区.
->DirectByteBuffer
3.除 boolean 类型以外, 每种基本类型都有对应的缓冲区类,->包括 CharBuffer(char[]), DoubleBuffer(double[]), FloatBuffer(float[]), IntBuffer(int[]), LongBuffer(long[])和 ShortBuffer(short[]).
4.MappedByteBuffer,->它是 ByteBuffer 的子类.->MappedByteBuffer 能够把缓冲区和文件的某个区域直接映射
->public abstract class MappedByteBuffer extends ByteBuffer
->A direct byte buffer whose content is a memory-mapped region of a file
5. 缓冲区类都提供了读写缓冲区的方法:
get(): 相对读. 从缓冲区的当前位置读取一个单元的数据, 读完后把位置加 1;
get(int index): 绝对读. 从参数 index 指定的位置读取一个单元的数据;
put(): 相对写. 向缓冲区的当前位置写入一个单元的数据, 写完后把位置加 1;
put(int index): 绝对写. 向参数 index 指定的位置写入一个单元的数据.
7.字符编码Charset
1.java.nio.Charset 类的每个实例代表特定的字符编码类型->
2.字节序列->解码->字符串
字符串->编码->字节序列
3.Charset 类提供了编码与解码的方法:
1.ByteBuffer encode(String str)->对参数 Str 指定的字符串进行编码, 把得到的字节序列存放在一个 ByteBuffer 对象中, 并将其返回;
2.ByteBuffer encode(CharBuffer cb)->对参数 cb 指定的字符缓冲区中的字符进行编码,把得到的字节序列存放在一个 ByteBuffer 对象中, 并将其返回;
3.CharBuffer decode(ByteBuffer bb)->把参数 bb 指定的 ByteBuffer 中的字节序列进行解码, 把得到的字符序列存放在一个 CharBuffer 对象中, 并将其返回
4.Charset 类的静态 forName(String encode) 方法返回一个 Charset 对象->它代表参数 encode 指定的编码类型->
5.Charset 类还有一个静态方法 defaultCharset(),->它返回代表本地平台的默认字符编码的 Charset 对象->
8.通道Channel
1.通道 Channel 用来连接缓冲区与数据源或数据汇(数据目的地)->数据源的数据经过管道到达缓冲区, 缓冲区的数据经过通道到达数据汇->
2.数据源->通道->内存(缓冲区)->通道->数据汇
3.Channel类层次结构:
1.public interface Channel extends Closeable
#isOpen() #close()
2.public interface ReadableByteChannel extends Channel
#read(ByteBuffer dst)
3.public interface WritableByteChannel extends Channel
#write(ByteBuffer src)
4.public interface ScatteringByteChannel extends ReadableByteChannel
#read(ByteBuffer[] dsts) #read(ByteBuffer[] dsts, int offset, int length)
5.public interface GatheringByteChannel extends WritableByteChannel
#write(ByteBuffer[] srcs) #write(ByteBuffer[] srcs, int offset, int length)
6.public interface ByteChannel extends ReadableByteChannel, WritableByteChannel
7.public abstract class SelectableChannel extends AbstractInterruptibleChannel implements Channel
#configureBlocking(boolean block) # register(Selector sel, int ops, Object att)
8.public abstract class AbstractSelectableChannel extends SelectableChannel
9.public abstract class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel
10.public abstract class SocketChannel extends AbstractSelectableChannel
implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel
11.public abstract class FileChannel extends AbstractInterruptibleChannel
implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel
4.ByteBuffer<- ReadableByteChannel <-数据源
ByteBuffer-> WritableByteChannel ->数据汇
5.ScatteringByteChannel->分散地读取数据->单个读取操作能填充多个缓冲区
GatheringByteChannel ->集中地写入数据.->集中写入数据是指单个写操作能把多个缓冲区的数据写入数据汇
->分散读取和集中写数据能够进一步提高输入和输出操作的速度
6.FileChannel代表一个与文件相连的通道->FileInputStream, FileOutputStream 和 RandomAccessFile 类中提供了 getChannel() 方法->返回FileChannel->
7.SelectableChannel 也是一种通道->它不仅支持阻塞的 I/O 操作, 还支持非阻塞的 I/O 操作.
9.SelectableChannel
1.SelectableChannel 类是一种支持阻塞 I/O 和非阻塞 I/O 的通道->在非阻塞模式下->读写数据不会阻塞->并且SelectableChannel 可以向 Selector 注册读就绪和写就绪等事件.->Selector 负责监控这些事件, 等到事件发生时, 比如发生了读就绪事件, SelectableChannel 就可以执行读操作了
2.public abstract SelectableChannel configureBlocking(boolean block) throws IOException
->参数block 为false, 表示把 SelectableChannel 设为非阻塞模式->
->默认情况下-> SelectableChannel 采用阻塞模式
3.public abstract boolean isBlocking()
->返回true表示阻塞模式
4.public abstract SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException
public final SelectionKey register(Selector sel, int ops) throws ClosedChannelException
->向selector注册事件
-> SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE)->SocketChannel向 Selector 注册读就绪和写就绪事件
->返回一个 SelectionKey 对象, SelectionKey 用来跟踪被注册的事件
->register() 方法还有一个Object 类型的参数 attachment,->它用于为 SelectionKey 关联一个附件, 当被注册事件发生后, 需要处理该事件时, 可以从 SelectionKey 中获得这个附件, 该附件可用来包含与处理这个事件相关的信息->
->如SelectionKey key = socketChannel.register(selector, SelectioinKey.OP_READ | SelectionKey.OP_WRITE, new EventHandler());
->等价于:SelectionKey key = socketChannel.register(selector, SelectioinKey.OP_READ | SelectionKey.OP_WRITE);
key.attach(new EventHandler());
10.ServerSocketChannel
1.从 SeletableChannel 中继承了 configureBlocking() 和 register()方法
2.ServerSocketChannel 是 ServerSocket 的替换类
3.public static ServerSocketChannel open() throws IOException
->返回一个 ServerSocketChannel 对象, 这个对象没有与任何本地端口绑定, 并且处于阻塞模式->
->serverSocketChannel.socket().bind(port)->绑定到一个本地端口
4. public abstract SocketChannel accept() throws IOException
->如果 ServerSocketChannel 处于非阻塞状态, 当没有客户连接时, 该方法立即返回 null
->如果ServerSocketChannel 处于阻塞状态, 当没有客户连接时, 它会一直阻塞下去, 直到有客户连接就绪, 或者出现了IOException.
->serverSocketChannel.configureBlocking(false)->修改为非阻塞模式运行
5. public final int validOps()
->return SelectionKey.OP_ACCEPT
6.public abstract ServerSocket socket()
->返回关联的ServerSocket对象->每个 ServerSocketChannel 对象都与一个 ServerSocket 对象关联->
11.SocketChannel
1.可看作是 Socket 的替代类
2.不仅从 SelectableChannel 父类中继承了 configureBlocking() 和 register() 方法, 并且实现了 ByteChannel 接口
3.public static SocketChannel open() throws IOException
public static SocketChannel open(SocketAddress remote)
->open(SocketAddress remote)会建立建立与远程服务器的连接->
->socketChannel.connect(remote)等价SocketChannel socketChannel = SocketChannel.open(remote)
->socketChannel.configureBlocking(false)->修改为非阻塞模式运行
4. public final int validOps()
->return (SelectionKey.OP_READ | SelectionKey.OP_WRITE| SelectionKey.OP_CONNECT)
5.public abstract Socket socket()
->返回与这个SocketChannel 关联的 Socket 对象. 每个 SocketChannel 对象都与一个 Socket 对象关联
6.public abstract boolean isConnected()
->return true,if, and only if, this channel's network socket is {@link #isOpen open} and connected.
7.public abstract boolean isConnectionPending()
->判断是否正在进行远程连接. 当远程连接操作已经开始, 但是还没有完成时, 则返回true
->return true,if, and only if, a connection operation has been initiated on this channel but not yet completed by invoking the{@link #finishConnect finishConnect} method(发起连接尚未调用调用finishConnect)
8.public abstract boolean connect(SocketAddress remote) throws IOException
->使底层Socket 建立远程连接-> 当SocketChannel 处于非阻塞模式时, 如果立即连接成功, 该方法返回true, 如果不能立即连接成功, 该方法返回false->程序过会儿必须通过调用finishConnect() 方法来完成连接.
->当SocketChannel 处于阻塞模式, 如果立即连接成功, 该方法返回true, 如果不能立即连接成功, 将进入阻塞状态, 直到连接成功, 或者出现 I/O 异常
9.public abstract boolean finishConnect() throws IOException
->试图完成连接远程服务器的操作->在非阻塞模式下, 建立连接从调用SocketChannel 的connect() 方法开始, 到调用 finishConnect() 方法结束. 如果finishConnect() 方法顺利完成连接, 或者在调用次方法之前连接已经建立, 则finishConnect() 方法立即返回true.
->如果连接操作还没有完成, 则立即返回false; 如果连接操作中遇到异常而失败, 则抛出响应的I/O 异常.
->阻塞模式下, 如果连接操作还没有完成, 则会进入阻塞状态, 直到连接完成或者出现I/O 异常
10.public int read(ByteBuffer dst) throws IOException
->从 Channel 中读入若干字节,->把他们存放到参数指定的 ByteBuffer 中. 假定执行read() 方法前, ByteBuffer 的位置为p, 剩余容量为r, r 等于dst.remaining() 方法的返回值. 假定read() 方法实际读入了 n 个字节, 那么 0 ≤ n ≤ r. read() 方法返回后, 参数 dst 引用的ByteBuffer 的位置变为 p+n, 极限保持不变->
->在阻塞模式下, read() 方法会争取读到 r 个字节, 如果输入流中不足 r 个字节, 就进入阻塞状态, 直到读入了 r 个字节, 或者读到了输入流末尾, 或者出现了 I/O 异常.
->非阻塞模式下, read() 方法奉行能读到多少数据就读多少数据的原则. read() 方法读取当前通道中的可读数据, 有可能不足 r 个, 或者为 0 个字节, read() 方法总是立即返回, 而不会等到读取了 r 个字节在返回
-> read() 方法返回的实际上读入的字节数, 有可能为 0. 如果返回 -1, 就表示读到了输入流的末尾
11.public int write(ByteBuffer src) throws IOException
->把参数 src 指定的 ByteBuffer 中的字节写到 Channel 中. 假定执行 write() 方法前, ByteBuffer 的位置为 p, 剩余容量为 r, r 等于 src.remaining() 方法的返回值. 假定 write() 方法实际上向通道中写了 n 个字节, 那么 0 ≤ n ≤ r. write() 方法返回后, 参数 src 引用的 ByteBuffer 的位置变为 p+n, 极限保持不变
->在阻塞模式下, write() 方法会争取输出 r 个字节, 如果底层网络的输出缓冲区不能容纳 r 个字节, 就进入阻塞状态, 直到输出了 r 个字节, 或者出现了 I/O 异常.
->非阻塞模式下, write() 方法奉行能输出多少数据就输出多少数据的原则, 有可能不足 r 个字节, 或者为 0 个字节, write() 方法总是立即返回, 而不会等到输出 r 个字节后再返回
->write() 方法返回实际上输出的字节数, 有可能为 0.
12.Selector
1.只要 ServerSocketChannel 及 SocketChannel 向 Selector 注册了特定的事件, Selector 就会监控这些事件是否发生-> SelectableChannel #register() 方法负责注册事件, 该方法返回一个SelectionKey 对象, 该对象是用于跟踪这些被注册事件的句柄
2. 一个Selector 对象中会包含 3 种类型的 SelectionKey 集合
1. public abstract Set<SelectionKey> keys()
->The key set is not directly modifiable. A key is removed only after it has been cancelled and its channel has been deregistered. Any
attempt to modify the key set will cause an linkUnsupportedOperationException to be thrown
->当前所有向Selector 注册的 SelectionKey 的集合
-> publicKeys = Collections.unmodifiableSet(keys)
2.public abstract Set<SelectionKey> selectedKeys()
->Keys may be removed from, but not directly added to, the selected-key set. Any attempt to add an object to the key set will cause an UnsupportedOperationException} to be thrown.
->相关事件已经被Selector 捕获的SelectionKey 的集合.
->publicSelectedKeys = Util.ungrowableSet(selectedKeys)
3.cancelledKeys
->已经被取消的 SelectionKey 的集合
注-landon:
源码中selectedKeys和keys均是HashSet->但是对外提供的访问接口keys和selectedKeys返回的却是二者的public view->
publicSelectedKeys/publicKeys->返回的view均对set做了限制.
4.第二种和第三种集合都是第一种集合的子集. 对于一个新建的Selector 对象, 它的上述集合都为空
5.执行SelectableChannel # register() 方法时,->该方法新建一个 SelectionKey, 并把它加入到 Selector 的keys集合中
6.关闭了与SelectionKey 对象关联的 Channel 对象->或者调用了 SelectionKey 对象的cancel() 方法, 这个 SelectionKey 对象就会被加入到 cancelled-keys 集合中, 表示这个 SelectionKey 对象已经被取消, 在程序下一次执行 Selector 的 select() 方法时, 被取消的 SelectionKey 对象将从所有的集合(包括 all-keys 集合, selected-keys集合和cancelled-keys 集合)中删除->
7.执行 Selector 的 select() 方法时, 如果与 SelectionKey 相关的事件发生了, 这个SelectionKey 就被加入到 selected-keys 集合中. 程序直接调用 selected-keys 集合的 remove() 方法, 或者调用它的 Iterator 的 remove() 方法, 都可以从 selected-keys 集合中删除一个 SelectionKey 对象
注-landon:因为对外提供的是publicSelectedKeys->ungrowableSet->(只是不可add/addAll,否则会报出UnsupportedOperationException)
8.不允许直接通过集合接口的 remove() 方法删除 all-keys 集合中的 SelectionKey 对象
注-landon:因为对外提供的是publicKeys->unmodifiableSet->
9.public static Selector open() throws IOException
->Selector 的静态工厂方法, 创建一个 Selector 对象
10.public abstract boolean isOpen()
->true,if, and only if, this selector is open
->判断Selector 是否处于打开状态.->Selector 对象创建后就处于打开状态, 当调用那个了 Selector 对象的 close()
11. public abstract int selectNow() throws IOException
->返回相关事件已经发生的 SelectionKey 对象的数目. 该方法采用非阻塞的工作方式, 返回当前相关时间已经发生的 SelectionKey 对象的数目, 如果没有, 就立即返回 0
12.public abstract int select() throws IOException
public abstract int select(long timeout)throws IOException
->该方法采用阻塞的工作方式, 返回相关事件已经发生的 SelectionKey 对象的数目, 如果一个也没有, 就进入阻塞状态, 直到出现以下情况之一, 才从 select() 方法中返回.
1.至少有一个 SelectionKey 的相关事件已经发生;
2.其他线程调用了 Selector 的 wakeup() 方法, 导致执行 select() 方法的线程立即从 select() 方法中返回.
3.当前执行 select() 方法的线程被其他线程中断.
4.超出了等待时间. 该时间由 select(long timeout) 方法的参数 timeout 设定, 单位为毫秒. 如果等待超时, 就会正常返回, 但不会抛出超时异常. 如果程序调用的是不带参数的 select() 方法, 那么永远不会超时, 这意味着执行 select) 方法的线程进入阻塞状态后, 永远不会因为超时而中断.
13.public abstract Selector wakeup()
->唤醒执行 Selector 的 select() 方法(也同样设用于 select(long timeout) 方法) 的线程. 当线程A 执行 Selector 对象的 wakeup() 方法时, 如果线程B 正在执行同一个 Selector 对象的 select() 方法, 或者线程B 过一会儿会执行这个 Selector 对象的 select() 方法, 那么线程B 在执行 select() 方法时, 会立即从 select() 方法中返回, 而不会阻塞. 假如, 线程B 已经在 select() 方法中阻塞了, 也会立即被唤醒, 从select() 方法中返回.
->wakeup() 方法只能唤醒执行select() 方法的线程B 一次. 如果线程B 在执行 select() 方法时被唤醒后, 以后在执行 select() 方法, 则仍旧按照阻塞方式工作, 除非线程A 再次调用 Selector 对象的 wakeup() 方法
14.public abstract void close() throws IOException
->关闭 Selector. 如果有其他线程正执行这个Selector 的select() 方法并且处于阻塞状态, 那么这个线程会立即返回. close() 方法使得 Selector 占用的所有资源都被释放, 所有与 Selector 关联的 SelectionKey 都被取消
13.SelectionKey
1.ServerSocketChannel/SocketChannel #register() 方法向 Selector 注册事件时-> register() 方法会创建一个 SelectionKey 对象, 这个 SelectionKey 对象是用来跟踪注册事件的句柄->
2.在 SelectionKey对象的有效期间-> Selector 会一直监控与 SelectionKey 对象相关的事件, 如果事件发生, 就会把 SelectionKey 对象加入到 selected-keys 集合中.->在以下情况下, SelectionKey 对象会失效, 这意味着 Selector 再也不会监控与它相关的事件了:
1.程序调用 SelectionKey 的 cancel() 方法;
2.关闭与 SelectionKey 关联的 Channel;
3.与 SelectionKey 关联的 Selector 被关闭.
3.SelectionKey 中定义了 4 种事件->分别用 4 个 int 类型的常量来表示
1.SelectionKey.OP_READ
->读就绪事件, 表示通道中已经有了可读数据, 可以执行读操作了
->public static final int OP_READ = 1 << 0
2.SelectionKey.OP_WRITE
-> 写就绪事件, 表示已经可以向通道写数据了
->public static final int OP_WRITE = 1 << 2
3.SelectionKey.OP_CONNECT
->连接就绪事件, 表示客户与服务器的连接已经建立成功
->public static final int OP_CONNECT = 1 << 3
4.SelectionKey.OP_ACCEPT
->接收连接就绪事件, 表示服务器监听到了客户连接, 服务器可以接收这个连接了
->public static final int OP_ACCEPT = 1 << 4
5.各常量分别占据不同的二进制位, 因此可以通过二进制的或运算 "|", 来将它们进行任意组合->
->注:当然在判断某个事件发生的时候可以用&判断->
6.SelectionKey#interestOps()
->返回所有感兴趣的事件->
->如返回值为 SelectionKey.OP_WRITE | SelectionKey.OP_READ, 就表示这个 SelectionKey 对读就绪和写就绪事件感兴趣.->与之关联的 Selector 对象会负责监控这些事件
->SelectableChannel#register() 方法注册事件时, 可以在参数中指定 SelectionKey 感兴趣的事件
7.SelectionKey# interestOps(int ops)
->为 SelectionKey 对象增加一个感兴趣的事件
8.SelectionKey#readyOps()
->返回所有已经发生的事件
->如返回值为 SelectionKey.OP_WRITE | SelectionKey.OP_READ , 表示读就绪和写就绪事件发生了, 这意味着与之关联的 SocketChannel 对象可以进行读操作和写操作了
9.程序调用SelectableChannel#register时->
->建立SelectableChannel对象,Selector对象及register返回的SelectionKey对象之间的关联关系.
->SelectionKey->public abstract SelectableChannel channel()->返回关联的channel对象
->SelectionKey->public abstract Selector selector()->返回关联的selector对象
10.SelectionKey#public abstract boolean isValid()
->判断SelectionKey是否有效
->A key is valid upon creation and remains so until it is cancelled,its channel is closed, or its selector is closed.
11.SelectionKey# public abstract void cancel()
->使SelectionKey失效
->Upon return the key will be invalid and will have been added to its selector's cancelled-key set
->The key will be removed from all of the selector's key sets during the next selection operation.
12.SelectionKey#public final boolean isReadable()
->判断与之关联的SocketChannel的读就绪是否已经发生
->return (readyOps() & OP_READ) != 0
13.SelectionKey#public final boolean isWritable()
->判断与之关联的SocketChannel的读就绪是否已经发生
->return (readyOps() & OP_WRITE) != 0
14.SelectionKey#public final boolean isConnectable()
->判断与之关联的SocketChannel的连接就绪是否已经发生
->return (readyOps() & OP_CONNECT) != 0
15.SelectionKey#public final boolean isAcceptable()
->判断与之关联的SocketChannel的接收连接就绪是否已经发生
->return (readyOps() & OP_ACCEPT) != 0
16.SelectionKey#public final Object attach(Object ob)
->使SelectionKey关联一个附件
->只能关联一个Object类型附件
->多次调用该方法则最后一个附件关联
17.SelectionKey# public final Object attachment()
->返回SelectionKey对象关联的附件
部分源码:
package com.game.landon.nonblocking;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;
/** *//**
*
*测试ByteBuffer使用
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-8-16
*
*/
public class ByteBufferTester
{
public static void main(Stringargs) throws Exception
{
ByteBuffer buffer = ByteBuffer.allocate(32);
// 输出ByteBuffer的各个选项 position/limit/capacity
System.out.println(buffer.position());//0
System.out.println(buffer.limit());//32
System.out.println(buffer.capacity());//32
System.out.println(buffer.toString());//java.nio.HeapByteBuffer[pos=0 lim=32 cap=32]
buffer.limit(3);//修改limit
buffer.put((byte)0);
buffer.put((byte)1);
buffer.put((byte)2);
//Exception in thread "main" java.nio.BufferOverflowException->说明0-[limit-1]是可写的.
//buffer.put((byte)3);
//java.nio.HeapByteBuffer[pos=3 lim=3 cap=32]->此时position也变为了3->说明position是指向下一个可读写的位置的
System.out.println(buffer);
// 测试一下charset#encode的ByteBuffer
Charset charset = Charset.forName("UTF-8");
ByteBuffer charsetBuffer = charset.encode("landon");
//java.nio.HeapByteBuffer[pos=0 lim=6 cap=6]
System.out.println(charsetBuffer);
ByteBuffer charsetBuffer2 = charset.encode("landon:tracy");
//java.nio.HeapByteBuffer[pos=0 lim=12 cap=13]->发现增加了一个0.->暂时不确定为什么会这样->
//[108, 97, 110, 100, 111, 110, 58, 116, 114, 97, 99, 121, 0]
System.out.println(charsetBuffer2);
//[108, 97, 110, 100, 111, 110, 58, 116, 114, 97, 99, 121]
System.out.println(Arrays.toString("landon:tracy".getBytes("utf-8")));
// warp方法->注String实现了CharSequence接口
CharBuffer charBuffer = CharBuffer.wrap("landon:tracy");
System.out.println(charBuffer.capacity());//12
}
}
package com.game.landon.nonblocking;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
/** *//**
*
*Echo的channel handler
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-8-14
*
*/
public class EchoChannelHandler implements Runnable
{
private SocketChannel socketChannel;
public EchoChannelHandler(SocketChannel channel)
{
socketChannel = channel;
}
@Override
public void run()
{
handle(socketChannel);
//handle2(socketChannel);
}
private void handle(SocketChannel socketChannel)
{
try
{
Socket socket = socketChannel.socket();
System.out.println("receive client connection:" + socket.getInetAddress() + ":" + socket.getPort());
BufferedReader br = getReader(socket);
PrintWriter pw = getWriter(socket);
String msg = null;
while((msg = br.readLine()) != null)
{
System.out.println(msg);
pw.write(echo(msg));
if("bye".equals(msg))
{
break;
}
}
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
try
{
if(socketChannel != null)
{
socketChannel.close();
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
private void handle2(SocketChannel socketChannel)
{
try
{
Socket socket = socketChannel.socket();
System.out.println("receive client connection:" + socket.getInetAddress() + ":" + socket.getPort());
PrintWriter pw = getWriter(socket);
String msg = null;
while((msg = readLine(socketChannel)) != null)
{
System.out.println(msg);
//必须用println->Prints a String and then terminates the line
pw.println(echo(msg));
if("bye".equals(msg))
{
break;
}
}
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
try
{
if(socketChannel != null)
{
socketChannel.close();
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
/** *//**
* echo to client
* @param msg
* @return
*/
public String echo(String msg)
{
return "echo:" + msg;
}
/** *//**
* Wrapped {@link Socket#getOutputStream()} to PrintWriter
* @param socket
* @return
* @throws IOException
*/
private PrintWriter getWriter(Socket socket) throws IOException
{
// 返回输出流对象,向输出流写数据,就能向对方发送数据
OutputStream socketOut = socket.getOutputStream();
//public PrintWriter(OutputStream out, boolean autoFlush)
return new PrintWriter(socketOut,true);
}
/** *//**
* Wrapped {@link Socket#getInputStream()} to BufferedReader
* @param socket
* @return
* @throws IOException
*/
private BufferedReader getReader(Socket socket) throws IOException
{
// 返回输入流对象;只需从输入流读数据,就能接收来自对方的数据
InputStream socketIn = socket.getInputStream();
return new BufferedReader(new InputStreamReader(socketIn));
}
/** *//**
*
* 利用{@link SocketChannel#read(java.nio.ByteBuffer) }来读取一行字符串
* 相对来说比较麻烦
*
* @param channel
* @return
* @throws IOException
*/
private String readLine(SocketChannel channel) throws IOException
{
//存放所有读到的数据.假定一行字符串对应的字节序列的长度小于1024
ByteBuffer buffer = ByteBuffer.allocate(1024);
//存放一次读到的数据,一次只读一个字节
ByteBuffer tmpBuffer = ByteBuffer.allocate(1);
boolean isLine = false;//表示是否读到了一行字符串
boolean isEnd = false;//表示是否已经读到了输入流的末尾
String data = null;
while(!isLine && !isEnd)
{
tmpBuffer.clear();
//阻塞模式下,只有等读到了1个字节或者读到输入流末尾才返回
//非阻塞模式下,有可能返回0
int n = channel.read(tmpBuffer);
if(n == -1)
{
isEnd = true;
break;
}
if(n == 0)
{
continue;
}
tmpBuffer.flip();//极限设为位置,把位置设为0
buffer.put(tmpBuffer);//把tmpBuffer的数据复制到buffer中
buffer.flip();
Charset charset = Charset.forName("UTF-8");
CharBuffer charBuffer = charset.decode(buffer);
data = charBuffer.toString();
if(data.indexOf("\r\n") != -1)//表示读到了一行字符串
{
isLine = true;
data = data.substring(0, data.indexOf("\r\n"));
break;
}
buffer.position(buffer.limit());//把位置设置极限,为下次读数据做准备
buffer.limit(buffer.capacity());//把极限设置容量,为下次读数据做准备
}
return data;
}
}
package com.game.landon.nonblocking;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.SocketChannel;
import com.game.landon.entrance.EchoClient;
/** *//**
*
*nio实现的阻塞模式的echo client
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-8-15
*
*/
public class NioBlockingEchoClient
{
private SocketChannel socketChannel = null;
public NioBlockingEchoClient() throws IOException
{
socketChannel = SocketChannel.open();
// 默认阻塞模式,连接成功才返回
socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(), 8000));
System.out.println("connect server .success.");
}
/** *//**
* Wrapped {@link Socket#getOutputStream()} to PrintWriter
* @param socket
* @return
* @throws IOException
*/
private PrintWriter getWriter(Socket socket) throws IOException
{
OutputStream socketOut = socket.getOutputStream();
//public PrintWriter(OutputStream out, boolean autoFlush)
return new PrintWriter(socketOut,true);
}
/** *//**
* Wrapped {@link Socket#getInputStream()} to BufferedReader
* @param socket
* @return
* @throws IOException
*/
private BufferedReader getReader(Socket socket) throws IOException
{
InputStream socketIn = socket.getInputStream();
return new BufferedReader(new InputStreamReader(socketIn));
}
/** *//**
*
* talk to echo server
*
* @throws IOException
*/
public void talk() throws IOException
{
try
{
//与Echo Server通信用的到包装后的reader和writer
BufferedReader br = getReader(socketChannel.socket());
PrintWriter pw = getWriter(socketChannel.socket());
//本地reader,用来从system.in,即控制台输入数据
BufferedReader localReader = new BufferedReader(new InputStreamReader(System.in));
String msg = null;
while((msg = localReader.readLine()) != null)
{
//向server输出消息
pw.println(msg);
//从server读取消息
System.out.println(br.readLine());
if("bye".equals(msg))
{
break;
}
}
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
try
{
socketChannel.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
public static void main(Stringargs) throws IOException
{
new EchoClient().talk();
}
}
package com.game.landon.nonblocking;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/** *//**
*
*使用nio包,不过是阻塞模式的echo server
*1.ServerSocketChannel和SocketChannel均配置为阻塞模式运行
*2.同时处理多个client连接,需使用多线程
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-8-14
*
*/
public class NioBlockingEchoServer
{
private int port = 8000;
private ServerSocketChannel serverSocketChannel = null;
private ExecutorService executorService;//线程池
private static final int POOL_MULTIPLE = 4;//线程池工作线程数目
public NioBlockingEchoServer() throws IOException
{
executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * POOL_MULTIPLE);
//open一个ServerSocketChannel对象
serverSocketChannel = ServerSocketChannel.open();
// 使得同一个主机上关闭了服务器程序紧接着再启动该服务器程序时可以顺利绑定相同端口
serverSocketChannel.socket().setReuseAddress(true);
//与一个本地端口绑定
serverSocketChannel.socket().bind(new InetSocketAddress(port));
System.out.println("NioBlockingEchoServer launched");
}
public void service()
{
while(true)
{
SocketChannel socketChannel = null;
try
{
socketChannel = serverSocketChannel.accept();
executorService.execute(new EchoChannelHandler(socketChannel));//处理客户连接
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
public static void main(Stringargs) throws Exception
{
new NioBlockingEchoServer().service();
}
}
package com.game.landon.nonblocking;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
/** *//**
*
*混合使用阻塞模式和非阻塞模式实现的echo server
*
*<pre>
*1.接收Client连接的操作单独由一个线程完成->阻塞模式
*2.接收数据和发送数据的操作由另一个线程完成->非阻塞模式
*->提高服务器的并发性能
*->SelectaleChannel#register和Select#selector都会操纵selector共享资源的keys即可->所以其实现都对共享资源代码块进行了同步.避免了对共享资源的竞争
* synchronized (publicKeys) {
synchronized (publicSelectedKeys)
->问题1.如果主线程执行select处于阻塞状态(锁住了publicKeys)->而accept线程向selector注册事件的时候也可能阻塞(因为假如此时selector中尚未有任何注册的事件,锁住了publicKeys)->
即两个线程都出现了阻塞->死锁->
->2.为了避免死锁->accept线程向selector注册事件时->不允许select阻塞->调用selector#wakeup->唤醒selector,退出select方法
->3.为了协调主线程和accept线程.因为是多线程,accept线程被唤醒后,在下一次进入循环后又会调用select.而假如accept线程还未执行register.->所以
->一定加一个同步锁.即gate->accept线程需要先获得锁再执行wakeup->register->而主线程在select前需要获得gate->即需要等待accept线程执行完
->同步块内的代码后释放锁后再执行select
*</pre>
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-8-15
*
*/
public class NioMixedEchoServer
{
private Selector selector = null;
private ServerSocketChannel serverSocketChannel = null;
private Charset charset = Charset.forName("UTF-8");
private final int port = 8000;
public NioMixedEchoServer() throws IOException
{
selector = Selector.open();
// 默认为阻塞模式
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().setReuseAddress(true);
serverSocketChannel.socket().bind(new InetSocketAddress(port));
System.out.println("NioMixedEchoServer launched");
}
private final Object gate = new Object();
//accept连接处理
public void accept()
{
for(;;)
{
try
{
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("receive client connection :" + socketChannel.socket().getInetAddress() + ":" + socketChannel.socket().getPort());
socketChannel.configureBlocking(false);
ByteBuffer attachBuffer = ByteBuffer.allocate(1024);
// 协调主线程和accept线程,保证wakeup->register->select,防止死锁
synchronized(gate)
{
selector.wakeup();
socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE, attachBuffer);
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
//read and write
public void service() throws IOException
{
for(;;)
{
//一个空的同步代码块->为了让主线程等待accept线程执行完同步代码块
synchronized(gate)
{
}
int n = selector.select();
if(n == 0)
{
continue;
}
//返回selected-keys->存放相关时间已经发生的SelectionKey对象
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> selectIterator = selectedKeys.iterator();
while(selectIterator.hasNext())
{
SelectionKey key = null;
try
{
key = selectIterator.next();
// 从集合移除
selectIterator.remove();
// 处理读就绪事件
if(key.isReadable())
{
receive(key);
}
// 处理写就绪事件
if(key.isWritable())
{
send(key);
}
}
catch(IOException e)
{
e.printStackTrace();
try
{
if(key != null)
{
key.cancel();//使key失效->selector不再监听这个SelectionKey感兴趣的事件
key.channel().close();//关闭关联的channel
}
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
}
}
}
public void receive(SelectionKey key) throws IOException
{
//获得关联的附件
ByteBuffer buffer = (ByteBuffer)key.attachment();
//获得关联的SocketChannel
SocketChannel sc = (SocketChannel)key.channel();
//用于存放读到的数据
ByteBuffer readBuffer = ByteBuffer.allocate(32);
//非阻塞模式下.读到的数据是不确定的->读到的字节数0<=n<readbuffer.compacity
//因为要返回一行字符串,因为无法保证一次读入一行.因此需把每次读到的数据都放在buffer中->等凑足一行字符串->再发送给client
sc.read(readBuffer);
readBuffer.flip();
buffer.limit(buffer.capacity());
//将readBuffer的内容拷贝到buffer->假定buffer的容量足够大->不会出现缓冲区移除异常
buffer.put(readBuffer);
}
public void send(SelectionKey key) throws IOException
{
//获得SelectionKey的附件和关联的SocketChannel
ByteBuffer buffer = (ByteBuffer)key.attachment();
SocketChannel sc = (SocketChannel)key.channel();
buffer.flip();//极限设为位置.位置设为0
//按照utf-8编码将buffer中字节转为字符串
String data = decode(buffer);
// 如果还未读到一行数据则返回
if(data.indexOf("\r\n") == -1)
{
return;
}
// 截取一行数据.因为可能data有多余一行的数据
String outputData = data.substring(0,data.indexOf("\n") + 1);//这个+1是保证outputData包括\r\n->否则client将在readLine处阻塞
//此处print即可.因为outputData已经附带了\r\n
System.out.print(outputData);
//把输出的字符串按照utf-8编码转为字节
ByteBuffer outputBuffer = encode("echo:" + outputData);
// 输出outputBuffer中所有字节
//因为非阻塞模式下,不能保证一次就发送完outputBuffer所有的字节->所以采用循环
while(outputBuffer.hasRemaining())
{
sc.write(outputBuffer);
}
ByteBuffer tmpBuffer = encode(outputData);
buffer.position(tmpBuffer.limit());
// 删除buffer中已经处理的数据
buffer.compact();
if(outputData.equals("bye\r\n"))
{
key.cancel();
sc.close();
System.out.println("close client by receive bye.");
}
}
public String decode(ByteBuffer buffer)
{
CharBuffer charBuffer = charset.decode(buffer);
return charBuffer.toString();
}
public ByteBuffer encode(String str)
{
return charset.encode(str);
}
public static void main(String[] args) throws Exception
{
final NioMixedEchoServer echoServer = new NioMixedEchoServer();
// 单独起了一个接收client连接的线程
Thread accepThread = new Thread()
{
public void run()
{
echoServer.accept();
}
};
accepThread.start();
echoServer.service();
}
}
package com.game.landon.nonblocking;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
/** *//**
*
*nio非阻塞模式实现的Echo Client
*
*<pre>
*1.对于client与server的通信.按照其发送数据的协调程度区分,分为同步通信和异步通信
*2.同步通信指甲方向乙方发送了一批数据后->必须等接收到了乙方的响应数据后->再发送下一批数据
*3.异步通信是指发送数据和接收数据的操作互不干扰,各自独立进行
*4.通信的两端并不要求都采用相同的通信方式
*5.同步通信要求一个i/o操作完成之后,才能完成下一个io操作,用阻塞方式更容易实现
*6.异步通信允许发送操作和接收操作各自独立运行,用非阻塞模式更容易实现
*</pre>
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-8-15
*
*/
public class NioNonBlockingEchoClient
{
private SocketChannel socketChannel = null;
private Selector selector = null;
private Charset charset = Charset.forName("UTF-8");
private ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
private ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
public NioNonBlockingEchoClient() throws IOException
{
socketChannel = SocketChannel.open();
// 采用阻塞方式连接server
socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(), 8000));
//设置为非阻塞模式(用于读写数据)
socketChannel.configureBlocking(false);
System.out.println("connect server success");
selector = Selector.open();
}
//解码
public String decode(ByteBuffer buffer)
{
return charset.decode(buffer).toString();
}
//编码
public ByteBuffer encode(String str)
{
return charset.encode(str);
}
// 接收用户从控制台输入的数据->放到sendBuffer
public void receiveFromConsole()
{
try
{
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String msg = null;
while((msg = reader.readLine()) != null)
{
// 因为主线程向server发送数据的时候也需要访问sendBuffer.因此同步
synchronized(sendBuffer)
{
sendBuffer.put(encode(msg + "\r\n"));
}
if("bye".equals(msg))
{
break;
}
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
// 接收和发送数据
public void talk() throws IOException
{
socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
while(selector.select() > 0)
{
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
for(;iterator.hasNext();)
{
SelectionKey key = null;
try
{
key = iterator.next();
iterator.remove();
if(key.isReadable())
{
receive(key);
}
if(key.isWritable())
{
send(key);
}
}
catch(IOException e)
{
e.printStackTrace();
try
{
if(key != null)
{
key.cancel();
key.channel().close();
}
}
catch(IOException ex)
{
ex.printStackTrace();
}
}
}
}
}
// 发送sendBuffer 中的数据->这里潜在的问题是write是非阻塞的,不一定能保证一次发送完sendBuffer所有数据->正确情况是应该循环调用
//{@link NioNonBlockingEchoServer#send}
public void send(SelectionKey key) throws IOException
{
SocketChannel socketChannel = (SocketChannel)key.channel();
synchronized(sendBuffer)
{
sendBuffer.flip();//极限设为位置,位置设为0
socketChannel.write(sendBuffer);
// 删除已发送的数据
sendBuffer.compact();
}
}
// 接收echo server发过来的数据
// 如果receiveBuffer中有一行数据,则打印,然后删除
public void receive(SelectionKey key) throws IOException
{
SocketChannel socketChannel = (SocketChannel)key.channel();
socketChannel.read(receiveBuffer);
receiveBuffer.flip();
String receiveData = decode(receiveBuffer);
if(receiveData.indexOf("\r\n") == -1)
{
return;
}
// receiveData中可能有不止一行数据
String outputData = receiveData.substring(0, receiveData.indexOf("\n") + 1);
System.out.print(outputData);
if(outputData.equals("echo:bye\r\n"))
{
key.cancel();
key.channel().close();
System.out.println("stop connection with server.");
// 结束程序
selector.close();
System.exit(0);
}
// 删除已经打印的数据 encode后position为0.
ByteBuffer temp = encode(outputData);
receiveBuffer.position(temp.limit());
receiveBuffer.compact();
}
public static void main(Stringargs) throws Exception
{
final NioNonBlockingEchoClient echoClient = new NioNonBlockingEchoClient();
Thread consoleThread = new Thread()
{
public void run()
{
echoClient.receiveFromConsole();
}
};
consoleThread.start();
echoClient.talk();
}
}
package com.game.landon.nonblocking;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
/** *//**
*
*使用nio非阻塞实现的EchoServer
*<pre>
*只需要启动一个主线程,便可以处理3件事
*1.接收Client的连接
*2.接收Client发送的数据
*3.向客户发回响应数据
*</pre>
*
*<output>
*1.当Client连接至Server时.触发Acceptable事件->同时一直在循环触发Writable事件,即Socket一直可写.
*2.当Client输入回车的时候->会触发Readable事件
*3.当Client断掉连接的时候->也会触发Readable事件->并抛出异常->java.io.IOException: 远程主机强迫关闭了一个现有的连接
*4.如果Server未处理这个异常,即未执行key.cancel/key.channel.close(内部实现中会执行cancel方法)->则Selector一直会监听->会一直触发Readble事件->并一直会抛出异常
*</output>
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-8-14
*
*/
public class NioNonBlockingEchoServer
{
private Selector selector = null;
private ServerSocketChannel serverSocketChannel = null;
private final int port = 8000;
private Charset charset = Charset.forName("UTF-8");
// private Charset charset = Charset.defaultCharset();
public NioNonBlockingEchoServer() throws IOException
{
// open一个Selector对象
selector = Selector.open();
//open一个ServerSocketChannel对象并设定一些选项
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().setReuseAddress(true);
//配置工作与非阻塞模式
serverSocketChannel.configureBlocking(false);
//与本地端口绑定
serverSocketChannel.socket().bind(new InetSocketAddress(port));
System.out.println("NioNonBlockingEchoServer launched");
}
public void service() throws IOException
{
// 注册接收连接就绪事件->selector监控到该事件发生后->将相应的SelectionKey对象加入到selected-keys中
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
//select返回相关事件已经发生的SelectionKey的个数->当然没有任何事件发生则会阻塞下去直到至少有一个事件发生
while(selector.select() > 0)
{
//返回selected-keys->存放相关时间已经发生的SelectionKey对象
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> selectIterator = selectedKeys.iterator();
while(selectIterator.hasNext())
{
SelectionKey key = null;
try
{
key = selectIterator.next();
// 从集合移除
selectIterator.remove();
// 处理接收连接就绪事件
if(key.isAcceptable())
{
System.out.println(String.format("[%s] is acceptable", key.channel()));
//获取关联的ServerSocketChannel
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
//获得与client连接的SocketChannel
SocketChannel socketChannel = ssc.accept();
System.out.println("receive client connection from:" + socketChannel.socket().getInetAddress() +
":" + socketChannel.socket().getPort());
//socketChannel设置为阻塞模式
socketChannel.configureBlocking(false);
//创建一个用于存放client发送过来的数据的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//向selector注册读写就绪事件并关联一个附件buffer
socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE,buffer);
}
// 处理读就绪事件
if(key.isReadable())
{
System.out.println(String.format("[%s] is readable", key.channel()));
receive(key);
}
// 处理写就绪事件
if(key.isWritable())
{
// System.out.println(String.format("[%s] is writable", key.channel()));
send(key);
}
}
catch(IOException e)
{
e.printStackTrace();
try
{
if(key != null)
{
key.cancel();//使key失效->selector不再监听这个SelectionKey感兴趣的事件
key.channel().close();//关闭关联的channel
}
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
}
}
}
public void receive(SelectionKey key) throws IOException
{
//获得关联的附件
ByteBuffer buffer = (ByteBuffer)key.attachment();
//获得关联的SocketChannel
SocketChannel sc = (SocketChannel)key.channel();
//用于存放读到的数据
ByteBuffer readBuffer = ByteBuffer.allocate(32);
//非阻塞模式下.读到的数据是不确定的->读到的字节数0<=n<readbuffer.compacity
//因为要返回一行字符串,因为无法保证一次读入一行.因此需把每次读到的数据都放在buffer中->等凑足一行字符串->再发送给client
sc.read(readBuffer);
readBuffer.flip();
buffer.limit(buffer.capacity());
//将readBuffer的内容拷贝到buffer->假定buffer的容量足够大->不会出现缓冲区移除异常
buffer.put(readBuffer);
}
public void send(SelectionKey key) throws IOException
{
//获得SelectionKey的附件和关联的SocketChannel
ByteBuffer buffer = (ByteBuffer)key.attachment();
SocketChannel sc = (SocketChannel)key.channel();
buffer.flip();//极限设为位置.位置设为0
//按照utf-8编码将buffer中字节转为字符串
String data = decode(buffer);
// 如果还未读到一行数据则返回
if(data.indexOf("\r\n") == -1)
{
return;
}
// 截取一行数据.因为可能data有多余一行的数据
String outputData = data.substring(0,data.indexOf("\n") + 1);//这个+1是保证outputData包括\r\n->否则client将在readLine处阻塞
//此处print即可.因为outputData已经附带了\r\n
System.out.print(outputData);
//把输出的字符串按照utf-8编码转为字节
ByteBuffer outputBuffer = encode("echo:" + outputData);
// 输出outputBuffer中所有字节
//因为非阻塞模式下,不能保证一次就发送完outputBuffer所有的字节->所以采用循环
while(outputBuffer.hasRemaining())
{
sc.write(outputBuffer);
}
ByteBuffer tmpBuffer = encode(outputData);
buffer.position(tmpBuffer.limit());
// 删除buffer中已经处理的数据
buffer.compact();
if(outputData.equals("bye\r\n"))
{
key.cancel();
sc.close();
System.out.println("close client by receive bye.");
}
}
public String decode(ByteBuffer buffer)
{
CharBuffer charBuffer = charset.decode(buffer);
return charBuffer.toString();
}
public ByteBuffer encode(String str)
{
return charset.encode(str);
}
public static void main(String[] args) throws Exception
{
NioNonBlockingEchoServer server = new NioNonBlockingEchoServer();
server.service();
}
}
package com.game.landon.nonblocking;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.LinkedList;
/** *//**
*
*非阻塞的ping实现
*
*<pre>
*1.当client程序只需要与服务器建立一个连接时,用阻塞模式即可->简化编程
*2.如果client程序需要与服务器同时建立多个连接,因每个连接都需要消耗一定时间.所以可采用非阻塞模式建立连接
*3.本例接收从console输入的域名->与这个主机的80端口建立连接->最后打印建立连接所花的时间
*4.本例使用了3个线程
* 1.主线程:从console接收输入数据,生成PingTask.加入到targets队列中{@link #receiveConsoleTargets()}
* 2.连接线程:负责从targets队列中取出PingTask->向Selecor注册连接就绪->查询已经就绪的事件->放入finisedTarget中
* {@link #registerTargets()} {@link #processSelectedKeys()}
3.打印线程:负责从finishedTargets中取出PingTask对象,print {@link #printFinishedTargets()}
*</pre>
*
*<output>
*www.thinkjava.org
*www.thinkjava.org/202.106.195.30:80:java.net.ConnectException: Connection timed out: no further information
*baidu.com
*baidu.com/123.125.114.144:80:2 ms
*blogjava.net
*blogjava.net/42.121.253.67:80:68 ms
*blogjava.net/landon
*blogjava.net/landon:80:java.nio.channels.UnresolvedAddressException
*</output>
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-8-16
*
*/
public class PingClient
{
private Selector selector;
//存放console新提交的任务
private LinkedList<PingTask> targets = new LinkedList<PingTask>();
// 存放已经完成的需要打印的任务
private LinkedList<PingTask> finishedTargets = new LinkedList<PingTask>();
private boolean isShutdown = false;
// 接收client从console输入的域名->向targets中加入任务->主线程会调用此方法
public void receiveConsoleTargets()
{
try
{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String msg = null;
while((msg = br.readLine()) != null)
{
if(!msg.equals("bye"))
{
PingTask task = new PingTask(msg);
addPingTask(task);
}
else
{
// 输入bye时,结束程序->如果此时selecor在阻塞->则唤醒->connector 线程在下一轮循环的时候就会退出while->
isShutdown = true;
selector.wakeup();//使得Connector线程从selector#select退出
break;
}
}
}
catch(IOException e)
{
e.printStackTrace();
}
}
// 向targets队列中加入一个任务
public void addPingTask(PingTask task)
{
SocketChannel socketChannel = null;
try
{
socketChannel = SocketChannel.open();
// 非阻塞模式
socketChannel.configureBlocking(false);
socketChannel.connect(task.getTargetAddress());
task.channel(socketChannel);
task.connectStart(System.currentTimeMillis());
synchronized(targets)
{
targets.add(task);
}
// 这个非常必要->因为如果之前没有任何SocketChannel向Selecor注册连接就绪事件->Connector线程执行selector#selector->
// 将会永远阻塞下去->主线程调用了其wakeup后->唤醒了connector线程->其退出select->connector线程在下次循环中先执行register->
// 而此时targets队列中已经有task->主线程就会向selector注册连接就绪事件->selecor就会执行监听了
selector.wakeup();
}
catch(Exception e)
{
if(socketChannel != null)
{
try
{
socketChannel.close();
}
catch(IOException ex)
{
e.printStackTrace();
}
}
task.exceptionFail(e);
addFinishedTarget(task);
}
}
// ping connect thread
private class PingConnector extends Thread
{
public PingConnector()
{
super("Ping Connector Thread.");
}
public void run()
{
while(!isShutdown)
{
try
{
registerTargets();
if(selector.select() > 0)
{
processSelectedKeys();
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
try
{
selector.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
// 取出targets队列中的任务.向selector注册连接就绪事件.PingConnector会调用此方法
public void registerTargets()
{
synchronized(targets)
{
while(!targets.isEmpty())
{
PingTask task = targets.removeFirst();
try
{
// 将task作为附件
task.getSocketChannel().register(selector, SelectionKey.OP_CONNECT,task);
}
catch(IOException e)
{
try
{
task.getSocketChannel().close();
}
catch(Exception ex)
{
ex.printStackTrace();
}
task.exceptionFail(e);
addFinishedTarget(task);
}
}
}
}
// 处理连接就绪事件.PingConnector会调用此方法
public void processSelectedKeys() throws IOException
{
for(Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();iterator.hasNext();)
{
SelectionKey key = iterator.next();
iterator.remove();
PingTask task = (PingTask)key.attachment();
SocketChannel channel = (SocketChannel)key.channel();
try
{
// 调用finishConnect
if(channel.finishConnect())
{
key.cancel();
task.connectFinish(System.currentTimeMillis());
channel.close();
addFinishedTarget(task);
}
}
catch(IOException e)
{
channel.close();
task.exceptionFail(e);
addFinishedTarget(task);
}
}
}
//打印线程
private class PingPrinter extends Thread
{
public PingPrinter()
{
super("Ping Printer Thread");
setDaemon(true);//设置为后台线程.主线程和connector结束后,其也会自动结束
}
public void run()
{
printFinishedTargets();
}
}
// 打印finishedTargets队列中的任务.PingPrinter线程会调用此方法
public void printFinishedTargets()
{
try
{
for(;;)
{
PingTask task = null;
synchronized(finishedTargets)
{
while(finishedTargets.size() == 0)
{
// 阻塞
finishedTargets.wait();
}
task = finishedTargets.removeFirst();
}
task.print();
}
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
// 向finishedTargets队列中加入一个任务.主线程和printConnector线程会调用该方法
public void addFinishedTarget(PingTask task)
{
synchronized(finishedTargets)
{
finishedTargets.add(task);
// 唤醒wait的print线程
finishedTargets.notify();
}
}
public PingClient() throws IOException
{
selector = Selector.open();
}
public void start()
{
PingConnector connector = new PingConnector();
PingPrinter printer = new PingPrinter();
connector.start();
printer.start();
receiveConsoleTargets();
}
public static void main(String[] args) throws Exception
{
PingClient client = new PingClient();
client.start();
}
}
package com.game.landon.nonblocking;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
/** *//**
*
*表示ping的任务{@link PingClient}
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-8-16
*
*/
public class PingTask
{
private InetSocketAddress targetAddress;
private SocketChannel socketChannel;
private Exception failure;
private long connectStartTime;//开始连接的时间
private long connectFinishTime;//连接成功的时间
private boolean isPrinted = false;// 该任务是否已经打印
public PingTask(String host)
{
try
{
targetAddress = new InetSocketAddress(host, 80);
}
catch(Exception e)
{
failure =e;
}
}
// 打印任务的执行结果
public void print()
{
String result;
if(connectFinishTime != 0)
{
result = (connectFinishTime - connectStartTime) + " ms";
}
else if(failure != null)
{
result = failure.toString();
}
else
{
result = "timeout";
}
System.out.println(targetAddress + ":" + result);
isPrinted = true;
}
public InetSocketAddress getTargetAddress()
{
return targetAddress;
}
public void channel(SocketChannel channel)
{
this.socketChannel = channel;
}
public void connectStart(long startTime)
{
connectStartTime = startTime;
}
public void connectFinish(long finishTime)
{
connectFinishTime = finishTime;
}
public void exceptionFail(Exception fail)
{
failure = fail;
}
public SocketChannel getSocketChannel()
{
return socketChannel;
}
}
package com.game.landon.nonblocking;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
/** *//**
*
*测试非阻塞的Accept
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-8-8
*
*/
public class TestNonBlockingAccept
{
public static void main(String[] args) throws IOException
{
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(8000));
ssc.configureBlocking(false);
// 注册接收连接就绪事件
ssc.register(selector, SelectionKey.OP_ACCEPT);
while(selector.select() > 0)
{
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext())
{
SelectionKey key = iterator.next();
iterator.remove();
if(key.isAcceptable())
{
ServerSocketChannel atachChannel = (ServerSocketChannel)key.channel();
SocketChannel socketChannel = atachChannel.accept();
System.out.println(socketChannel.toString());
}
}
}
}
}
package com.game.landon.nonblocking;
import java.io.IOException;
import java.net.Socket;
/** *//**
*
*测试非阻塞Accept的client
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-8-8
*
*/
public class TestNonBlockingAcceptClient
{
public static void main(String[] args) throws IOException
{
Socket socket = new Socket("localhost",8000);
socket.close();
System.out.println("end.");
}
}
posted on 2013-08-16 18:42
landon 阅读(4084)
评论(1) 编辑 收藏 所属分类:
Program 、
Book