ByteBuffer:
ByteBuffer类是在Java NIO中常常使用的一个缓冲区类,使用它可以进行高效的IO操作.
使用缓冲区有这么两个好处:
1、减少实际的物理读写次数
2、缓冲区在创建时就被分配内存,这块内存区域一直被重用,可以减少动态分配和回收内存的次数
创建ByteBuffer的方法:
使用allocate()静态方法
ByteBuffer buffer=ByteBuffer.allocate(256);
以上方法将创建一个容量为256字节的ByteBuffer,如果发现创建的缓冲区容量太小,唯一的选择就是重新创建一个大小合适的缓冲区.
通过包装一个已有的数组来创建
如下,通过包装的方法创建的缓冲区保留了被包装数组内保存的数据.
ByteBuffer buffer=ByteBuffer.wrap(byteArray);
如果要将一个字符串存入ByteBuffer,可以如下操作:
String sendString="你好,服务器. ";
ByteBuffer sendBuffer=ByteBuffer.wrap(sendString.getBytes("UTF-16"));
回绕缓冲区
buffer.flip();
这个方法用来将缓冲区准备为数据传出状态,执行以上方法后,输出通道会从数据的开头而不是末尾开始.回绕保持缓冲区中的数据不变,只是准备写入而不是读取.
清除缓冲区
buffer.clear();
这个方法实际上也不会改变缓冲区的数据,而只是简单的重置了缓冲区的主要索引值.不必为了每次读写都创建新的缓冲区,那样做会降低性能.相反,要重用现在的缓冲区,在再次读取之前要清除缓冲区.
从套接字通道(信道)读取数据
int bytesReaded=socketChannel.read(buffer);
执行以上方法后,通道会从socket读取的数据填充此缓冲区,它返回成功读取并存储在缓冲区的字节数.在默认情况下,这至少会读取一个字节,或者返回-1指示数据结束.
向套接字通道(信道)写入数据
socketChannel.write(buffer);
此方法以一个ByteBuffer为参数,试图将该缓冲区中剩余的字节写入信道.
ByteBuffer俗称缓冲器, 是将数据移进移出通道的唯一方式,并且我们只能创建一个独立的基本类型缓冲器,或者使用“as”方法从 ByteBuffer 中获得。ByteBuffer 中存放的是字节,如果要将它们转换成字符串则需要使用 Charset , Charset 是字符编码,它提供了把字节流转换成字符串 ( 解码 ) 和将字符串转换成字节流 ( 编码) 的方法。
private byte[] getBytes (char[] chars) {//将字符转为字节(编码)
Charset cs = Charset.forName ("UTF-8");
CharBuffer cb = CharBuffer.allocate (chars.length);
cb.put (chars);
cb.flip ();
ByteBuffer bb = cs.encode (cb)
return bb.array();
}
private char[] getChars (byte[] bytes) {//将字节转为字符(解码)
Charset cs = Charset.forName ("UTF-8");
ByteBuffer bb = ByteBuffer.allocate (bytes.length);
bb.put (bytes);
bb.flip ();
CharBuffer cb = cs.decode (bb);
return cb.array();
}
//主要通过读取文件内容,写到ByteBuffer里,然后再从ByteBuffer对象中获取数据,显示到控制台
public static void readFile(String fileName) {
try {
RandomAccessFile randomAccessFile = new RandomAccessFile(fileName, "rw");
FileChannel fileChannel = randomAccessFile.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
int size = fileChannel.read(byteBuffer);
while(size>0){
//把ByteBuffer从写模式,转变成读取模式
byteBuffer.flip();
Charset charset = Charset.forName("UTF-8");
System.out.println(charset.newDecoder().decode(byteBuffer).toString());
byteBuffer.clear();
size = fileChannel.read(byteBuffer);
}
fileChannel.close();
randomAccessFile.close();
} catch (Exception e) {
e.printStackTrace();
}
}
SocketChannel:
SocketChannel是一种面向流连接只sockets套接字的可选择通道
- SocketChannel是用来连接Socket套接字
- SocketChannel主要用途用来处理网络I/O的通道
- SocketChannel是基于TCP连接传输
- SocketChannel实现了可选择通道,可以被多路复用的
SocketChannel具有以下的特征:
- 对于已经存在的socket不能创建SocketChannel
- SocketChannel中提供的open接口创建的Channel并没有进行网络级联,需要使用connect接口连接到指定地址
- 未进行连接的SocketChannle执行I/O操作时,会抛出
NotYetConnectedException
- SocketChannel支持两种I/O模式:阻塞式和非阻塞式
- SocketChannel支持异步关闭。如果SocketChannel在一个线程上read阻塞,另一个线程对该SocketChannel调用shutdownInput,则读阻塞的线程将返回-1表示没有读取任何数据;如果SocketChannel在一个线程上write阻塞,另一个线程对该SocketChannel调用shutdownWrite,则写阻塞的线程将抛出
AsynchronousCloseException
- SocketChannel支持设定参数
参数名 | 作用描述 |
---|---|
SO_SNDBUF | 套接字发送缓冲区大小 |
SO_RCVBUF | 套接字接收缓冲区大小 |
SO_KEEPALIVE | 保活连接 |
O_REUSEADDR | 复用地址 |
SO_LINGER | 有数据传输时延缓关闭Channel (只有在非阻塞模式下有用) |
TCP_NODELAY | 禁用Nagle算法 |
创建SocketChannel
//方式1.
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("www.baidu.com", 80));
//方式2.
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("www.baidu.com", 80));
连接校验
socketChannel.isOpen(); // 测试SocketChannel是否为open状态
socketChannel.isConnected(); //测试SocketChannel是否已经被连接
socketChannel.isConnectionPending(); //测试SocketChannel是否正在进行连接
socketChannel.finishConnect(); //校验正在进行套接字连接的SocketChannel是否已经完成连接
阻塞和非阻塞两种模式
socketChannel.configureBlocking(false);//false表示非阻塞,true表示阻塞。
读写
//以下为阻塞式读,当执行到read出,线程将阻塞,控制台将无法打印test end!。
SocketChannel socketChannel = SocketChannel.open(
new InetSocketAddress("www.baidu.com", 80));
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
socketChannel.read(byteBuffer);
socketChannel.close();
System.out.println("test end!");
//以下为非阻塞读,控制台将打印test end!。
SocketChannel socketChannel = SocketChannel.open(
new InetSocketAddress("www.baidu.com", 80));
socketChannel.configureBlocking(false);
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
socketChannel.read(byteBuffer);
socketChannel.close();
System.out.println("test end!");
设置和获取参数
//通过setOptions方法可以设置socket套接字的相关参数
socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, Boolean.TRUE)
.setOption(StandardSocketOptions.TCP_NODELAY, Boolean.TRUE);
//可以通过getOption获取相关参数的值。如默认的接收缓冲区大小是8192byte。
socketChannel.getOption(StandardSocketOptions.SO_KEEPALIVE);
socketChannel.getOption(StandardSocketOptions.SO_RCVBUF);
public static void startClient()throws Exception{
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 8999));
// socketChannel.configureBlocking(false);
String request = "hello mmmmm";
ByteBuffer buf = ByteBuffer.wrap(request.getBytes("UTF-8"));
socketChannel.write(buf);
ByteBuffer rbuf = ByteBuffer.allocate(48);
int size = socketChannel.read(rbuf);
while(size>0){
rbuf.flip();
Charset charset = Charset.forName("UTF-8");
System.out.println(charset.newDecoder().decode(rbuf));
rbuf.clear();
size = socketChannel.read(rbuf);
}
buf.clear();
rbuf.clear();
socketChannel.close();
Thread.sleep(50000);
}
ServerSocketChannel:
Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样。
打开 ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
监听新进来的连接(阻塞式)
//通过 ServerSocketChannel.accept() 方法监听新进来的连接。当 accept()方法返回的时候,它返回一个包含新进来的连接的 SocketChannel。因此, accept()方法会一直阻塞到有新连接到达。
while(true){
SocketChannel socketChannel =
serverSocketChannel.accept();
//do something with socketChannel...
}
监听新进来的连接(非阻塞式)
serverSocketChannel.configureBlocking(false);
while(true){
SocketChannel socketChannel =
serverSocketChannel.accept();
if(socketChannel != null){
//do something with socketChannel...
}
}
Selector:
Selector(选择器)是Java NIO中能够检测一到多个NIO渠道,并能够知晓渠道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。
创建Selector
Selector selector = Selector.open();
向selector注册channel,和感兴趣的事件
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8999));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
注意register()方法的第二个参数, 这是一个interest集合,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听以下4中不同类型的事件:
1,服务端接收客户端连接事件 SelectionKey.OP_ACCEPT
2,客户端连接服务端事件 SelectionKey.OP_CONNECT
3,读事件 SelectionKey.OP_READ
4,写事件 SelectionKey.OP_WRITE
如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来如下:
SelectionKey key = channel.register(selector,Selectionkey.OP_READ|SelectionKey.OP_WRITE);
//当向Selector注册Channel时,registor()方法会返回一个SelectorKey对象。
通过Selector选择通道
一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)已经准备就绪的那些通道。换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。
一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道。完整代码如下。
public static void startServer() throws Exception{
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8999));
Selector selector = Selector.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while(true){
//每一个连接,只会select一次
int select = selector.select();
//是否有可用的通道已接入
if(select>0){
for(SelectionKey key :selector.selectedKeys()){
if(key.isAcceptable()){
SocketChannel socketChannel = ((ServerSocketChannel)key.channel()).accept();
ByteBuffer buf = ByteBuffer.allocate(40);
int size = socketChannel.read(buf);
while(size>0){
buf.flip();
Charset charset = Charset.forName("UTF-8");
System.out.print(charset.newDecoder().decode(buf).toString());
size = socketChannel.read(buf);
}
buf.clear();
ByteBuffer response = ByteBuffer.wrap("hi,已经收到了请求!".getBytes("UTF-8"));
socketChannel.write(response);
socketChannel.close();
selector.selectedKeys().remove(key);
}
}
}
}
}
public static void startClient()throws Exception{
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost",8999));
//主要是设置成非阻塞
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_READ);
new ClientThread(selector).start();
ByteBuffer byteBuffer = ByteBuffer.wrap("hello 我是客户端".getBytes("UTF-8"));
socketChannel.write(byteBuffer);
byteBuffer.clear();
}
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
public class ClientThread extends Thread{
private Selector selector;
public ClientThread(Selector selector) {
super();
this.selector = selector;
}
@Override
public void run() {
try {
while(selector.select()>0){
for(SelectionKey key : selector.selectedKeys()){
SocketChannel socketChannel= (SocketChannel)key.channel();
ByteBuffer buf = ByteBuffer.allocate(40);
int size = socketChannel.read(buf);
while(size>0){
buf.flip();
Charset charset = Charset.forName("UTF-8");
System.out.print(charset.newDecoder().decode(buf).toString());
size = socketChannel.read(buf);
}
selector.selectedKeys().remove(key);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}