Java nio(四)

本来打算昨天总结这部分的,一方面昨天上了一天课,另一方面,这部分想理清全部的思路
并不那么容易.看《Java nio》那本书,差点没看崩溃,洋洋洒洒写了近三百大页介绍java nio,写的太散太细,反而觉得很乱,理不清思路了。那本书用起来作为参考手册还行,想很快上手这本书还真不适合。
今天打算总结和Socket异步通信相关的内容。
传统的Socket是阻塞,像ServerSocket在调用accept方法后便处于阻塞状态等待Client端的连接,所以一般会在Server端使用许多线程,对每一个Socket连接分配一个线程。充分利用并发特性来提高性能。
但这样会带来许多问题:
1、Server端创建了许多线程来处理Socket连接,而这些线程大部分的时间都在等待连接,
也就是说这些线程占了资源,真正做事情的时间却不多。也就是说资源利用率比较低,这就会
直接导致一个问题,可伸缩性比较差,当接受1000个连接还可以,但增加到10000个或更多时性能会很快下降。像Web服务器Jetty和Tomcat6.x都是用了异步通信模式,来接受客户端的连接,从而很大程度上提高了系统的伸缩性,提高了性能。
2、由于使用多线程,就会使问题变得复杂,事实如果你敢说精通并发编程,说明你太乐观了,并发编程很容易出错,并且你很难发现问题。并且需要互斥访问一些资源,这往往是个瓶颈,会降低并发性。
异步的Socket可以解决上面的问题,异步的Socket它是非阻塞的,它尝试去连接,但不管是否能够立即建立连接,它都会立即返回,返回之后它便可以做其他的事情了,但连接真正建立成功,就会有相应的事件来通知,这时候你去做连接成功之后的读写操作了.这个过程,整个线程都是处于忙碌状态,所以只需要单个或者很少几个线程,就可以达到阻塞方式的成百上千的线程的性能.
类似异步Socket功能应运而生,但Java在jdk1.4才引入这个功能,考虑以前Socket的已提供的功能,而且接口很难一致,Java并没有单独设计异步Socket,而是在java nio中引入了SocketChannel之类的通道来处理这个问题,我们会发现这些通道和对应的Socket提供的接口有很大的相似之处,但这些Channel不是关于Socket的抽象,它们并不处理TCP/UDP协议,而是委托给对Socket来处理。
这种新的Channel可以在非阻塞的模式下操作。通过注册Selector,使用一种类似观察者模式。其实是Selector不断的轮询连接的端口,我们可以通过Selector的select()方法,这个方法是阻塞的,他会更新就绪操作集的键的数目,并作为返回值返回。我们会通常在判断这个返回值如果不为零,则可能有我们感兴趣的事件发生,然后我们可以通过
Iterator it = selector.selectedKeys().iterator();

来得到键的迭代器,这样我们就可以通过遍历这个集合来判断有没有我们感兴趣的事情发生,如果有我们就做一些处理,没有我们可以把这个键remove掉:

while(it.hasNext()){
SelectionKey key = (SelectionKey)it.next();
if(key.isAcceptable()){
//do something
}
if(key.isReadable()){
//read data
}
it.remove();//we remove the key beacause of we don't care about it
}

一、下面我们介绍一下与Socket相关的各种Channel
与几种Socket对应的提供了一下几种Channel:
ServerSocketChannel,SocketChannel,DatagramChannel.
1、ServerSocketChannel:

abstract SocketChannel accept()
// 接受到此通道套接字的连接。
static ServerSocketChannel open()
//打开服务器套接字通道。
abstract ServerSocket socket()
//获取与此通道关联的服务器套接字。
int validOps()
//返回一个操作集,标识此通道所支持的操作。

我们发现这个Channel根本不支持读写操作,叫Channel有点名不副实了,但Java中Channel本身的抽象也不一定支持读写操作的,需要实现WriteableChannel和ReadableChannnel接口才能支持。其实ServerSocketChannel本身也不是用于读写
操作的,它通常通过socket() 方法获取相关的ServerSocket,然后通过ServerSocket 方法bind来绑定端口。ServerSocketChannel提供了open()静态工厂方法来创建ServerSocketChannel对象。同时ServerSocketChannel从AbstractSelectableChannel
继承了:

SelectableChannel configureBlocking(boolean block)
//调整此通道的阻塞模式。
SelectionKey register(Selector sel, int ops)
//向给定的选择器注册此通道,返回一个选择键。

这两个方法是最常用的了。通过configureBlocking来设置通道的是否为阻塞模式,
通过register向给定的选择器注册此通道。
从上面的过程我们用代码总结一下一般的流程:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
Selector selector = Selector.open();
serverSocket.bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
while (true) {
int n = selector.select();
if( n == 0)
continue;
Iterator it = selector.selectedKeys().iterator();
while(it.hasNext()){
SelectionKey key = (SelectionKey)it.next();
if(key.isAcceptable()){
ServerSocketChannel server =
(ServerSocketChannel) key.channel();
SocketChannel channel = server.accept();//获取SocketChannel来通信
registerChannel (selector, channel,
SelectionKey.OP_READ); doSomething(channel);
}
if(key.isReadable()){
readDataFormSocket(key);
}
it.remove();
}
}

2、SocketChannel:
SocketChannel通常作为客户端,建立一个对Server端的连接,任何一个SocketChannel
都是和对应的Socket关联的,但一个Socket并不一定有SocketChannel可以获得。
同样的SocketChannel也适用静态工厂方法open()来实例化SocketChannel.
下面来看看最常用的操作:
[quote]
public abstract boolean connect(SocketAddress remote)
throws IOException连接此通道的套接字。
如果此通道处于非阻塞模式,则调用此方法会发起一个非阻塞连接操作。如果立即建立连接(使用本地连接时就是如此),则此方法返回 true。否则此方法返回 false,并且必须在以后通过调用 finishConnect 方法来完成该连接操作。
[/quote]
我们可以通过

socketChannel.connect (new InetSocketAddress ("somehost", somePort));

来建立连接,这个过程是异步的,他会立即返回,如果立即建立连接成功则返回true,否则
返回false.随后可以通过finishConnect来完成连接的建立。
另外可通过:

SocketChannel socketChannel =
SocketChannel.open (new InetSocketAddress ("somehost", somePort));
建立连接
等价于:

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect (new InetSocketAddress ("somehost", somePort));

下面我们演示一下整个的使用过程:

InetSocketAddress addr = new InetSocketAddress (host, port);
SocketChannel sc = SocketChannel.open();
sc.configureBlocking (false);
sc.connect (addr);
while ( ! sc.finishConnect()) {
doSomethingElse();
}
doSomethingWithChannel (sc);
sc.close();

3、DatagramChannel
这个Channel与DatagramSocket相对应,提供了基于UDP协议的数据包的套接字的通道。
UDP协议是无连接的,DatagramChannelt既可以作为Server端,也可以作为Client端,如果想新创建一个DatagramChannel作为Server端来监听,那么需要绑定到特定的端口或地址和端口的组合,一般过程如下:

DatagramChannel channel = DatagramChannel.open();
DatagramSocket socket = channel.socket();
socket.bind (new InetSocketAddress (portNumber));

但是一个没有绑定特定端口的DatagramChannel仍然是可以接收数据的,事实上会有一个
动态生成的端口分配给他。不管DatagramChannel是否绑定到了一个端口,任何一个包的发送都会包含它的地址,下面是DatagramChannel提供的发送和接收数据的方法:

SocketAddress receive (ByteBuffer dst) throws
IOException;
int send (ByteBuffer src, SocketAddress target)

DatagramChannel并不能保证数据能够发送到目的端,因为UDP协议本身就是不可靠的。
另外我们再看看DatagramChannel提供了以下下几个方法:
[code]
public abstract DatagramChannel connect (SocketAddress remote)
throws IOException;
public abstract boolean isConnected();
public abstract DatagramChannel disconnect() throws IOException;
[/code]
从名字看起来很让人迷惑,因为DatagramChannel就是基于无连接的,为什么还会有
connect之类的方法呢?
其实这个Connect的语义和基于流的Socket是不一样的,这里的连接只是制定了远程的
地址,这样就可以忽略其他地址发来的数据包了。一但使用完connect方法,就不能像其
他的地址send数据了。但这里的connect和SockectChannel的connect不同,它是可以
随时disconnect,然后去connect其他的地址的。但使用了connect方法之后,就可以
像FileChannel那样read,write数据,而不用指名地址了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值