Netty简单认识:
1) Netty 是由JBOSS 提供的一个Java 开源框架。
2) Netty 是一个异步的、基于事件驱动的网络应用框架,用以快速开发高性能、高可靠性的网络I0 程序。
3) Netty 主要针对在TCP协议下的使用
4)Netty本质是- 个NIO框架,适用于服务器通讯相关的多种应用场景
Netty应用:
https://netty.io/wiki/related-projects.html这里面是和netty有关的框架
Netty应用于网络间的通信,如阿里的dubbo框架,应用于服务之间的调用;谷歌的grpc框架;
I/O模型:
netty是基于nio开发,所以我们需要了解java中的I/O模型:
- Bio:同步阻塞io,服务器实现模式为一 一个连接-一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理
如果这个连接不做任何事情会造成不必要的线程开销,适用于连接数小的和固定的,编程简单。

- Nio:同步非阻塞,JavaNIO :同步非阻塞, 服务器实现模式为- -个线程处理多个请求(连接),即客户端发送的连接请求
都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理,适用于连接数多的连接时间短的,编程复杂。(1.4以后版本)

- Aio:异步非阻塞,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,
一般适用于连接数较多且连接时间较长的应用,适用于连接数多的和连接时间长的,编程复杂,(1.7以后版本)
bio:
public class ServerClient {
public static void main(String[] args) throws IOException {
//创建自定义线程池
ExecutorService executorService = new ThreadPoolExecutor(
3,
7,
2000,
TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<>(3),
new ThreadPoolExecutor.CallerRunsPolicy());
//启动服务端
ServerSocket serverSocket = new ServerSocket(8086);
System.out.println("服务端启动,等待连接。。。。。。。");
while(true){
//等待连接
final Socket socket = serverSocket.accept();
System.out.println("连接上一个客户。。。。。。"+socket.getPort());
executorService.execute(()->{
handel(socket);
});
}
}
//处理读取到的数据
public static void handel(Socket socket){
try{
byte[] bytes = new byte[1024];
InputStream inputStream = socket.getInputStream();
while (true){
//等待读取数据
int length = inputStream.read(bytes);
if(length != -1){
System.out.println("读取到数据"+new String(bytes,0,length));
}else{
break;
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
通过这个demo我们可以看到,每个客户端需要独立的线程,使用bio会在等待连接和读取数据两处进行阻塞,造成线程资源的浪费。
nio:
1》 NIO 有三大核心部分: Channel(通道), Buffer(缓冲区), Selector(选择器)
2》NIO是面向缓冲区,或者面向块编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后
移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络
3》Java NIO的非阻塞模式,使-一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,
如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可
以继续做其他的事情。非阻塞写 也是如此,-一个线程请求写入- -些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
通俗理解: NIO是可以做到用-一个线程来处理多个操作的。假设有10000 个请求过来,根据实际情况,可以分配50或者100个线程来处理。
不像之前的阻塞I0那样,非得分配10000个。
4》HTTP2.0采用多路复用技术
5》nio中buffer的demo:
public class NioBaise {
public static void main(String[] args) {
//创建容量为5的IntBuffer
IntBuffer intBuffer = IntBuffer.allocate(5);
for (int i = 0; i <5 ; i++) {
intBuffer.put(i);
}
//切换,有写变读
intBuffer.flip();
//判断是否还有数据
while (intBuffer.hasRemaining()){
//获取数据,get()获取数据后,就会移动下标
System.out.println(intBuffer.get());
}
}
}
NIO 和BIO的比较:
1》BIO 是阻塞的,NIO则是非阻塞的
2》 BIO 以流的方式处理数据,而NIO以块的方式处理数据,块I/O 的效率比流I0高很多
3》 BIO 基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区 )进行操作,数据总是从通道
读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择 器)用于监听多个通道的事件(比如:连接请求,
数据到达等),因此使用单个线程就可以监听多个客户端通道
nio中的buffer:
有四个重要属性
Capacity:容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变
Limit:表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的
Position:位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变改值,每次读写缓冲区数据时都会改变改值,
Mark:标记
Buffer中常用方法:

在Buffer中ByteBuffer是最常用的(二进制数据),主要方法如下:

nio中的channel:
1) NIO的通道类似于流,但有些区别如下:
●通道可以同时进行读写, 而流只能读或者只能写
●通道可以实现异步读写 数据
●通道可以从缓冲读数据, 也可以写数据到缓冲:
2) BIO 中的stream 是单向的,例如FilelnputStream 对象只能进行读取数据的操作,而NIO中的通道(Channel)是双向的,可以读操作,也可以写操作。
3) Channel 在NIO中是一个接口 public interface Channel extends Closeable{}
4)常用的Channel 类有: FileChannel、DatagramChannel、ServerSocketChannel 和SocketChannel。
5) FileChannel 用于文件的数据读写,DatagramChannel 用于UDP 的数据读写,ServerSocketChannel 和SocketChannel用于TCP的数据读写。
FileChannel常用方法:

FileChannel的demo1:向文件中写入字符串
public class FileChannelDemo {
public static void main(String[] args) throws IOException {
String str = "hello";
//创建文件流
FileOutputStream fileOutputStream = new FileOutputStream("e:\\hello.txt");
//将流转为通道
FileChannel fileChannel = fileOutputStream.getChannel();
//创建换缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(str.getBytes());
//将byteBuffer进行反转
byteBuffer.flip();
//将缓冲区数据写入到fileChannel通道里
fileChannel.write(byteBuffer);
//关闭管道
fileChannel.close();
}
}
FileChannel的demo2:向文件中读取字符串
public class FileChannelDemo2 {
public static void main(String[] args) throws IOException {
//创建文件输入流
FileInputStream fileInputStream = new FileInputStream("e:\\hello.txt");
//将流转为通道
FileChannel fileChannel = fileInputStream.getChannel();
//创建换缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(fileInputStream.available());
//将管道里面的数据写入到byteBuffer中
fileChannel.read(byteBuffer);
//将byteBuffer进行反转
byteBuffer.flip();
System.out.println(new String(byteBuffer.array()));
//关闭管道
fileChannel.close();
}
}
FileChannel的demo3:文件复制:
public class FileChannelDemo3 {
public static void main(String[] args) throws IOException {
//创建文件输入流
FileInputStream fileInputStream = new FileInputStream("e:\\hello.txt");
FileChannel fileChannel = fileInputStream.getChannel();
//创建文件输出流
FileOutputStream fileOutputStream = new FileOutputStream("e:\\hello2.txt");
FileChannel channel = fileOutputStream.getChannel();
//创建换缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (true){
//复位
byteBuffer.clear();
//将管道里面的数据写入到byteBuffer中
int read = fileChannel.read(byteBuffer);
if(read == -1){
break;
}
//将byteBuffer进行反转
byteBuffer.flip();
//将byteBuffer里面的数据写入到通道中
channel.write(byteBuffer);
}
System.out.println("写入完成");
//关闭管道
fileChannel.close();
fileOutputStream.close();
fileInputStream.close();
}
}
FileChannel的demo4:文件复制,直接使用transferFrom()方法;
public class FileChannelDemo4 {
public static void main(String[] args) throws IOException {
//创建文件输入流
FileInputStream fileInputStream = new FileInputStream("e:\\hello.txt");
FileChannel sourceChannel = fileInputStream.getChannel();
//创建文件输出流
FileOutputStream fileOutputStream = new FileOutputStream("e:\\hello2.txt");
FileChannel targetChannel = fileOutputStream.getChannel();
//复制
targetChannel.transferFrom(sourceChannel,0,sourceChannel.size());
sourceChannel.close();
targetChannel.close();
fileInputStream.close();
fileOutputStream.close();
}
}
Buffer和channel的注意点:
1) ByteBuffer 支持类型化的put和get, put放入的是什么数据类型,get 就应该使用相应的数据类型来取出,否
则可能有BufferUnderflowException 异常。
2)可以将-一个普通Buffer转成只读Buffer,使用asReadOnlyBuffer()返回ByteBuffer
public abstract ByteBuffer asReadOnlyBuffer()
3) NIO 还提供了MappedByteBuffer, 可以让文件直接在内存 (堆外的内存)中进行修改,而 如何同步到文件由nio完成。
demo:
public class MapedByteBufferDemo {
public static void main(String[] args) throws Exception{
RandomAccessFile randomAccessFile = new RandomAccessFile("e:\\hello.txt","rw");
FileChannel channel = randomAccessFile.getChannel();
/**
* 参数一:读写模式
* 参数二:开始读取的位值
* 参数三:是映射到内存的大小,而不是索引的大小
*/
MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
map.put(0,(byte)'H');
map.put(2,(byte)'y');
//如果超过大小会发生异常:java.lang.IndexOutOfBoundsException
//map.put(5,(byte)'0');
randomAccessFile.close();
System.out.println("修改成功");
}
}
nio中的Selector:
示意图及特点:


常用方法:
public static Selector open() throws IOException:得到一个选择器对象public int select(long timeout);//监控所有注册的通道,当其中有I0操作可以进行时,将对应的SelectionKey加入到内部集合中并返回,参数用来设置超时时间public Set<SelectionKey> selectedKeys()//从内部集合中得到所有的SelectionKey方法说明:

过程:
1)当客户端连接时, 会通过ServerSocketChannel得到SocketChannel
2) Selector 进行监听select 方法,返回有事件发生的通道的个数.
3)将socketChannel注册到Selector上, register(Selector sel, int ops),一个selector上可以注册多个SocketChannel
4)注册后返回一个SelectionKey, 会和该Selector关联(集合)
5) 进-步得到各个SelectionKey (有事件发生)
6)在通过SelectionKey 反向 获取SocketChannel ,方法channel()
7)可以通过得到的 channel, 完成业务 处理
demo:NioServer(服务端)
public class NioServer {
public static void main(String[] args) throws IOException {
//创建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//绑定网络端口
serverSocketChannel.socket().bind(new InetSocketAddress(666));
//得到selector对象
Selector selector = Selector.open();
//设置为非阻塞的
serverSocketChannel.configureBlocking(false);
//将serverSocketChannel注册到selector中,selector只关心OP_ACCEPT事件
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//循环等待客户连接
while (true){
//没有事件就返回
if(selector.select(1000)==0){
System.out.println("服务端等待连接。。。。。。");
continue;
}
//获取到相关SelectionKey集合,表示获取到关注的事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//使用迭代器遍历
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
if(keyIterator.hasNext()){
//获取到SelectionKey
SelectionKey key = keyIterator.next();
//根据key对应的事件作相应的处理
if(key.isAcceptable()){//表示有新客户端连接
//每次连接一个客户端都会产生新的SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("每次连接一个客户端都会产生新的SocketChannel的hashcode"+socketChannel.hashCode());
//设置客户端为非阻塞的
socketChannel.configureBlocking(false);
//将socketChannel注册到selector,关注事件为OP_READ,同时给socketChannel关联一个buffer
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
System.out.println("连接上一个客户端....");
}
if(key.isReadable()){//发生OP_READ事件
//通过key反向获取到channel
SocketChannel socketChannel = (SocketChannel)key.channel();
//获取到该channer关联的buffer
ByteBuffer byteBuffer =(ByteBuffer) key.attachment();
socketChannel.read(byteBuffer);
System.out.println("读取到数据。。。"+new String(byteBuffer.array()));
}
//移除seletorKey,防止重复操作。
keyIterator.remove();
}
}
}
}
客户端:NioClient
public class NioClient {
public static void main(String[] args) throws IOException {
//连接服务端的ip和端口
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8888);
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置为非阻塞的
socketChannel.configureBlocking(false);
//连接服务器
if (!socketChannel.connect(inetSocketAddress)) {
while (!socketChannel.finishConnect()) {
System.out.println("因为连接需要时间,客户端不会阻塞,可以做其他工作");
}
}
String str = "hello";
//创建buffer
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
//将buffer数据写入到channel中
socketChannel.write(byteBuffer);
System.in.read();
}
}
SelectKey介绍:
1) SelectionKey,表示Selector 和网络通道的注册关系,共四种:
int OP_ ACCEPT:有新的网络连接可以accept,值为16
int OP_ CONNECT:代表连接已经建立,值为8
intOP_ READ:代表读操作,值为1
intOP_ WRITE:代表写操作,值为4
源码是采用位操作符:
public static final int OP_READ= 1 << 0; public static final int OP_WRITE= 1 << 2; public static final int OP_CONNECT= 1 << 3; public static final int OP_ACCEPT= 1 <<4;
相关方法:
public abstract Selector selecto/(;/得到与之关联的Selector对象) public abstract SelectableChannel chanel)://得到与之关联的通道 public final Object ttachment():/得到与之关联的共享数据 public abstract SelectionKey interestOps(int ops//设置或改变监听事件 public final boolean isAcceptable(;//是否可以accept public final boolean isReadablel(;//是否可以读 public final boolean isWritable(;//是否可以写
ServerSocketChannel:在服务端监听客户端的连接
public static ServerSocketChannel open(),得到-一个ServerSocketChannel通道 public final ServerSocketChannel bind(SocketAddress local),设置服务器端端口号 public final SelectableChannel configureBlocking(boolean block),设置阻塞或非阻塞模式,取值false表示采用非阻塞模式 public SocketChannel accept),接受一个连接, 返回代表这个连接的通道对象 public final SelectionKey register(Selectorsel, intops),注册一个选择器并设置监听事件
SocketChannel:网络io操作,负责数据的读写操作。Nio把数据从缓存中写入通道或把通道里的数据读取到缓存中。
public static SocketChannel open/://得到一个SocketChannel通道 public final SelectableChannel configureBlocking(boolean blok;//设置阻塞或非阻塞模式,取值false表示采用非阻塞模式 public boolean connect(SocketAddress remote://连接服务器 public boolean finishConnet://如果上面的方法连接失败,接下来就要通过该方法完成连接操作 public int write(ByteBuffer sc);//通道里写数据 public int read(ByteBuffer dst);//从通道里读数据 public final SelectionKey register(Selector sel, int ops, Object a://注册个选择器并设置监听事件,最后一个参数可以设置共享数据 public final void close(;//关闭通道
SocketChannel和ServerSocketChannel区别:SocketChannel更注重于数据上的操作,ServerSocketChannel注重于连接上。
群聊demo:服务端
package com.netty.groupChat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class GroupChatServer {
private int port = 9999;
private ServerSocketChannel listenChannel;
private Selector selector;
//初始化
public GroupChatServer(){
try{
//创建选择器
selector = Selector.open();
//创建ServerSocketChannel
listenChannel = ServerSocketChannel.open();
//绑定端口
listenChannel.socket().bind(new InetSocketAddress(port));
//设置非阻塞模式
listenChannel.configureBlocking(false);
//将ServerSocketChannel注册到Secletor中,接受连接事件
listenChannel.register(selector,SelectionKey.OP_ACCEPT);
}catch (Exception e){
e.printStackTrace();
}
}
//监听连接
public void listenEvent() {
try {
while (true) {
//有事件才处理
if(selector.select() > 0){
//遍历循环得到SelectionKey
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()) {
//得到SelectKey
SelectionKey key = keyIterator.next();
//监听对应的事件,这里监听连接事件
if (key.isAcceptable()) {
//创建SocketChannel
SocketChannel sc = listenChannel.accept();
//设置非阻塞
sc.configureBlocking(false);
//将SocketChannel注册到Selector中,处理读事件
sc.register(selector, SelectionKey.OP_READ);
//提示上线
System.out.println(sc.getRemoteAddress() + "上线");
}
//处理读事件
if (key.isReadable()) {
readData(key);
}
//删除当前的key,避免重复处理
keyIterator.remove();
}
}else{
System.out.println("等待。。。。");
}
}
} catch (Exception e) {
e.printStackTrace();
}
finally {
}
}
//读取数据
public void readData(SelectionKey key){
//通过key获取绑定的SocketChannel
SocketChannel socketChannel = (SocketChannel)key.channel();
//创建缓存区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
try{
//从通道的数据读取到缓冲区中
int read = socketChannel.read(byteBuffer);
//当有数据时
if(read>0){
//获取缓冲区数据
String msg = new String(byteBuffer.array());
//虎丘客户端信息
System.out.println("客户端信息。。。。。"+msg);
//向其他用户分发信息
forwardMsg(msg,socketChannel);
}
}catch (Exception e){
try{
//取消注册
key.cancel();
//关闭通道
socketChannel.close();
System.out.println(socketChannel.getRemoteAddress()+"下线");
}catch (Exception ex){
ex.printStackTrace();
}
}
}
//转发信息
public void forwardMsg(String msg,SocketChannel self) throws IOException {
System.out.println("消息转发。。。。。");
//获取到所有注册到selector中的SocketChannel
Set<SelectionKey> keys = selector.keys();
for(SelectionKey key : keys){
Channel targetChannel = key.channel();
//不向自己发消息
if(targetChannel instanceof SocketChannel && targetChannel != self){
SocketChannel dest = (SocketChannel) targetChannel;
ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
//将数据从缓冲区中读到通道中
dest.write(byteBuffer);
}
}
}
public static void main(String[] args) {
GroupChatServer groupChatServer = new GroupChatServer();
System.out.println("服务器启动。。。");
groupChatServer.listenEvent();
}
}
客户端:
package com.netty.groupChat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
public class GroupChatClient {
private String host = "127.0.0.1";
private int port = 9999;
private SocketChannel socketChannel;
private Selector selector;
private String user;
public GroupChatClient(){
try {
//创建选择器
selector = Selector.open();
//连接服务器
socketChannel = socketChannel.open(new InetSocketAddress(host,port));
//设置非阻塞
socketChannel.configureBlocking(false);
//将socketChannel注册到selector,处理OP_READ事件
socketChannel.register(selector, SelectionKey.OP_READ);
//得到用户
user = socketChannel.getRemoteAddress().toString();
System.out.println("ok ............");
} catch (IOException e) {
e.printStackTrace();
}
}
//发送消息
public void sendInfo(String info) {
try{
info = user+"说:"+info;
ByteBuffer byteBuffer = ByteBuffer.wrap(info.getBytes());
socketChannel.write(byteBuffer);
}catch (Exception e){
e.printStackTrace();
}
}
//读取数据
public void readInfo() {
try{
int readChannel = selector.select();
//有事件
if(readChannel > 0){
//遍历循环,得到SelectionKey
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
//处理读取事件
if(key.isReadable()){
//通过key获取绑定的SocketChannel
SocketChannel socketChannel =(SocketChannel) key.channel();
//创建缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//将通道里的数据写出到byteBuffer
socketChannel.read(byteBuffer);
//获取信息
System.out.println(new String(byteBuffer.array()));
}
//删除当前的SelectionKey,防止重复
iterator.remove();
}
}else{
}
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
GroupChatClient groupChatClient = new GroupChatClient();
new Thread(() -> {
while (true) {
groupChatClient.readInfo();
try {
Thread.currentThread().sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
groupChatClient.sendInfo(scanner.nextLine());
}
};
}
NIO与零拷贝.
1)零拷贝是网络编程的关键,很多性能优化都离不开。
2)在Java 程序中,常用的零拷贝有mmap(内存映射)和sendFile. 那么,他们在OS里,到底是怎么样的一个
的设计?我们分析mmap和sendFile 这两个零拷贝
传统IO模型:
DMA: directmemory access直接内存拷则(不使用CPU)
mmap优化:
1) mmap通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据.这样,在进行网
络传输时,就可以减少内核空间到用户空间的拷贝次数。如下图
sendFile优化:
Linux2.1版本提供了sendFile 函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到
Socket Buffer,同时,由于和用户态完全无关,就减少了一次上下文切换。
零拷贝理解:
1) 我们说零拷贝,是从操作系统的角度来说的。因为内核缓冲区之间,没有数据是重复的(只有kermel buffer 有
一份数据),
2)零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的CPU缓存伪共享
以及无CPU校验和计算。
mmap和sendFile的区别:
1》mmap适合小数据量读写,sendFile 适合大文件传输。
2》mmap需要4次上下文切换,3次数据拷贝: sendFile 需要3次上下文切换,最少2次数据拷贝。
3》sendFile可以利用DMA方式,减少CPU拷贝,mmap则不能(必须从内核拷贝到Socket 缓冲区)。
在nio中FileChannel中的transformTo()方法底层原理就是零拷贝。
demo:
public class NioCopyServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9998));
while (true){
SocketChannel socketChannel = serverSocketChannel.accept();
ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
int readCount = socketChannel.read(byteBuffer);
while (readCount == -1){
break;
}
byteBuffer.rewind();
}
}
}
客户端
public interface NioCopyClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9999));
FileInputStream fileInputStream = new FileInputStream(new File("e:\\hello.txt"));
FileChannel fileChannel = fileInputStream.getChannel();
long startTimeMillis = System.currentTimeMillis();
long transferTo = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
long endTimeMillis = System.currentTimeMillis();
System.out.println("文件大小:"+transferTo+",耗时:"+(endTimeMillis-startTimeMillis));
}
}
待更。。。