文章目录
一、Netty介绍和应用场景
Netty的介绍
同步与异步
Netty的应用场景
https://netty.io/wiki/related-projects.html
二、Java BIO编程
I/O模型
Java BIO的基本介绍
Java BIO应用实例
public class BIOServer {
public static void main(String[] args) throws IOException {
//创建线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
//创建ServerSocket
ServerSocket serverSocket = new ServerSocket(6666);
System.out.println("服务器启动完成...");
while (true) {
//监听,等待客户端连接
System.out.println("等待连接...");
final Socket socket = serverSocket.accept();
System.out.println("已连接上客户端...");
//创建一个线程与客户端进行通信
threadPool.execute(new Runnable() {
@Override
public void run() {
handler(socket);
}
});
}
}
//与客户端通信
public static void handler(Socket socket){
try{
System.out.println("线程信息:id=" + Thread.currentThread().getId()
+ " name=" + Thread.currentThread().getName());
byte[] bytes = new byte[1024];
//通过socket获取输入流
InputStream inputStream = socket.getInputStream();
//循环读取客户端发送的数据
while (true){
System.out.println("线程信息:id=" + Thread.currentThread().getId()
+ " name=" + Thread.currentThread().getName());
System.out.println("read...");
int read = inputStream.read(bytes);
if(read != -1){
//输出读取的数据
System.out.print(new String(bytes, 0, read));
}else{
break;
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
System.out.println("关闭client的连接");
socket.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
三、Java NIO编程
Java NIO的基本介绍
案例说明NIO的Buffer
//Buffer的使用
public class BasicBuffer {
public static void main(String[] args) {
//创建一个intBuffer,大小为5,即存放五个int
IntBuffer intBuffer = IntBuffer.allocate(5);
//向buffer里存放数据
for (int i = 0; i < intBuffer.capacity(); i++) {
intBuffer.put(i * 2);
}
//从buffer中读取数据
//首先进行读写切换
intBuffer.flip();
//然后循环读取
while (intBuffer.hasRemaining()){
System.out.println(intBuffer.get());
}
}
}
NIO三大核心原理示意图
缓冲区(Buffer)
基本介绍
Buffer类及子类
Buffer类相关方法一览
ByteBuffer
通道(Channel)
基本介绍
FileChannel类
在channel的角度,read是写到buffer,write是写入channel。
应用实例
例1
public class NIOFileChannel01 {
public static void main(String[] args) throws Exception {
String str = "hello";
//创建一个输出流 -> channel
FileOutputStream fileOutputStream = new FileOutputStream("./file01.txt");
//通过 fileOutputStream 获取对应的 filechannel
//fileChannel的真实类型是FileChannelImpl,因为FileChannel是一个抽象类
FileChannel fileChannel = fileOutputStream.getChannel();
//创建一个缓冲区ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//将str放入byteBuffer
byteBuffer.put(str.getBytes());
//切换为读模式
byteBuffer.flip();
//将byteBuffer里的数据写入到fileChannel
fileChannel.write(byteBuffer);
//关闭资源
fileOutputStream.close();
}
}
例2
public class NIOFileChannel02 {
public static void main(String[] args) throws Exception {
//创建文件的输入流
File file = new File("./file01.txt");
FileInputStream fileInputStream = new FileInputStream(file);
//通过 fileInputStream 获取对应的FileChannel(实际的类型是FileChannelImpl)
FileChannel fileChannel = fileInputStream.getChannel();
//创建缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate((int)file.length());
//将fileChannel的数据读到byteBuffer中
fileChannel.read(byteBuffer);
//切换为读模式
//不进行flip也可以,因为下面直接把整个byteBuffer的数据转为string
//byteBuffer.flip();
//将byteBuffer的字节数据转为string并打印
System.out.println(new String(byteBuffer.array()));
//关闭资源
fileInputStream.close();
}
}
例3
public class NIOFileChannel03 {
public static void main(String[] args) throws Exception {
//文件输入流
FileInputStream fileInputStream = new FileInputStream("./1.txt");
FileChannel fileChannel01 = fileInputStream.getChannel();
//文件输出流
FileOutputStream fileOutputStream = new FileOutputStream("./2.txt");
FileChannel fileChannel02 = fileOutputStream.getChannel();
//缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(5);
while (true){
//返回读取的数据数,-1代表读取完毕
int read = fileChannel01.read(byteBuffer);
if(read == -1){
break;
}
//切换为读模式
byteBuffer.flip();
//将数据写入到fileChannel02
fileChannel02.write(byteBuffer);
//清空byteBuffer
byteBuffer.clear();
/*
(其实里面的数据没有删除,这是只是把属性初始化,下次写入覆盖掉原来的数据,没覆盖前是可以读到原来的数据的)
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
*/
}
//关闭资源
fileOutputStream.close();
fileInputStream.close();
}
}
例4
public class NIOFileChannel04 {
public static void main(String[] args) throws Exception {
//创建流
FileInputStream fileInputStream = new FileInputStream("1.jpg");
FileOutputStream fileOutputStream = new FileOutputStream("2.jpg");
//获取filechannel
FileChannel sourceCh = fileInputStream.getChannel();
FileChannel destCh = fileOutputStream.getChannel();
//使用transferFrom完成拷贝 sourceCh -> destCh
destCh.transferFrom(sourceCh, 0, sourceCh.size());
//使用transferTo完成拷贝 sourceCh -> destCh
// sourceCh.transferTo(0, sourceCh.size(), destCh);
//transferFrom与transferTo相同,只是使用的对象不一样
//关闭资源
destCh.close();
sourceCh.close();
fileOutputStream.close();
fileInputStream.close();
}
}
关于Buffer和Channel的注意事项和细节
public class NIOByteBufferPutGet {
public static void main(String[] args) {
//创建一个byteBuffer
ByteBuffer buffer = ByteBuffer.allocate(64);
//类型化方式放入
buffer.putInt(100);
//取出
buffer.flip();
// System.out.println(buffer.getInt());
System.out.println(buffer.getLong());
}
}
public class ReadOnlyBuffer {
public static void main(String[] args) {
//创建byteBuffer
ByteBuffer buffer = ByteBuffer.allocate(64);
//写入
for (int i = 0; i < 64; i++) {
buffer.put((byte) i);
}
//切换读模式
buffer.flip();
//得到一个只读buffer
ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
System.out.println(readOnlyBuffer.getClass()); //class java.nio.HeapByteBufferR
//读取
while (readOnlyBuffer.hasRemaining()){
System.out.println(readOnlyBuffer.get());
}
//往readOnlyBuffer中写入,报错:ReadOnlyBufferException
readOnlyBuffer.put((byte) 1);
}
}
//MappedByteBuffer的好处:可让文件直接在内存(堆外内存)修改,操作系统不需要去拷贝一次
public class MappedByteBufferTest {
public static void main(String[] args) throws Exception {
//随机存取文件
RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");
//获取通道
FileChannel channel = randomAccessFile.getChannel();
/*
参数1:FileChannel.MapMode.READ_WRITE 读写模式
参数2:可以直接修改的起始位置
参数3:映射内存的大小(不是索引位置),即将1.txt的多少个字节映射到内存中
即:可以直接修改的范围是 0到4
因为MappedByteBuffer为抽象类,mappedByteBuffer实际类型为DirectByteBuffer
*/
MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
mappedByteBuffer.put(0, (byte) 'A');
mappedByteBuffer.put(1, (byte) 'B');
// mappedByteBuffer.put(5, (byte) 'C'); //IndexOutOfBoundsException
System.out.println("修改成功...");
channel.close();
randomAccessFile.close();
}
}
//Scattering:将数据写入到buffer时,可以采用buffer数组,依次写入 [分散]
//Gathering:从buffer读取数据时,可以采用buffer数组,依次读取 [聚合]
public class ScatteringAndGatheringTest {
public static void main(String[] args) throws Exception {
//使用 ServerSocketChannel 和 SocketChannel 网络
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
//绑定端口到socket,并启动
serverSocketChannel.socket().bind(inetSocketAddress);
//创建buffer数组(不需要使用一块大的内存空间,拆成数组可以有效的利用内存碎片)
ByteBuffer[] byteBuffers = new ByteBuffer[2];
byteBuffers[0] = ByteBuffer.allocate(5);
byteBuffers[0] = ByteBuffer.allocate(3);
//等待客户端连接(telnet)
SocketChannel socketChannel = serverSocketChannel.accept();
//假定从客户端接收8个字节
int messageLength = 8;
//循环读取
while (true){
int byteRead = 0;
while (byteRead < messageLength) {
long l = socketChannel.read(byteBuffers);
//累计读取的字节数
byteRead += l;
System.out.println("byteRead=" + byteRead);
//使用流打印,看看当前的这个buffer的position和limit
Arrays.asList(byteBuffers).stream().map(buffer -> "position=" + buffer.position()
+ ", limit=" + buffer.limit()).forEach(System.out::println);
}
//将所有的buffer进行flip
Arrays.asList(byteBuffers).forEach(buffer -> buffer.flip());
//将数据读出显示到客户端
long byteWrite = 0;
while (byteWrite < messageLength) {
long l = socketChannel.write(byteBuffers);
byteWrite += l;
}
//将所有的buffer进行clear
Arrays.asList(byteBuffers).forEach(buffer -> buffer.clear());
System.out.println("byteRead=" + byteRead + " byteWrite=" + byteWrite + " messageLenth" + messageLength);
}
}
}
选择器(Selector)
基本介绍
Selector示意图和特点说明
Selector类相关的方法
注意事项
NIO 非阻塞 网络编程原理分析图
对上图的说明:
入门案例
服务器端:
//服务器端
public class NIOServer {
public static void main(String[] args) throws Exception {
//创建ServerSocketChannel -> ServerSocket
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//得到一个Selector对象
Selector selector = Selector.open();
//绑定一个端口6666,在服务器端监听
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
//设置为非阻塞
serverSocketChannel.configureBlocking(false);
//把 serverSocketChannel 注册到 selector,关心的事件为OP_ACCEPT(连接事件)
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//selector.selectedKeys().size()为当前selector上现有的数量
//selector.keys().size()为注册过的selectionKey的数量,叠加的
System.out.println("注册后的selectionKey 数量=" + selector.keys().size());
System.out.println("现有的selectionKey 数量=" + selector.selectedKeys().size());
//循环等待客户端连接
while (true){
//阻塞1秒后返回对应的SelectionKey,如果返回0说明:1秒内没有事件发生
//没有事件发送
if(selector.select(1000) == 0){
System.out.println("服务器等待了1秒,没有事件发生...");
continue;
}
//如果返回大于0,就获取相关的SelectionKey集合
//1.如果返回的值大于0,表示已经获取到关注的事件
//2.selector.selectedKeys() 返回关注事件的集合
// 通过 selectionKeys 反向获取通道
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//使用迭代器遍历selectionKeys
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()){
//获取到SelectionKey
SelectionKey selectionKey = keyIterator.next();
//根据selectionKey对应的通道发生的事件做相应的处理
//如果是连接事件,有新的客户端连接(连接事件是服务器端产生的,监听服务器端的连接事件)
if(selectionKey.isAcceptable()){
//给该客户端生成一个socketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("客户端连接成功,生成一个socketChannel " + socketChannel.hashCode());
//将socketChannel设置为非阻塞
socketChannel.configureBlocking(false);
//将socketChannel 注册到selector,关注事件为OP_READ(读事件),同时给socketChannel关联一个buffer
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
System.out.println("注册后的selectionKey 数量=" + selector.keys().size());
System.out.println("现有的selectionKey 数量=" + selector.selectedKeys().size());
}
//如果是读事件(读事件是客户端产生的,监听客户端的读事件)
if(selectionKey.isReadable()){
//通过 selectionKey 反向获取到对应的channel
SocketChannel channel = (SocketChannel)selectionKey.channel();
//获取到该channel关联的buffer
ByteBuffer buffer = (ByteBuffer)selectionKey.attachment();
channel.read(buffer);
buffer.flip();
System.out.println("from 客户端:" + new String(buffer.array(), 0, buffer.limit()));
buffer.clear();
}
//手动从集合中移动当前的selectionKey,防止重复操作
keyIterator.remove();
}
}
}
}
客户端:
//客户端
public class NIOClient {
public static void main(String[] args) throws Exception {
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞
socketChannel.configureBlocking(false);
//提供服务器端的ip和端口
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
//如果没有成功连接上服务器
if (!socketChannel.connect(inetSocketAddress)){
while (!socketChannel.finishConnect()){
System.out.println("因为连接需要时间,客户端不会阻塞,可以做其他工作...");
}
}
//如果连接成功
String str = "hello";
//wrap()传递一个byte数组到buffer中去,buffer的大小和传递进来的byte数组大小一致
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
//发送数据,将buffer中的数据写入channel
socketChannel.write(buffer);
System.in.read();
}
}
SelectionKey
ServerSocketChannel
SocketChannel
NIO网络编程应用实例——群聊系统
GroupChatServer
public class GroupChatServer {
//定义属性
private Selector selector;
private ServerSocketChannel listenChannel;
private static final int PORT = 6667;
//构造器
//初始化工作
public GroupChatServer() {
try {
//初始化选择器
selector = Selector.open();
//初始化ServerSocketChannel
listenChannel = ServerSocketChannel.open();
//绑定端口
listenChannel.socket().bind(new InetSocketAddress(PORT));
//设置非阻塞模式
listenChannel.configureBlocking(false);
//将listenChannel注册到选择器上
listenChannel.register(selector, SelectionKey.OP_ACCEPT);
}catch (IOException e){
e.printStackTrace();
}
}
//监听事件
public void listen(){
try {
//循环处理
while (true){
int count = selector.select();
//有事件要处理
if(count > 0){
//遍历得到的selectionKey集合
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
//取出selectionKey
SelectionKey key = iterator.next();
//监听到accept事件
if(key.isAcceptable()) {
//获取连接的客户端SocketChannel
SocketChannel sc = listenChannel.accept();
//设置成非阻塞
sc.configureBlocking(false);
//将sc注册到selector
sc.register(selector, SelectionKey.OP_READ);
//提示客户端上线
System.out.println("【" + sc.getRemoteAddress() + "】上线");
}
//监听到read事件,通道是可读状态
if (key.isReadable()){
//处理读(专门写方法...)
readData(key);
}
//删除当前的key,防止重复处理
iterator.remove();
}
}else{
System.out.println("等待中...");
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
}
}
//读取客户端消息
private void readData(SelectionKey key){
//定义一个socketChannel
SocketChannel channel = null;
try {
//取到关联的channel
channel = (SocketChannel) key.channel();
//创建buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取通道的数据到缓冲区
int count = channel.read(buffer);
//根据count的值做处理
//读取到数据
if (count > 0){
//切换为读模式
buffer.flip();
//把缓冲区的数据转成字符串
String msg = new String(buffer.array(), 0, buffer.limit());
//输出该消息
System.out.println("from 客户端:" + msg);
//向其他的客户端转发此消息(去掉自己),专门写一个方法来处理
sendInfoToOtherClients(msg, channel);
}
}catch (IOException e){
try {
//离线提示
//这里有bug,不能一下线就显示,下线后只有其他客户端发送消息后才能知道是已经下线并显示,不知道老师为什么能一下线就显示
System.out.println("【" + channel.getRemoteAddress() + "】离线了...");
//取消注册
key.cancel();
//关闭通道
channel.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
//转发信息给其他客户端(通道)
private void sendInfoToOtherClients(String msg, SocketChannel self) throws IOException {
System.out.println("服务器转发消息中...");
//遍历所有注册到selector上的socketChannel,并排除自己self
for (SelectionKey key : selector.keys()) {
//通过key取出对应的socketChannel
Channel targetChannel = key.channel();
//因为取出来的可能是ServerSocketChannel,所以需要判断instanceof SocketChannel
//排除自己
if (targetChannel instanceof SocketChannel && targetChannel != self){
//转型
SocketChannel dest = (SocketChannel) targetChannel;
//将msg存储到buffer
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
//将buffer中的数据写入通道中
dest.write(buffer);
}
}
}
public static void main(String[] args) {
//创建服务器对象
GroupChatServer server = new GroupChatServer();
//监听事件
server.listen();
}
}
GroupChatClient
public class GroupChatClient {
//定义相关属性
//服务器的ip
private final String HOST = "127.0.0.1";
//服务器的端口
private final int PORT = 6667;
private Selector selector;
private SocketChannel socketChannel;
private String username;
//构造器,完成初始化工作
public GroupChatClient(){
try {
//获取选择器
selector = Selector.open();
//连接服务器
socketChannel = SocketChannel.open(new InetSocketAddress(HOST, PORT));
//设置非阻塞
socketChannel.configureBlocking(false);
//将channel注册到selector
socketChannel.register(selector, SelectionKey.OP_READ);
//得到username
username = socketChannel.getLocalAddress().toString().substring(1);
System.out.println(username + " is ok...");
} catch (IOException e) {
e.printStackTrace();
}
}
//向服务器发送消息
public void sendInfo(String info){
info = username + "说:" + info;
try {
//发送消息
socketChannel.write(ByteBuffer.wrap(info.getBytes()));
}catch (IOException e){
e.printStackTrace();
}
}
//读取从服务器端回复的消息
public void readInfo(){
try {
int readChannels = selector.select();
//有可以用的通道
if (readChannels > 0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
//遍历
while (iterator.hasNext()){
SelectionKey key = iterator.next();
if(key.isReadable()){
//得到相关的通道
SocketChannel sc = (SocketChannel) key.channel();
//得到一个buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取
sc.read(buffer);
//把读到的数据转成字符串
buffer.flip();
String msg = new String(buffer.array(), 0, buffer.limit());
System.out.println(msg.trim());
//移除
iterator.remove();
}
}
}else {
//System.out.println("没有可用的通道...");
}
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
//启动客户端
GroupChatClient client = new GroupChatClient();
//启动一个线程,专门用来接收数据,每隔3秒,读取从服务器发送过来的数据
new Thread(){
@Override
public void run(){
while (true){
client.readInfo();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
//发送数据给服务器端
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
String str = scanner.nextLine();
client.sendInfo(str);
}
}
}
零拷贝
基本介绍
零拷贝是指没有CPU拷贝,而非不拷贝,DMA拷贝是无法避免的。
传统IO数据读写
传统IO:4次拷贝,3次切换。
mmap优化
mmap优化:3次拷贝,3次切换。
sendFile优化
sendFile优化:3次拷贝,2次切换。
更新后的sendFile优化:2次拷贝,2次切换。
零拷贝的再次理解#
mmap和sendFile的区别
案例
传统IO实现代码
OldIOServer
public class OldIOServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(7001);
while (true) {
Socket socket = serverSocket.accept();
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
try {
byte[] byteArray = new byte[4096];
while (true) {
int readCount = dataInputStream.read(byteArray, 0, byteArray.length);
if (-1 == readCount) {
break;
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
OldIOClient
public class OldIOClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("localhost", 7001);
String fileName = "protoc-3.6.1-win32.zip";
InputStream inputStream = new FileInputStream(fileName);
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
byte[] buffer = new byte[4096];
long readCount;
long total = 0;
long startTime = System.currentTimeMillis();
while ((readCount = inputStream.read(buffer)) >= 0) {
total += readCount;
dataOutputStream.write(buffer);
}
System.out.println("发送总字节数: " + total + ", 耗时: " + (System.currentTimeMillis() - startTime));
dataOutputStream.close();
socket.close();
inputStream.close();
}
}
零拷贝实现代码
NewIOServer
public class NewIOServer {
public static void main(String[] args) throws Exception {
//指定端口
InetSocketAddress address = new InetSocketAddress(7001);
//得到ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//得到ServerSocket
ServerSocket serverSocket = serverSocketChannel.socket();
//地址绑定
serverSocket.bind(address);
//创建buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
//监听
while (true){
SocketChannel socketChannel = serverSocketChannel.accept();
int readCount = 0;
while (-1 != readCount){
try {
//读取数据
readCount = socketChannel.read(byteBuffer);
}catch (Exception e){
//e.printStackTrace();
break;
}
//倒带 position变为0,mark标记作废
byteBuffer.rewind();
}
}
}
}
NewIOClient
public class NewIOClient {
public static void main(String[] args) throws Exception {
//得到SocketChannel
SocketChannel socketChannel = SocketChannel.open();
//连接
socketChannel.connect(new InetSocketAddress("localhost", 7001));
String filename = "zipkin-server-2.12.9-exec.jar";
//得到一个文件channel
FileChannel fileChannel = new FileInputStream(filename).getChannel();
//准备发送
long startTime = System.currentTimeMillis();
//在linux/mac下,一个transferTo方法就可以完成传输
//在windows下,一次调用transferTo方法只能发送8m,所以需要分段传输文件,分段后需要记录分段的位置,下一次从这个位置开始
//transferTo底层使用了零拷贝,transferFrom也是
// long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
// System.out.println("发送的总的字节数=" + transferCount + " 耗时:" + (System.currentTimeMillis() - startTime));
//分段传输,需要一个大一点的文件
//如果小于8m,不分段
if((int) fileChannel.size() < (8 * 1024 * 1024)){
long transferCount = fileChannel.transferTo(0, (int) fileChannel.size(), socketChannel);
System.out.println("发送的总的字节数=" + transferCount + " 耗时:" + (System.currentTimeMillis() - startTime));
}else{
//分段
int sendCount = (int) (fileChannel.size() / (8 * 1024 * 1024));
if((int) (fileChannel.size() % (8 * 1024 * 1024)) > 0){
sendCount++;
}
//发送
long transferCount = 0;
int head = 0;
int tail = 8 * 1024 * 1024;
for (int i = 0; i < sendCount; i++) {
transferCount += fileChannel.transferTo(head, tail, socketChannel);
head = tail;
//倒数第2段,修改tail
if (i == sendCount - 2){
tail = (int) (fileChannel.size() % (8 * 1024 * 1024));
}
}
System.out.println("分段数:"+ sendCount + " 发送的总的字节数:" + transferCount + " 耗时:" + (System.currentTimeMillis() - startTime));
}
//关闭通道
fileChannel.close();
socketChannel.close();
}
}
Java AIO
基本介绍
http://www.52im.net/thread-306-1-1.html
BIO、NIO、AIO对比表
下一篇笔记:Netty入门学习笔记(二)
学习视频(p1-p35):https://www.bilibili.com/video/BV1DJ411m7NR?p=1