NIO和Netty全文
Channel
- 通道,通过它读取和写入数据
- 双向,全双工,同时用于读写,可以异步读写
流,单向
- 多种数据源,文件、网络socket
- 用于在字节缓冲区和通道另一侧的实体之间有效地传输数据
- 流程:写入,从通道中读取数据到缓冲区;读取,从缓冲区写入数据到通道
- 重要的实现
- FileChannel从文件中
- DataGramChannel 通过UDP
- SocketChannel 通过TCP
- ServerSocketChannel 监听新进来TCP连接,类似服务器,当接收到一个链接时,创建一个
SocketChannel
FileChannel
读取文件进buffer
//访问文件的部分内容,而不是把文件从头读到尾
//可以直接跳转到文件的任意地方来读写数据
RandomAccessFile af = new RandomAccessFile("/Users/mzx/Desktop/java/nio/test1.txt","rw");
//得到fc
FileChannel fc = af.getChannel();
ByteBuffer buf = ByteBuffer.allocate(1024);
//往buf里面读,文件中的字节长度
int readByte = fc.read(buf);
while (readByte != -1) {
System.out.println("从文件里读取了 - " + readByte);
//从buffer中再获取数据
buf.flip();
while(buf.hasRemaining()) {
System.out.println("buf里面有 - " + (char)buf.get());
}
buf.clear();
readByte = fc.read(buf);
}
//close...
- 另一个案例
File file = new File("/Users/mzx/Desktop/java/netty/test.txt");
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteBuffer bb = ByteBuffer.allocate((int) file.length());
int read = fc.read(bb);
while (read != -1) {
bb.flip();
System.out.println(new String(bb.array(), 0, read));
read = fc.read(bb);
}
fc.close();
fis.close();
获取fc
- FileInputStream
FileInputStream fis = new FileInputStream("/Users/mzx/Desktop/java/nio/test1.txt");
FileChannel channel = fis.getChannel();
- RandomAccessFile
RandomAccessFile af = new RandomAccessFile("/Users/mzx/Desktop/java/nio/test1.txt","rw");
//得到fc
FileChannel fc = af.getChannel();
往文件中写数据
RandomAccessFile rf = new RandomAccessFile("/Users/mzx/Desktop/java/nio/test1.txt", "rw");
FileChannel fc = rf.getChannel();
ByteBuffer bf = ByteBuffer.allocate(1024);
String s = "test33333333";
bf.clear();
//写入,将内容放入buffer中
bf.put(s.getBytes());
//读写转换
bf.flip();
//从buffer中读取数据,写入到文件中
while(bf.hasRemaining()) {
//要循环写入,直到写完
fc.write(bf);
}
fc.close();
rf.close();
拷贝文件
//项目目录下
FileInputStream fis = new FileInputStream("test.txt");
FileChannel fic = fis.getChannel();
FileOutputStream fos = new FileOutputStream("test2.txt");
FileChannel foc = fos.getChannel();
ByteBuffer bb = ByteBuffer.allocate(2);
int read = fic.read(bb);
while (read != -1) {
//读完之后,反转
bb.flip();
foc.write(bb);
//写完之后,清空
//此时position与limit相等,如果不清空,会循环读到0,
bb.clear();
read = fic.read(bb);
}
fic.close();foc.close();fis.close();fos.close();
通道间数据传输
- position
- 获取指向文件的当前位置,或者指定当前指向文件的位置
- 如果将位置设置在文件结束符之后,会撑大文件,造成文件空洞,磁盘上物理文件中写入的数据有空隙
//获取fromFc当前在文件中的位置
long position = fromFc.position();
//指定位置
fromFc.position(position+3);
- transferFrom
RandomAccessFile rf1 = new RandomAccessFile("/Users/mzx/Desktop/java/nio/test1.txt", "rw");
FileChannel fromFc = rf1.getChannel();
RandomAccessFile rf2 = new RandomAccessFile("/Users/mzx/Desktop/java/nio/test2.txt", "rw");
FileChannel toFc = rf2.getChannel();
//from 传输到 to中
//文件大小
long size = fromFc.size();
//将fromFc中的内容,复制到toFc中,从7开始,size大小
toFc.transferFrom(fromFc, 7, size);
//close...
- transferTo
RandomAccessFile rf1 = new RandomAccessFile("/Users/mzx/Desktop/java/nio/test1.txt", "rw");
FileChannel fromFc = rf1.getChannel();
RandomAccessFile rf2 = new RandomAccessFile("/Users/mzx/Desktop/java/nio/test3.txt", "rw");
FileChannel toFc = rf2.getChannel();
//from 传输到 to中
//指定位置
fromFc.position(position+3);
//文件大小
long size = fromFc.size();
//将fromF中,从起始位置,size大小的内容,传输到toFc中
fromFc.transferTo(fromFc.position(), size, toFc);
//close...
Socket通道
- 可以设置非阻塞模式,具有伸缩性和灵活性
- 一个线程可以管理多个socket连接
ServerSocketChannle负责监听传入的连接和创建新的SocketChannel对象,内部没有实现读和写的接口,不传输数据socket通道可以被重复操作使用- 非阻塞模式适用于服务端;客户端也可以使用,可以用于维护维护一个用户请求和多个服务器的回话
ServerSocketChannel
- 只是一个监听器
int port = 8888;
ByteBuffer bf = ByteBuffer.wrap("ServerSocketChannel".getBytes());
//打开
ServerSocketChannel ssc = ServerSocketChannel.open();
//绑定
ssc.socket().bind(new InetSocketAddress(port));
//设置非阻塞模式
ssc.configureBlocking(false);
//监听是否有新的连接
while(true) {
System.out.println("waiting...");
//阻塞模式运行,没有连接,会一直阻塞等待
SocketChannel sc = ssc.accept();
if (sc == null) { //没有新的连接传入
System.out.println(sc);
Thread.sleep(2000);
} else {
System.out.println("Connection from: " + sc.socket().getRemoteSocketAddress());
bf.rewind(); //指针指向最开始
sc.write(bf);
sc.close();
}
}
SocketChannle
- 适用于TCP连接的网络套接字的通道,实现了可选择通道,可以被多路复用
//创建
//SocketChannel sc = SocketChannel.open(new InetSocketAddress("www.baidu.com", 80));
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("www.baidu.com", 80));
System.out.println(sc.isOpen()); //sc是否打开
System.out.println(sc.isConnected()); //是否被连接
System.out.println(sc.isConnectionPending()); //是否正在进行连接
System.out.println(sc.finishConnect()); //正在进行套接字连接的sc是否已经完成连接
//设置阻塞模式
sc.configureBlocking(false);
//读
ByteBuffer bf = ByteBuffer.allocate(1024);
sc.read(bf);
//设置参数
sc.setOption(StandardSocketOptions.SO_KEEPALIVE, Boolean.TRUE);
System.out.println(sc.getOption(StandardSocketOptions.SO_KEEPALIVE));
sc.close();
DatagramChannel
- 关联
DatagramSocket对象,每一个数据报对象都是一个自包含的实体,拥有他自己的目的地址及不依赖其他数据报的数据负载 - 面向UDP连接,包导向无连接协议
- 可以将数据包发送到任意地址,也可以从任意地址获取数据包
@Test
public void sendDatagram() throws Exception {
//发送包
DatagramChannel sendDc = DatagramChannel.open();
InetSocketAddress sendAdd = new InetSocketAddress("localhost", 9999);
//发送
while (true) {
ByteBuffer bf = ByteBuffer.wrap("发送sendDatagramChannel".getBytes("UTF-8"));
//数据和地址
sendDc.send(bf, sendAdd);
System.out.println("send-finished");
Thread.sleep(1000);
}
}
@Test
public void receiveDatagram() throws Exception {
//接收包
DatagramChannel receiveDc = DatagramChannel.open();
//绑定
receiveDc.bind(new InetSocketAddress(9999));
//接收
ByteBuffer bf = ByteBuffer.allocate(1024);
while (true) {
bf.clear();
SocketAddress sa = receiveDc.receive(bf);
bf.flip();
System.out.println(sa.toString());
System.out.println(Charset.forName("UTF-8").decode(bf));
}
}
- 连接
public void testCon() throws Exception {
DatagramChannel conDc = DatagramChannel.open();
conDc.bind(new InetSocketAddress(9999));
//创建连接,9999向8888发数据
conDc.connect(new InetSocketAddress("127.0.0.1", 8888));
//write
conDc.write(ByteBuffer.wrap("发送testCon".getBytes("UTF-8")));
//read
ByteBuffer readBf = ByteBuffer.allocate(1024);
while (true) {
readBf.clear();
conDc.read(readBf);
readBf.flip();
System.out.println(Charset.forName("UTF-8").decode(readBf));
}
}
- 8888向9999写
@Test
public void receiveDatagram() throws Exception {
//接收包
DatagramChannel receiveDc = DatagramChannel.open();
//绑定
receiveDc.bind(new InetSocketAddress(8888));
receiveDc.connect(new InetSocketAddress("127.0.0.1", 9999));
while (true) {
receiveDc.write(ByteBuffer.wrap("receiveDatagram".getBytes("UTF-8")));
System.out.println("send-finished");
}
}
@Test
public void testCon() throws Exception {
DatagramChannel conDc = DatagramChannel.open();
conDc.bind(new InetSocketAddress(9999));
conDc.connect(new InetSocketAddress("127.0.0.1", 8888));
ByteBuffer readBf = ByteBuffer.allocate(1024);
while (true) {
readBf.clear();
conDc.read(readBf);
readBf.flip();
System.out.println(Charset.forName("UTF-8").decode(readBf));
}
}
分散和聚集
- 针对Channel中读取和写入的操作
- 分散,scatter,从通道中读取数据后,将数据写入到多个buffer中
读取时,是依次填满的
- 聚集,gather,往通道中写数据时,从多个buffer获取
- 传输由消息头和消息体组成的消息,可以将消息头和消息体分散到不同的buffer中
- UDP实现
@Test
public void writeDatagram() throws Exception {
DatagramChannel writeDc = DatagramChannel.open();
//绑定
writeDc.bind(new InetSocketAddress(8888));
writeDc.connect(new InetSocketAddress("127.0.0.1", 9999));
ByteBuffer header = ByteBuffer.wrap("I am header".getBytes("UTF-8"));
ByteBuffer body = ByteBuffer.wrap("I am Body".getBytes("UTF-8"));
ByteBuffer[] bfArr = {header, body};
while (true) {
long len = writeDc.write(bfArr);
System.out.println("send finished - " + len);
Thread.sleep(1000);
header = ByteBuffer.wrap("I am header".getBytes());
body = ByteBuffer.wrap("I am Body".getBytes());
bfArr = new ByteBuffer[]{header, body};
}
}
@Test
public void readDatagram() throws Exception {
DatagramChannel readDc = DatagramChannel.open();
readDc.bind(new InetSocketAddress(9999));
//创建连接,9999向8888发数据
readDc.connect(new InetSocketAddress("127.0.0.1", 8888));
ByteBuffer header = ByteBuffer.allocate(10);
ByteBuffer body = ByteBuffer.allocate(20);
ByteBuffer[] bfArr = {header, body};
while (true) {
for (ByteBuffer bf : bfArr) {
bf.clear();
}
long len = readDc.read(bfArr);
System.out.println("read - " + len);
for (ByteBuffer bf : bfArr) {
bf.flip();
System.out.println(Charset.forName("UTF-8").decode(bf));
}
}
}