1、Socket通道介绍java
Socket通道即套接字通道,是一种基于TCP链接协议传输数据的通道。Socket通道能够以分阻塞模式运行,而且能够被Reactor设计模式使用,由于Socket通道是可供选择的,其继承SelectableChannel类。设计模式
非阻塞I/O是复杂的、高性能的程序构建的基础。数组
使用NIO的Socket通道就没有必须像旧的Socket同样为每一个Socket建立一个线程,避免了N个线程之间切换上下文的性能开销,使用NIO的Socket通道能够一个或几个线程就能够管理成百上千个已链接的Socket,而且性能消耗很是小。缓存
1. ServerSocketChannel:负责监听某个端口,等待客户端Socket链接进来服务器
2. SocketChannel:能够理解为一对一对使用,客户端使用SocketChannel链接服务器,服务器也会有个对等的SocketChannel网络
3. DatagramChannel:基于UDP发送数据报socket
全部的Socket通道类被建立后都有一个关联的Socket;可是使用旧的方法直接建立Socket并不会关联一个通道,虽然它们有getChannel()方法,可是这个方法会返回null。性能
2、ServerSocketChannel.net
ServerSocketChannel有以下方法可供使用:线程
1. open():打开一个ServerSocketChannel,用于建立ServerSocketChannel
2. validOps():返回SelectionKey.OP_ACCEPT,通常用不到,可用于验证注册到选择器时指定的兴趣是否位OP_ACCEPT
3. socket():返回与通道关联的ServerSocket
4. accept():等待接受一个Socket链接,非阻塞模式下可能返回null
//打开一个ServerSocketChannel ServerSocketChannel ssc = ServerSocketChannel.open(); //将通道关联的ServerSocket绑定65535端口 //jdk1.7以后能够ssc.bind(new InetSocketAddress(65535)) ssc.socket().bind(new InetSocketAddress(65535)); //设置非阻塞模式 ssc.configureBlocking(false);
3、SocketChannel
SocketChannel有以下方法可供使用:
open():打开一个SocketChannel open(SocketAddress remote):同open(),并指定链接服务器地址 validOps():返回SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT,做用同ServerSocketChannel的validOps() socket():返回通道关联的Socket isConnected():检查是否已链接,返回true or false isConnectionPending():检查是否处于请求链接状态 connect(SocketAddress remote):链接指定地址服务器 finishConnect():强制完成链接,完成链接则返回true,返回false read(ByteBuffer dst):将通道的数据读到ByteBuffer read(ByteBuffer[] dsts):将通道数据分散读到ByteBuffer数组 read(ByteBuffer[] dsts, int offset, int length):将通道数据分散读到ByteBuffer数组,并指定读取数据的开始位置和读取的长度 write(ByteBuffer src):将字节缓冲区的数据写入通道 write(ByteBuffer[] srcs):将分散的字节缓冲区的数据汇集写入通道 write(ByteBuffer[] srcs, int offset, int length):同上一个方法,并指定写入数据的开始位置和长度 SocketChannel sc = SocketChannel.open(); sc.configureBlocking(false); sc.connect(new InetSocketAddress("127.0.0.1", 65535)); //轮询直到成功链接为止 while(!sc.isConnected()){ }
4、DatagramChannel
open():打开一个DatagramChannel validOps():返回SelectionKey.OP_READ | SelectionKey.OP_WRITE socket():返回关联的DatagramSocket isConnected():是否已链接 connect(SocketAddress remote)》:链接某个地址 disconnect():断开链接 receive(ByteBuffer dst):接收数据,从通道读入缓冲区 send(ByteBuffer src, SocketAddress target):将缓冲区的数据发送到指定网络地址 read(ByteBuffer dst) read(ByteBuffer[] dsts) read(ByteBuffer[] dsts, int offset, int length) write(ByteBuffer src) write(ByteBuffer[] srcs) write(ByteBuffer[] srcs, int offset, int length)
这么须要说明下,DatagramChannel提供了connect方法,别被这样的方法套住了,DatagramSocket是基于UDP的,UDP是无链接的,connection方法只是绑定某个地址,只能从这个地址接收数据或发送数据到这个地址。
DEMO:
Java简单版飞鸽传书:http://download.csdn.net/detail/abc_key/7497543
Java简单聊天室:http://download.csdn.net/detail/abc_key/7459723
Java NIO聊天室 中:若客户端强制关闭,服务器会报“java.io.IOException: 远程主机强迫关闭了一个现有的链接。而且服务器会在报错后中止运行,错误的意思就是客户端关闭了,可是服务器还在从这个套接字通道读取数据,便抛出IOException,致使这种状况出现的缘由就是,客户端异常关闭后,服务器的选择器会获取到与客户端套接字对应的套接字通道SelectionKey,而且这个key的兴趣是OP_READ,执行从这个通道读取数据时,客户端已套接字已关闭,因此会出现“java.io.IOException: 远程主机强迫关闭了一个现有的链接”的错误。解决这种问题也很简单,就是服务器在读取数据时,若发生异常,则取消当前key并关闭通道,以下代码:
//获取此key对应的套接字通道 SocketChannel channel = (SocketChannel) key.channel(); //建立一个大小为1024k的缓存区 ByteBuffer buffer = ByteBuffer.allocate(1024); StringBuffer sb = new StringBuffer(); //将通道的数据读到缓存区 int count = 0; try{ count = channel.read(buffer); }catch(IOException e){ key.cancel(); channel.socket().close(); channel.close(); return; }
因在catch中取消了key,readMsg返回后,run方法继续往下走,以前的代码会报“java.nio.channels.CancelledKeyException”错误,因此须要判断当前key是否有效,
以前的代码:
//若此key的通道的行为是"读" if (key.isReadable()) { readMsg(key); } //若次key的通道的行为是"写" if (key.isWritable()) { writeMsg(key); }
修复后的代码:
//若此key的通道的行为是"读" if (key.isValid() && key.isReadable()) { readMsg(key); } //若次key的通道的行为是"写" if (key.isValid() && key.isWritable()) { writeMsg(key); }
这样改良以后的聊天室服务端,客户端异常强制关闭后,服务器便会妥善处理了,也不会报错,更不会中止运行。改良以后还算比较稳定运行的。