非阻塞通讯(3)

通道Channel:

通道Channel用来连接缓冲区与数据源或数据汇(数据目的地)。如图:


数据源的数据通过通道到达缓冲区,缓冲区的数据经过通道到达数据汇。

Channel的主要层次结构:


java.nio.channels.Channel接口只声明了两个方法:
close():关闭通道;
isOpen():判断通道是否打开
通道在创建时被打开,一旦关闭通道,就不能重新打开了。

Channel接口的两个重要的子接口是ReadableByteChannel和WritableByteChannel.ReadableByteChannel接口声明了read(ByteBuffer dst)方法,该方法把数据缘的数据读入参数指定的ByteBuffer缓冲区中。WriteableByteChannel接口声明了write(ByteBuffer src)方法,该方法把参数指定的ByteBuffer缓冲区的数据写到数据汇中。下图显示了Channel与Buffer的关系,ByteChannel接口是一个便利的接口,它扩展了ReadableByteChannel和WriteableByteChannel接口,因而同时支持读写操作。


ScatteringByteChannel接口扩张了ReadableByteChannel接口,允许分散地读取数据。分散读取数据是纸单个读取操作能填充多个缓冲区。ScatteringByteChannel接口声明了read(ByteBuffer[] dsts)方法,该方法就从数据源读取的数据依次填充到参数指定的ByteBuffer数组的各个ByteBuffer中。GatheringByteChannel接口扩展了WriteableByteChannel接口,允许集中地写入数据。集中写入数据是指单个写操作能把多个缓冲区的数据写到数据汇,GatheringByteChannel接口声明了write(ByteBuffer[] srcs)方法,该方法依次把参数指定的ByteBuffer数组的每个ByteBuffer中的数据写到数据汇。分散读取和集中写数据能够进一步提高输入和输出操作的速度。

FileChannel类是Channel接口的实现类,代表一个与文件相连的通道。该类实现了ByteChannel,ScatteringByteChannel和GatheringByteChannel接口,支持读操作,写操作,分散读操作和集中写操作。FileChannel类没有提供公开的构造方法,因此客户程序不能用new语句来构造它的实例。不过,在FileInputStream,FileOutputStream和RandomAccessFile类中提供了getChannel方法,改方法返回相应的FileChannel对象。
SelectableChannel也是一种通道,它不仅支持阻塞的I/O操作,还支持非阻塞的I/O操作。SelectableChannel有两个子类:ServerSocketChannel和SocketChannel。SocketChannel还实现了ByteChannel接口,具有read(ByteBuffer dst)和write(ByteBuffer src)方法。

SelectableChannel类:
SelectableChannel是一种支持阻塞I/O和非阻塞I/O的通道。在非阻塞模式下,该写数据不会阻塞,并且SelectableChannel可以向Selector注册读就绪和写就绪时间,Selector负责监控这些事件,等到事件发生时,比如发生了读就绪事件,SelectableChannel就可以执行读操作了。
SelectableChannel的主要方法如下:
public SelectableChannel configureBlocking(boolean block)throws IOException
当参数block为true时,表示把SelectableChannel设为阻塞模式:如果参数block为false,表示把SelectableChannel设为非阻塞模式。默认情况下,SelectableChannel采用阻塞模式。该方法返回SelectableChannel对象本身引用,相当于"return this"。
SelectableChannel的isBlocking()方法判断SelectableChannel是否处于阻塞模式,如果返回true,表示处于阻塞模式,负责表示处于非阻塞模式。
public SelectionKey register(Selector sel,int ops)throws ClosedChannelException
public SelectionKey register(Selector sel,int ops,Object attachment)throws Closed ChannelException
后两个方法都向Selector注册事件,以如下socketChannel(SelectableChannel的一个子类)向Selector注册读就绪和写就绪事件:
SelectionKey key = socketChannel.register(selector,SelectionKey.OP_READ | SelctionKey.OP_WRITE);
register方法返回一个SelectionKey对象,SelectionKey用来跟踪注册的事件。第二个register方法还有一个Object类型的参数attachment,它用于为SelectionKey关联一个附件,当被注册事件发生后,需要处理该事件时,可以从SelectionKey中获得这个附件,该附件可用来包含与处理这个事件相关的信息。一下两段代码等价:
MyHandler handler = new MyHandler();
SelectionKey key = socketChannel.register(selector,SelectionKey.OP_READ | SelectionKey.OP_WRITE,handler);
等价于:
SelectionKey key = socketChannel.register(selector,SelectionKey.OP_READ | SelectionKey.OP_WRITE);
MyHandler handler = new MyHandler();
key.attach(handler);

ServerSocketChannel类:
ServerSocketChannel从SelectableChannel中继承了configureBlocking()和register()方法。ServerSocketChannel是ServerSocket的替代类,也具有负责接收客户的accept方法。ServerSocketChannel并没有public类型的构造方法,必须通过它的构造方法open()来创建ServerSocketChannel对象。
    public static ServerSocketChannel open() throws IOException {
    return SelectorProvider.provider().openServerSocketChannel();//选择器通过专门的工厂SelectorProvider来创建Selector的实现,SelectorProvider屏蔽了不同操作系统及版本创建实现的差异性
    }
    
每个ServerSocketChannel对象都与一个ServerSocket对象关联。ServerSocketChannel的socket()方法返回与它关联的ServerSocket对象,可通过一下方式把服务器进程绑定到一个本地端口:
serverSocketChannel.socket().bind(port);
SocketSocketChannel的主要方法如下:
public static ServerSocketChannel open()throws IOException
这是ServerSocketChannel类的静态工厂方法,它返回一个ServerSocketChannel对象。这个对象没有与任何本地端口绑定,并且处于阻塞模式。
public SocketChannel accept()throws IOException
类似于ServerSocket的accept方法,用于接收客户的连接。如果ServerSocketChannel处于非阻塞模式,当没有客户连接时,该方法立即返回null,如果ServerSocketChannel处于非阻塞模式,当没有客户连接时,它会一直阻塞下去,直到有客户连接就绪,或者出现了IOException。
值得注意的是,该方法返回的SocketChannel对象处于阻塞模式,如果希望它改为非阻塞模式,必须执行一下代码:
socketChannel.configureBlocking(false);
public final int vaildOps()
返回ServerSocketChannel所能产生的事件,这个方法总是返回SelectionKey.OP_ACCEPT
public ServerSocket socket()
返回与ServerSocketChannel关联的ServerSocket对象,每个ServerSocketChannel对象都与一个ServerSocket对象关联。

SocketChannel类:
SocketChannel可看做是Socket的替代类,但它比Socket具有更多的功能。SocketChannel不仅从SelectableChannel父类中继承了configureBLocking()和register()方法,而且实现了ByteChannel接口,因此具有用于读写数据的read(ByteBuffer dst)和write(ByteBuffer src)方法。SocketChannel没有public类型的构造方法,必须通过它的静态方法open()来创建SocketChannel对象。
SocketChannel的主要方法如下:
public static SocketChannel open()throws IOException
public static SocketChannel open(SocketAddress remote)throws IOException
以下两段代码是等价的:
SocketChannel socketChannel = SocketChannel.open()
channel.connect(remote)//remote为SocketAddress类型
等价于:SocketChannel socketChannel = SocketChannel.open(remote)为什么呢?看看源码做了些什么...
 public static SocketChannel open(SocketAddress remote)
    throws IOException
    {
    SocketChannel sc = open();
    sc.connect(remote);
    return sc;
    }
无非就是在open方法里面同时实现了connect方法。
值得注意的是,open()方法返回的SocketChannel对象处于阻塞模式,如果它改为非阻塞模式,必须执行以下代码。
socketChannel.configureBlocking(false);
public final int validOps()这个的返回跟ServerSocket.validOps()不同:
   public final int validOps() {
    return (SelectionKey.OP_READ
        | SelectionKey.OP_WRITE
        | SelectionKey.OP_CONNECT);
    }
返回SocketChannel所能产生的事件。
public Socket socket()
返回与这个SocketChannel关联的Socket对象。每个SocketChannel对象都与一个Socket对象关联。
public boolean isConnected()
判断底层Socket是否已经建立了远程连接
public boolean connect(SocketAddress remote)throws IOException
使底层Socket建立连接。当SocketChannel处于非阻塞模式时,如果立即连接成功,该方法返回true,如果不能立即连接成功,该方法返回false,程序过会儿必须通过调用finishConncet()方法来完成连接。
public boolean finishConnect()throws IOException
试图完成连接远程服务器的操作,在非阻塞模式下,建立连接从调用SocketChannel的connect方法开始,到调用finishConnect()方法结束。如果finishConnect()方法顺利完成连接,或者在调用此方法之前连接已经建立,则finishConnect()方法立即返回true。如果连接操作还没有完成,则立即返回false;如果连接操作中遇到异常而失败,则抛出相应的I/O异常。

public int read(ByteBuffer dst)throws IOException

从Channel中读入若干字节,把它们存放到参数指定的ByteBuffer中,假定执行read()方法前,ByteBuffer的位置为P,剩余容量为r,r等于容量为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,表示读到了输入流的末尾。
public int write(ByteBuffer src)throws IOException

把参数src指定的ByteBuffer中的字节写到Channel中。假定执行 wirte()方法前,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.

Selector类:
只要ServerSocketChannel及SocketChannel向Selector注册了特定的事件,Selector就会监控这些事件是否发生,SelectableChannel的register方法负责注册事件,该方法返回一个SelectionKey对象,该对象是用来追踪这些被注册事件的句柄。一个Selector对象中会包含3中类型的SelectionKey集合:
all-keys集合:当前所有向Selector注册的SelectionKey的集合,Selector的Keys()方法返回该集合。
selected-keys集合:相关事件已经被Selector捕获的SelectionKey的集合。Selector的selectedKeys()方法返回该集合。
cancelled-keys集合:已经被取消的SelectionKey的集合。Selector没有提供访问这种集合的方法。
当执行SelectableChannel的register()方法时,该方法新建一个SelectionKey,并把它加入Selector的all-keys集合中。
如果关闭了与SelectionKey对象关联的Channel对象,或者调用了SelectionKey对象的cannel()方法,这个SelectionKey对象就会被加入到cannelled-keys集合中,表示这个SelectionKey对象已经被取消,在程序下一个执行Selector的select()方法时,被取消的SelectionKey对象将从所有的集合(包括all-keys集合,selected-keys集合和cancelled-keys集合)中删除。
在执行Selector的select()方法时,如果与SelectionKey相关的事件发生了,这个SelectionKey就被加入到selected-keys集合中,程序直接调用selected-keys集合的remove()方法,或者调用它的Iterator的remove()方法,都可以从selected-keys集合中删除一个SelectionKey对象。
程序不允许直接通过集合接口的remove()方法删除all-keys集合中的SelectionKey对象。如果程序试图这么做,那么会导致UnsupportedOperationException。
Selector类的主要方法如下:
public static Selection open()throws IOException
这是Selector的静态工厂方法,创建一个Selector对象
public boolean isOpen()
判断Selector是否处于打开状态。Selector对象创建后就处于打开状态,当调用了Selector对象的close()方法,它就进入关闭状态
pulbic Set<SelectionKey> keys()
返回Selector的all-keys集合,它包含了所有与Selector关联的SelectionKey对象
public int selectNow()throws IOException
返回相关事件已经发生的SelectionKey对象的数目,该方法采用非阻塞的工作方式,返回当前相关事件已经发生的SelectionKey对象的数目,如果没有,就返回0
public int select()throws IOException
该方法采用阻塞的工作方式,返回相关事件已经发生的SelectionKey对象的数目,如果一个也没有,就进入阻塞状态,知道出现以下情况之一,才从select()方法中返回。
1,至少有一个SelectionKey的相关事件已经发生了;
2,其他线程调用了Selector的wakup()方法,导致执行select()方法的线程立即从select()方法返回;
3,当前执行select()方法的线程被其他线程中断了;
4,超出了等待时间,该时间由select(long timeout)方法的参数timeout设定,单位为毫秒,如果等待超时,就会正常返回,但不会抛出超时异常。如果程序调用的是不带参数的select()方法,那么永远不会超时,这意味着执行select()方法的线程进入阻塞状态后,永远不会因为超时而中断。
public 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()方法
public void close()throws IOException

注意一下Selector的并发性:
选择器自身可由多个并发线程安全使用,但是其键集并非如此。
选择操作在选择器本身上、在键集上和在已选择键集上是同步的,顺序也与此顺序相同。它们在已取消键集上也是同步的。
在执行选择操作的过程中,更改选择器键的相关集合对该操作没有影响;进行下一次选择操作才会看到此更改。
可在任意时间取消键和关闭通道。因此,在一个或多个选择器的键集中出现某个键并不意味着该键是有效的,也不意味着其通道处于打开状态。如果存在另一个线程取消某个键或关闭某个通道的可能性,那么应用程序代码进行同步时应该小心,并且必要时应该检查这些条件。

虽然保证在选择器本身上、在键集上和在已选择键集上是同步的,但是任何线程可在任意时间取消键和关闭通道 ,从而导致在一个或多个选择器的键集中出现某个键并不意味着该键是有效的,也不意味着其通道处于打开状态。

SelctionKey类:
这个类具体的方法看API
主要有一个attachment,让一个Object类型与SelectionKey对象关联。
多个并发线程可安全地使用选择键。一般情况下,读取和写入 interest 集合的操作将与选择器的某些操作保持同步。具体如何执行该同步与实现有关:在一般实现中,如果正在进行某个选择操作,那么读取或写入 interest 集合可能会无限期地阻塞;在高性能的实现中,可能只会暂时阻塞。无论在哪种情况下,选择操作将始终使用该操作开始时当前的 interest 集合值。
API的内容暂时到这里。

说一下编码问题;
javac如何编译该字符串。首先,javac看命令行中有没有用-encoding参数指定一个字符集,没有,则用系统环境中指定的字符集。接下来javac开始解释源码文件,遇到多字节的字符,就用前面确定的字符集编码来解释,并转换为unicode,写入.class 的字节码文件里面。
当运行这个class文件了,jvm启动后读入class字节码,那些个中文字符串都以unicode表示,在哪都一样(平台无关的)。下面可能需要输出这个字符串到其它的应用程序了:控制台/文件/socket等等等等....这样jvm首先要检查操作系统的 encoding(注意,jvm从字节码里面完全不知道这些字符串是什么编码的,全是unicode),然后encoding来解释这些 unicode码到操作系统的encoding。当然,对于特殊的环境jvm也许不去检查操作系统的encoding,而是从环境变量里面读,这样你就可以控制jvm执行时的目的encoding了。最后一步,如果你的操作系统的(或目的)encoding与真实的encoding一致(或具有兼容关系),如果不一只那么就出现乱码问题了。所以你需要手动的设定编码方式,让unicode编码成你原来的编码方式,这样就可以在其他机子上整常显示。
再说说PritStream和PrintWriter的区别:
java的一个字符(char)是16bit的,一个BYTE是8bit的
1,PrintStrean是写入一串8bit的数据的,PrintWriter是写入一串16bit的数据的。
2,PrintStream操作byte,PrintWriter操作Unicode字符。
3,String缺省是用UNICODE编码,是16bit的。因此用PrintWriter写入的字符串,跨平台性好一些。PrintStream的可能会出现字符集乱码吧。

接下来会介绍jsockets开源框架!加油,COME ON!


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值