NIO读服务器文件,NIO实现TCP文件传输

最近这段时间学习了一下NIO,就想把FileChannel和SocketChannel方面的知识结合一下,于是就练习了这个基于NIO的TCP文件传输的例程。主要使用了SocketChannel,ServerSocketChannel,Selector,FileChannel等实现。当服务器端和客户端都启动后,服务端接受客户端的连接,然后通过TCP将本地的一个文件发送至客户端,客户端接收到可读事件后,读取该文件,并存储,完成所有工作。

以下是服务器端代码:

/**

* Title: Server.java

* Description:

* @author:wh

* @date 2019年7月9日

* @version 1.0

* Company: itiis

*/

import java.io.FileInputStream;

import java.io.IOException;

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.nio.channels.FileChannel;

import java.nio.channels.SelectionKey;

import java.nio.channels.Selector;

import java.nio.channels.ServerSocketChannel;

import java.nio.channels.SocketChannel;

import java.util.Iterator;

/**

*@class_name:Server

*@comments:nio传输文件(发送)

*@param:

*@return:

*@author:wh

*@createtime:2019年7月9日

*/

public class Server {

//通道管理器

private Selector selector;

//获取一个ServerSocket通道,并初始化通道

public Server init(int port) throws IOException{

//1.获取一个ServerSocket通道

ServerSocketChannel serverChannel = ServerSocketChannel.open();

System.out.println(serverChannel.isBlocking());

serverChannel.configureBlocking(false);设置为非阻塞

System.out.println(serverChannel.isBlocking());

//2.绑定监听,配置TCP参数,例如backlog大小

serverChannel.socket().bind(new InetSocketAddress(port));

//3.获取通道管理器

selector=Selector.open();

//将通道管理器与通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,

//只有当该事件到达时,Selector.select()会返回,否则一直阻塞。

serverChannel.register(selector, SelectionKey.OP_ACCEPT);//注册channel到selector,监测接受此通道套接字的连接

return this;

}

public void listen() throws IOException{

System.out.println("服务器启动成功");

boolean isRun = true;

while(isRun){

//当有注册的事件到达时,方法返回,否则阻塞。

selector.select();

//获取selector中的迭代器,选中项为注册的事件

Iterator ite=selector.selectedKeys().iterator();

while(ite.hasNext()){

SelectionKey key = ite.next();

//删除已选的key,防止重复处理

ite.remove();

if(key.isAcceptable()){

ServerSocketChannel server = (ServerSocketChannel)key.channel();

//获得客户端连接通道

SocketChannel channel = server.accept();

channel.configureBlocking(false);//可以在任意位置调用这个方法,新的阻塞模式只会影响下面的i/o操作

//在与客户端连接成功后,为客户端通道注册SelectionKey.OP_WRITE事件。

channel.register(selector, SelectionKey.OP_WRITE);

System.out.println("客户端请求连接事件");

}else if(key.isReadable()){

}else if(key.isWritable()){

SocketChannel socketChannel = (SocketChannel)key.channel();

FileInputStream file = new FileInputStream("C:\\Users\\WH\\Desktop\\实验室各种账号密码.txt");

FileChannel fileChannel = file.getChannel();

//500M 堆外内存

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(524288000);

while(fileChannel.position()

fileChannel.read(byteBuffer);//从文件通道读取到byteBuffer

byteBuffer.flip();

while(byteBuffer.hasRemaining()){

socketChannel.write(byteBuffer);//写入通道

}

byteBuffer.clear();//清理byteBuffer

System.out.println(fileChannel.position()+" "+fileChannel.size());

}

System.out.println("结束写操作");

socketChannel.close();

}

}

}

}

public static void main(String[] args) throws IOException {

new Server().init(9981).listen();

}

}

以下是客户端代码:

/**

* Title: Client.java

* Description:

* @author:wh

* @date 2019年7月9日

* @version 1.0

* Company: itiis

*/

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.nio.channels.FileChannel;

import java.nio.channels.SelectionKey;

import java.nio.channels.Selector;

import java.nio.channels.SocketChannel;

import java.util.Iterator;

/**

*@class_name:Client

*@comments:nio传输文件客户端(接收)

*@param:

*@return:

*@author:wh

*@createtime:2019年7月9日

*/

public class Client {

//管道管理器

private Selector selector;

public Client init(String serverIp, int port) throws IOException, InterruptedException{

//获取socket通道

SocketChannel channel = SocketChannel.open();

channel.configureBlocking(false);

//获得通道管理器

selector=Selector.open();

//客户端连接服务器,需要调用channel.finishConnect();才能实际完成连接。

boolean connectResult = channel.connect(new InetSocketAddress(serverIp, port));

//为该通道注册SelectionKey.OP_CONNECT事件

channel.register(selector, SelectionKey.OP_CONNECT);

return this;

}

public void listen() throws IOException{

System.out.println("客户端启动");

//轮询访问selector

while(true){

//选择注册过的io操作的事件(第一次为SelectionKey.OP_CONNECT)

selector.select();

Iterator ite = selector.selectedKeys().iterator();

while(ite.hasNext()){

SelectionKey key = ite.next();

//删除已选的key,防止重复处理

ite.remove();

if(key.isConnectable()){

SocketChannel channel=(SocketChannel)key.channel();

//如果正在连接,则完成连接

if(channel.isConnectionPending()){

channel.finishConnect();

}

channel.configureBlocking(false);

//向服务器发送消息

channel.write(ByteBuffer.wrap(new String("send message to server.").getBytes()));

//连接成功后,注册接收服务器消息的事件

channel.register(selector, SelectionKey.OP_READ);//订阅读取事件

System.out.println("客户端连接成功");

}else if(key.isReadable()){ //有可读数据事件。

SocketChannel channel = (SocketChannel)key.channel();

ByteBuffer byteBuffer = ByteBuffer.allocate(10);

int readLength = channel.read(byteBuffer);

byteBuffer.flip();

int count = 0;

File file = new File("C:\\Users\\WH\\Desktop\\实验室各种账号密码_copy.txt");

if(!file.exists()) file.createNewFile();

FileOutputStream fe =new FileOutputStream(file,true);//可追加写

FileChannel outFileChannel = fe.getChannel();

while(readLength != -1){ //分多次读取

count = count+readLength;

System.out.println("count="+count+" readLength="+readLength);

readLength = channel.read(byteBuffer);//将socketChannel数据读到byteBuffer

byteBuffer.flip();

outFileChannel.write(byteBuffer);//byteBuffer数据写到FileChannel

fe.flush();

byteBuffer.clear();

}

outFileChannel.close();

fe.close();

System.out.println("读取结束");

channel.close();

}

}

}

}

public static void main(String[] args) throws IOException, InterruptedException {

new Client().init("127.0.0.1", 9981).listen();

}

}

运行服务器和客户端,可以看到桌面出现一个“实验室各种账号密码_copy.txt”文件,打开后和原始文件一样。

客户端运行结果.png

使用了Selector的NIO可以使用一个线程来对多个通道中就绪通道进行选择,大大节省了CPU资源,省去了多个线程来回切换资源浪费。

你可能感兴趣的:(NIO读服务器文件)