一、背景
很多时候我们都会用FTP工具来实现文件的上传下载功能,于是琢磨着也用java的相关知识来自己实现一个简单的文件服务器
二、NIO Socket
考虑到文件的上传下载其实就是客户端和服务器进行通讯,然后进行数据交换。此时就可以采用Socket来实现。从JDK1.4版本以来java提供了更加高效的非阻塞形式的Socket,也就是NIO方式的Socket,通过通道Channel和缓冲器Buffer的方式来进行数据的读写。关于NIO的介绍,这里找了网上的一篇文章,可以参考一下 JAVA NIO简介
三、实施步骤
1、实现文件的上传下载等,需要服务器和客户端两部分。服务端我们取名为FileCenter,客户端我们取名为FileClient
2、首先实现FileCenter,具体代码如下,理解可参照注释
package org.filecenter;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;
/**
* 文件下中心,命令模式如下
* 显示文件:list
* 列出所有可下载的文件
* 下载文件:get server_file [local_file]
* server_file服务器中的文件名,local_file本地存储路径
* 上传文件:put local_file [server_file]
* local_file本地文件路径,server_file服务器存储文件名
*
* @author cyxl
*
*/
public class FileCenter {
private static Selector selector; // 选择器
private static final int server_port = 12345; // 服务器端口
private static CharsetDecoder decoder = Charset.forName("GB2312")
.newDecoder(); // 字节转字符
private static CharsetEncoder encoder = Charset.forName("GB2312")
.newEncoder(); // 字符转字节
private static ByteBuffer buffer = ByteBuffer.allocate(1024);
private static final String server_path = "C:\\file_center\\"; // 服务器文件路径
public static void main(String[] args) {
try {
selector = Selector.open();// 打开选择器
ServerSocketChannel serverChannel = ServerSocketChannel.open();
ServerSocket server = serverChannel.socket();
server.bind(new InetSocketAddress(server_port));
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("等待客户端连接……");
while (true) {
selector.select();
Iterator<SelectionKey> itr = selector.selectedKeys().iterator();
while (itr.hasNext()) {
SelectionKey key = itr.next();
itr.remove();
process(key);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void process(SelectionKey key) throws IOException {
if (key.isAcceptable()) {
// 连接
ServerSocketChannel serverChannel = (ServerSocketChannel) key
.channel();
SocketChannel client = serverChannel.accept();
client.configureBlocking(false);
SelectionKey sKey = client.register(selector, SelectionKey.OP_READ);
sKey.attach("[r_cmd]"); // 连接好后读取客户端发来的命令
} else if (key.isReadable()) {
// 读取
SocketChannel channel = (SocketChannel) key.channel();
String attach = key.attachment().toString();
if (attach.equals("[r_cmd]")) {
// 获取命令
int len = channel.read(buffer);
if (len > 0) {
buffer.flip();
String cmd = "";
CharBuffer charBuffer = decoder.decode(buffer);
cmd = charBuffer.toString();
SelectionKey sKey = channel.register(selector,
SelectionKey.OP_WRITE);
System.out.println("cmd:" + cmd);
if (cmd.trim().equals("list")) {
sKey.attach("[list_file]");
} else {
String[] temp = cmd.split(" ");
if (temp.length >= 2) {
cmd = temp[0];
String filename = temp[1];
if (cmd.equals("get")) {
// 下载
File file = new File(server_path, filename);
if (file.exists()) {
sKey.attach("[get]:" + filename);
} else {
sKey.attach("[no_file]");
}
} else if (cmd.equals("put")) {
// 上传
sKey.attach("[put]:" + filename);
} else {
// 错误命令格式
sKey.attach("[error_command]");
}
}
}
} else {
channel.close();
}
}
buffer.clear();
} else if (key.isWritable()) {
// 写入
SocketChannel channel = (SocketChannel) key.channel();
String attach = key.attachment().toString();
if (attach.startsWith("[list_file]")) {
channel.write(encoder.encode(CharBuffer.wrap("list files")));
File file = new File(server_path);
String[] filenames = file.list();
String temp = "";
for (String filename : filenames) {
temp += filename + ";";
}
temp = temp.substring(0, temp.length() - 1);
// 写入所有可下载的文件
channel.write(ByteBuffer.wrap(temp.getBytes()));
channel.close();
} else if (attach.equals("[no_file]")) {
channel.write(ByteBuffer.wrap("no such file".getBytes()));
channel.close();
} else if (attach.equals("[error_command]")) {
channel.write(ByteBuffer.wrap("error command".getBytes()));
channel.close();
} else if (attach.startsWith("[get]")) {
channel.write(encoder.encode(CharBuffer.wrap("开始下载")));
File file = new File(server_path, attach.split(":")[1]);
DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream(file)));
int len = 0;
byte[] buf = new byte[1024];
while ((len = dis.read(buf)) != -1) {
channel.write(ByteBuffer.wrap(buf, 0, len));
}
dis.close();
System.out.println("下载完成");
channel.close();
} else if (attach.startsWith("[put]")) {
channel.write(encoder.encode(CharBuffer.wrap("开始上传")));
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(new File(
server_path, attach.split(":")[1]))));
int len = channel.read(buffer);
while (len >= 0) {
if (len != 0) {
buffer.flip();
}
dos.write(buffer.array(), 0, len);
len = channel.read(buffer);
}
dos.close();
channel.close();
System.out.println("上传完毕");
}
}
}
}
作为服务器,我们首先在C盘根目录下新建一个file_center的目录,该目录下的的所有文件可提供下载,上传的文件也是保存在这个目录下
3、客户端FileClient的实现代码如下
package org.filecenter;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
public class FileClient {
private int ServerPort = 12345;
private String ServerAddress = "127.0.0.1";
private String CMD = "list";
private String local_file = "";
private String server_file = "";
class SocketThread extends Thread {
@Override
public void run() {
try {
File file = new File(local_file);
if (!file.exists() && CMD.equals("put")) {
System.out.println("本地没有这个文件,无法上传!");
return;
}
InetAddress loalhost = InetAddress.getLocalHost();
Socket socket = new Socket(ServerAddress, ServerPort, loalhost,
44);
// 服务器IP地址 端口号 本机IP 本机端口号
DataInputStream dis = new DataInputStream(socket
.getInputStream());
DataOutputStream dos = new DataOutputStream(socket
.getOutputStream());
// dos.writeUTF(GetOrPut+" "+server_file);//服务器端如果是do的socket,writeUTF和writeUTF对接
dos.write((CMD + " " + server_file).getBytes());
dos.flush();
// String tempString = dis.writeUTF();
byte[] buf = new byte[1024];
int len = dis.read(buf);
String tempString = new String(buf, 0, len);// 服务器反馈的信息
// System.out.println(tempString);
if (tempString.equals("no such file")) {
System.out.println("服务器没有这个文件,无法下载!");
dos.close();
dis.close();
socket.close();
return;
}
if (tempString.startsWith("开始下载")) {
DataOutputStream fileOut = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(file)));
while ((len = dis.read(buf)) != -1) {
fileOut.write(buf, 0, len);
}
System.out.println("下载完毕!");
fileOut.close();
dos.close();
dis.close();
socket.close();
} else if (tempString.equals("开始上传")) {
System.out.println("正在上传文件.......");
DataInputStream fis = new DataInputStream(
new BufferedInputStream(new FileInputStream(file)));
while ((len = fis.read(buf)) != -1) {
dos.write(buf, 0, len);
}
dos.flush();
System.out.println("上传完毕!");
fis.close();
dis.close();
dos.close();
socket.close();
}
else if(tempString.equals("list files"))
{
len=dis.read(buf);
String temp=new String(buf,0,len);
String[] strs=temp.split(";");
System.out.println("文件列表");
for(String str:strs)
{
System.out.println(str);
}
dis.close();
dos.close();
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public boolean checkCommand(String command) {
if (!command.startsWith("put") && !command.startsWith("get") && !command.equals("list")) {
System.out.println("输入命令错误");
return false;
}
int index = -1;
String temp = "";
String[] tempStrings = null;
if ((index = command.indexOf("-h")) > 0) {
temp = command.substring(index + 3);
temp = temp.substring(0, temp.indexOf(' '));
ServerAddress = temp;
}
if ((index = command.indexOf("-p")) > 0) {
temp = command.substring(index + 3);
temp = temp.substring(0, temp.indexOf(' '));
ServerPort = Integer.valueOf(temp);
}
tempStrings = command.split(" ");
if (command.startsWith("put")) {
CMD = "put";
local_file = tempStrings[tempStrings.length - 2];
server_file = tempStrings[tempStrings.length - 1];
} else if (command.startsWith("get")) {
CMD = "get";
local_file = tempStrings[tempStrings.length - 1];
server_file = tempStrings[tempStrings.length - 2];
}
else
{
CMD = "list";
}
return true;
}
public static void main(String[] args) {
FileClient client = new FileClient();
Scanner sc = new Scanner(System.in);
String commandString = "";
do {
System.out.println("请输入命令:");
commandString = sc.nextLine();
} while (!client.checkCommand(commandString));
FileClient.SocketThread a = client.new SocketThread();
a.start();
}
}
这里我们提供了三种命令方式
1)list命令,列出服务器中所有可供下载的文件列表
2)get命令,下载服务器中的文件。示例:get test.txt d:\test2.txt
3)put命令,上传文件至服务器。示例:put e:\hello.rar hello.rar
4、将上述两个java文件进行编译,然后开启两个命令窗口进行测试
四、总结
1、此文件服务器相对简单,有很多可以扩展和进行改善的地方。比如可以根据需要对文件的上传下载等功能可以开启独立的线程进行操作
2、在文件的读写方面也可以考虑nio的方式进行