一、背景
很多時候我們都會用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的方式進行