转:使用非阻塞ServerSocketChannel、SocketChannel代替ServerSocket和Socket
在使用传统的ServerSocket和Socket的时候 很多时候程序是会阻塞的
比如 serversocket.accept() , socket.getInputStream().read()?的时候都会阻塞?accept()方法除非等到客户端socket的连接或者被异常中断 否则会一直等待下去
read()方法也是如此 除非在输入流中有了足够的数据 否则该方法也会一直等待下去知道数据的到来.在ServerSocket与Socket的方式中?服务器端往往要为每一个客户端(socket)分配一个线程,而每一个线程都有可能处于长时间的阻塞状态中.而过多的线程也会影响服务器的性能.在JDK1.4引入了非阻塞的通信方式,这样使得服务器端只需要一个线程就能处理所有客户端socket的请求.
下面是几个需要用到的核心类
- ServerSocketChannel: ServerSocket 的替代类, 支持阻塞通信与非阻塞通信.
- SocketChannel: Socket 的替代类, 支持阻塞通信与非阻塞通信.
- Selector: 为ServerSocketChannel 监控接收客户端连接就绪事件, 为 SocketChannel 监控连接服务器就绪, 读就绪和写就绪事件.
- SelectionKey: 代表 ServerSocketChannel 及 SocketChannel 向 Selector 注册事件的句柄. 当一个 SelectionKey 对象位于Selector 对象的 selected-keys 集合中时, 就表示与这个?SelectionKey 对象相关的事件发生了.在SelectionKey 类中有几个静态常量
- SelectionKey.OP_ACCEPT???????->客户端连接就绪事件?等于监听serversocket.accept()返回一个socket
- SelectionKey.OP_CONNECT???->准备连接服务器就绪??????????跟上面类似,只不过是对于socket的 相当于监听了 socket.connect()
- SelectionKey.OP_READ????????????->读就绪事件,??表示输入流中已经有了可读数据, 可以执行读操作了
- SelectionKey.OP_WRITE??????????->写就绪事件
?
下面是服务器端:
Selector selector = Selector.open();?????????//静态方法 实例化selector
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);??//设置为非阻塞方式,如果为true 那么就为传统的阻塞方式
serverChannel.socket().bind(new InetSocketAddress(port));??//绑定IP 及 端口
serverChannel.register(selector, SelectionKey.OP_ACCEPT);?//注册 OP_ACCEPT事件new ServerThread().start();?//开启一个线程 处理所有请求
ServerThread中的run方法
view plainprint?- public?void?run()??
- ??{??
- ???while(true)??
- ???{??
- ????try??
- ????{??
- ?????selector.select();??
- ?????Set<SelectionKey>?keys?=?selector.selectedKeys();??
- ?????Iterator<SelectionKey>?iter?=?keys.iterator();??
- ?????SocketChannel?sc?;??
- ?????while(iter.hasNext())??
- ?????{??
- ??????SelectionKey?key?=?iter.next();??
- ??????if(key.isAcceptable());??//?新的连接??
- ???????else?if(key.isReadable())?;//?可读???????
- ???????iter.remove();?//处理完事件的要从keys中删去????????
- ?????}??
- ????catch?(Exception?e)??
- ????{??
- ?????e.printStackTrace();??
- ????}??
- ???}??
- ??}??
其中在 isAcceptable()中 通过????????ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); SocketChannel ?sc = ssc.accept(); 得到客户端的SocketChannel
在isReadable()中SocketChannel ??sc = (SocketChannel) key.channel(); 得到SocketChannel .?
在SocketChannel 对象中可以用write() read() 进行读写操作 只不过操作的对象不再是byte[] String之类 而是ByteBuffer
?
客户端基本一样
??selector = Selector.open();
??channel = SocketChannel.open(new InetSocketAddress(port));
??channel.configureBlocking(false);
??channel.register(selector,SelectionKey.OP_CONNECT);
??new ClientThread().start();run方法
????while (true)
????{
?????selector.select();
?????Set<SelectionKey> keys = selector.selectedKeys();
?????Iterator<SelectionKey> iter = keys.iterator();
?????while(iter.hasNext())
?????{
??????SelectionKey key = iter.next();??????if(key.isConnectable());//连接成功&正常
??????else??if(key.isReadable())//可读
??????iter.remove();
????}
可以通过key.channel();方法得到当前的socketchannel对象
?
总结 其实这里将阻塞变为非阻塞实际是用一个while死循环来处理的
首先通过seleector.select()重新得到事件 只要有事件无论是什么 都交给循环体去处理 在循环体中分别进行不同的处理
而多个socket通过一个seleector进行同意管理
while(一直等待, 直到有接收连接就绪事件, 读就绪事件或写就绪事件发生){?????????????//阻塞
??????????????if(有客户连接)
???????????????????接收客户的连接;????????????????????????????????????????????????????//非阻塞
??????????????if(某个 Socket 的输入流中有可读数据)
???????????????????从输入流中读数据;?????????????????????????????????????????????????//非阻塞
??????????????if(某个 Socket 的输出流可以写数据)
???????????????????向输出流写数据;????????????????????????????????????????????????????//非阻塞
???????}
类似这样??以上处理流程采用了轮询的工作方式, 当某一种操作就绪时, 就执行该操作, 否则就查看是否还有其他就绪的操作可以执行. 线程不会因为某一个操作还没有就绪, 就进入阻塞状态, 一直傻傻地在那里等待这个操作就绪.?
?
?
http://blog.sina.com.cn/s/blog_81c2545a01011afh.html
?
SocketChannel使用心得博客分类:?
- Java基础知识1.服务器端while(true) 一直等待client端来连接。?
? 基本步骤为:?
Java代码?- ServerSocketChannel?server?=?ServerSocketChannel.open();??
- server.configureBlocking(false);??
- ??
- server.socket().bind(new?InetSocketAddress(5200));??
- ??
- Selector?select?=?Selector.open();??
- server.register(select,?SelectionKey.OP_ACCEPT);??
声明一个等待客户端的服务器。?
Java代码?- select.select();??
- ???????Set?readkeys?=?select.selectedKeys();??
- ???????Iterator?iterator?=?readkeys.iterator();??
- ???????while?(iterator.hasNext())?{??
- ???????????SelectionKey?key?=?(SelectionKey)?iterator.next();??
- ???????????if?(key.isAcceptable())?{??
- ???????????????SocketChannel?client?=?((ServerSocketChannel)?key.channel()).accept();??
- ???????????????System.out.println("Accept?connection?from:?"?+?client);??
- ???????????????client.configureBlocking(false);??
- ???????????????client.register(key.selector(),?SelectionKey.OP_READ,?ByteBuffer.allocate(1024));??
- ???????????}??
- ???????????if?(key.isReadable())?{??
- ??
- ???????????????//?获得与客户端通信的信道??
- ???????????????SocketChannel?clientChannel?=?(SocketChannel)?key.channel();??
- ??
- ???????????????//?得到并清空缓冲区??
- ???????????????ByteBuffer?buffer?=?(ByteBuffer)?key.attachment();??
- ???????????????buffer.clear();??
- ??
- ???????????????//?读取信息获得读取的字节数??
- ???????????????long?bytesRead?=?clientChannel.read(buffer);??
- ??
- ???????????????if?(bytesRead?==?-1)?{??
- ???????????????????//?没有读取到内容的情况??
- ???????????????????clientChannel.close();??
- ???????????????}?else?{??
- ???????????????????//?将缓冲区准备为数据传出状态??
- ???????????????????buffer.flip();??
- ??
- ???????????????????//?将字节转化为为UTF-16的字符串?????
- ???????????????????String?receivedString?=?Charset.forName("UTF-16").newDecoder().decode(buffer).toString();??
- ??
- ???????????????????//?控制台打印出来??
- ???????????????????System.out.println("接收到来自"?+?clientChannel.socket().getRemoteSocketAddress()?+?"的信息:"?+?receivedString);??
- ??
- ???????????????????//?准备发送的文本??
- ???????????????????String?sendString?=?"你好,客户端.?@"?+?new?Date().toString()?+?",已经收到你的信息"?+?receivedString;??
- ???????????????????buffer?=?ByteBuffer.wrap(sendString.getBytes("UTF-16"));??
- ???????????????????clientChannel.write(buffer);??
- ??
- ???????????????????//?设置为下一次读取或是写入做准备??
- ???????????????????key.interestOps(SelectionKey.OP_READ?|?SelectionKey.OP_WRITE);??
- ???????????????}??
- ???????????}??
- ???????????if?(key.isWritable())??
- ???????????{??
- ???????????????SocketChannel?sc?=?(SocketChannel)?key.channel();??
- ???????????????ByteBuffer?writeBuffer=ByteBuffer.wrap("我的程序员之道".getBytes("UTF-16"));??
- ???????????????sc.write(writeBuffer);??
- ???????????}??
- ???????????key.channel().close();??
- ???????}??
遍历键集,然后判断键的可读可写等做不同的操作?
客户端建立一个线程,等待服务器端的回应:?
1.建立基本的SocketChannel?
Java代码?- SocketChannel?sc?=?SocketChannel.open(new?InetSocketAddress("172.16.22.11",?5200));??
- ?//打开一个SocketChannel并连接到服务器??
- ?sc.configureBlocking(false);??
- ??
- ?Selector?selector?=?Selector.open();??
- ??
- ?sc.register(selector,?SelectionKey.OP_READ);??
在实现Runable的run方法里遍历键集,判断做不同的操作:?
Java代码?- while?(selector.select()?>?0)?{??
- ????????????????//?遍历每个有可用IO操作Channel对应的SelectionKey??
- ????????????????Iterator?it?=?selector.selectedKeys().iterator();??
- ????????????????while?(it.hasNext())?{??
- ????????????????????SelectionKey?sk?=?(SelectionKey)?it.next();??
- ????????????????????it.remove();??
- ????????????????????//?如果该SelectionKey对应的Channel中有可读的数据??
- ????????????????????if?(sk.isReadable())?{??
- ????????????????????????//?使用NIO读取Channel中的数据??
- ????????????????????????SocketChannel?socketChannel?=?(SocketChannel)?sk.channel();??
- ??
- ????????????????????????ByteBuffer?buffer?=?ByteBuffer.allocate(1024);??
- ????????????????????????socketChannel.read(buffer);??
- ????????????????????????buffer.flip();??
- ??
- ????????????????????????//?将字节转化为为UTF-16的字符串?????
- ????????????????????????String?receivedString?=?Charset.forName("UTF-16").newDecoder().decode(buffer).toString();??
- ??
- ????????????????????????//?控制台打印出来??
- ????????????????????????System.out.println("接收到来自服务器"?+?socketChannel.socket().getRemoteSocketAddress()?+?"的信息:"?+?receivedString);??
- ??
- ????????????????????????//?为下一次读取作准备??
- //????????????????????????sk.interestOps(SelectionKey.OP_READ);//将键设为可读??
- ??
- ????????????????????}??
- ????????????????????if?(sk.isWritable())?{??
- ????????????????????????SocketChannel?socketChannel?=?(SocketChannel)?sk.channel();??
- ????????????????????????ByteBuffer?writeBuffer?=?ByteBuffer.wrap("我的程序员之道".getBytes("UTF-16"));??
- ????????????????????????socketChannel.write(writeBuffer);??
- ????????????????????}??
- ??
- ????????????????????//?删除正在处理的SelectionKey??
- ????????????????????selector.selectedKeys().remove(sk);??
- ????????????????}??
- ????????????}??
- ????????}?catch?(IOException?ex)?{??
- ????????????ex.printStackTrace();??
- ????????} ?
?
?