NIO Socket實現文件服務器

一、背景

       很多時候我們都會用FTP工具來實現文件的上傳下載功能,於是琢磨着也用Java的相關知識來自己實現一個簡單的文件服務器

二、NIO Socket

       考慮到文件的上傳下載其實就是客戶端和服務器進行通訊,然後進行數據交換。此時就可以採用Socket來實現。從JDK1.4版本以來java提供了更加高效的非阻塞形式的Socket,也就是NIO方式的Socket,通過通道Channel和緩衝器Buffer的方式來進行數據的讀寫。關於NIO的介紹,這裏找了網上的一篇文章,可以參考一下 JAVA NIO簡介

三、實施步驟

       1、實現文件的上傳下載等,需要服務器和客戶端兩部分。服務端我們取名爲FileCenter,客戶端我們取名爲FileClient

       2、首先實現FileCenter,具體代碼如下,理解可參照註釋

[java] view plain copy
  1. package org.filecenter;  
  2.   
  3. import java.io.BufferedInputStream;  
  4. import java.io.BufferedOutputStream;  
  5. import java.io.DataInputStream;  
  6. import java.io.DataOutputStream;  
  7. import java.io.File;  
  8. import java.io.FileInputStream;  
  9. import java.io.FileOutputStream;  
  10. import java.io.IOException;  
  11. import java.net.InetSocketAddress;  
  12. import java.net.ServerSocket;  
  13. import java.nio.ByteBuffer;  
  14. import java.nio.CharBuffer;  
  15. import java.nio.channels.SelectionKey;  
  16. import java.nio.channels.Selector;  
  17. import java.nio.channels.ServerSocketChannel;  
  18. import java.nio.channels.SocketChannel;  
  19. import java.nio.charset.Charset;  
  20. import java.nio.charset.CharsetDecoder;  
  21. import java.nio.charset.CharsetEncoder;  
  22. import java.util.Iterator;  
  23.   
  24. /** 
  25.  * 文件下中心,命令模式如下  
  26.  * 顯示文件:list  
  27.  *      列出所有可下載的文件  
  28.  * 下載文件:get server_file [local_file]  
  29.  *      server_file服務器中的文件名,local_file本地存儲路徑 
  30.  * 上傳文件:put local_file [server_file]  
  31.  *      local_file本地文件路徑,server_file服務器存儲文件名 
  32.  *  
  33.  * @author cyxl 
  34.  *  
  35.  */  
  36. public class FileCenter {  
  37.     private static Selector selector; // 選擇器  
  38.     private static final int server_port = 12345// 服務器端口  
  39.     private static CharsetDecoder decoder = Charset.forName("GB2312")  
  40.             .newDecoder(); // 字節轉字符  
  41.     private static CharsetEncoder encoder = Charset.forName("GB2312")  
  42.             .newEncoder(); // 字符轉字節  
  43.     private static ByteBuffer buffer = ByteBuffer.allocate(1024);  
  44.     private static final String server_path = "C:\\file_center\\"// 服務器文件路徑  
  45.   
  46.     public static void main(String[] args) {  
  47.         try {  
  48.             selector = Selector.open();// 打開選擇器  
  49.             ServerSocketChannel serverChannel = ServerSocketChannel.open();  
  50.             ServerSocket server = serverChannel.socket();  
  51.             server.bind(new InetSocketAddress(server_port));  
  52.             serverChannel.configureBlocking(false);  
  53.             serverChannel.register(selector, SelectionKey.OP_ACCEPT);  
  54.             System.out.println("等待客戶端連接……");  
  55.             while (true) {  
  56.                 selector.select();  
  57.                 Iterator<SelectionKey> itr = selector.selectedKeys().iterator();  
  58.                 while (itr.hasNext()) {  
  59.                     SelectionKey key = itr.next();  
  60.                     itr.remove();  
  61.                     process(key);  
  62.                 }  
  63.             }  
  64.         } catch (IOException e) {  
  65.             e.printStackTrace();  
  66.         }  
  67.     }  
  68.   
  69.     public static void process(SelectionKey key) throws IOException {  
  70.         if (key.isAcceptable()) {  
  71.             // 連接  
  72.             ServerSocketChannel serverChannel = (ServerSocketChannel) key  
  73.                     .channel();  
  74.             SocketChannel client = serverChannel.accept();  
  75.             client.configureBlocking(false);  
  76.             SelectionKey sKey = client.register(selector, SelectionKey.OP_READ);  
  77.             sKey.attach("[r_cmd]"); // 連接好後讀取客戶端發來的命令  
  78.         } else if (key.isReadable()) {  
  79.             // 讀取  
  80.             SocketChannel channel = (SocketChannel) key.channel();  
  81.   
  82.             String attach = key.attachment().toString();  
  83.             if (attach.equals("[r_cmd]")) {  
  84.                 // 獲取命令  
  85.                 int len = channel.read(buffer);  
  86.                   
  87.                 if (len > 0) {  
  88.                     buffer.flip();  
  89.                     String cmd = "";  
  90.                     CharBuffer charBuffer = decoder.decode(buffer);  
  91.                     cmd = charBuffer.toString();  
  92.                       
  93.                     SelectionKey sKey = channel.register(selector,  
  94.                             SelectionKey.OP_WRITE);  
  95.                     System.out.println("cmd:" + cmd);  
  96.                     if (cmd.trim().equals("list")) {  
  97.                         sKey.attach("[list_file]");  
  98.                     } else {  
  99.                         String[] temp = cmd.split(" ");  
  100.                         if (temp.length >= 2) {  
  101.                             cmd = temp[0];  
  102.                             String filename = temp[1];  
  103.   
  104.                             if (cmd.equals("get")) {  
  105.                                 // 下載  
  106.                                 File file = new File(server_path, filename);  
  107.                                 if (file.exists()) {  
  108.                                     sKey.attach("[get]:" + filename);  
  109.                                 } else {  
  110.                                     sKey.attach("[no_file]");  
  111.                                 }  
  112.                             } else if (cmd.equals("put")) {  
  113.                                 // 上傳  
  114.                                 sKey.attach("[put]:" + filename);  
  115.                             } else {  
  116.                                 // 錯誤命令格式  
  117.                                 sKey.attach("[error_command]");  
  118.                             }  
  119.                         }  
  120.   
  121.                     }  
  122.                 } else {  
  123.                     channel.close();  
  124.                 }  
  125.             }  
  126.             buffer.clear();  
  127.         } else if (key.isWritable()) {  
  128.             // 寫入  
  129.             SocketChannel channel = (SocketChannel) key.channel();  
  130.             String attach = key.attachment().toString();  
  131.             if (attach.startsWith("[list_file]")) {  
  132.                 channel.write(encoder.encode(CharBuffer.wrap("list files")));  
  133.                 File file = new File(server_path);  
  134.                 String[] filenames = file.list();  
  135.                 String temp = "";  
  136.                 for (String filename : filenames) {  
  137.                     temp += filename + ";";  
  138.                 }  
  139.                 temp = temp.substring(0, temp.length() - 1);  
  140.                 // 寫入所有可下載的文件  
  141.                 channel.write(ByteBuffer.wrap(temp.getBytes()));  
  142.   
  143.                 channel.close();  
  144.             } else if (attach.equals("[no_file]")) {  
  145.                 channel.write(ByteBuffer.wrap("no such file".getBytes()));  
  146.   
  147.                 channel.close();  
  148.             } else if (attach.equals("[error_command]")) {  
  149.                 channel.write(ByteBuffer.wrap("error command".getBytes()));  
  150.   
  151.                 channel.close();  
  152.             } else if (attach.startsWith("[get]")) {  
  153.                 channel.write(encoder.encode(CharBuffer.wrap("開始下載")));  
  154.                 File file = new File(server_path, attach.split(":")[1]);  
  155.                 DataInputStream dis = new DataInputStream(  
  156.                         new BufferedInputStream(new FileInputStream(file)));  
  157.   
  158.                 int len = 0;  
  159.                 byte[] buf = new byte[1024];  
  160.                 while ((len = dis.read(buf)) != -1) {  
  161.                     channel.write(ByteBuffer.wrap(buf, 0, len));  
  162.                 }  
  163.                 dis.close();  
  164.                 System.out.println("下載完成");  
  165.                 channel.close();  
  166.             } else if (attach.startsWith("[put]")) {  
  167.                 channel.write(encoder.encode(CharBuffer.wrap("開始上傳")));  
  168.                 DataOutputStream dos = new DataOutputStream(  
  169.                         new BufferedOutputStream(new FileOutputStream(new File(  
  170.                                 server_path, attach.split(":")[1]))));  
  171.                 int len = channel.read(buffer);  
  172.                 while (len >= 0) {  
  173.                     if (len != 0) {  
  174.                         buffer.flip();  
  175.                     }  
  176.                     dos.write(buffer.array(), 0, len);  
  177.                     len = channel.read(buffer);  
  178.                 }  
  179.                 dos.close();  
  180.                 channel.close();  
  181.                 System.out.println("上傳完畢");  
  182.             }  
  183.         }  
  184.     }  
  185.   
  186. }  

作爲服務器,我們首先在C盤根目錄下新建一個file_center的目錄,該目錄下的的所有文件可提供下載,上傳的文件也是保存在這個目錄下

       3、客戶端FileClient的實現代碼如下

[java] view plain copy
  1. package org.filecenter;  
  2.   
  3. import java.io.BufferedInputStream;  
  4. import java.io.BufferedOutputStream;  
  5. import java.io.DataInputStream;  
  6. import java.io.DataOutputStream;  
  7. import java.io.File;  
  8. import java.io.FileInputStream;  
  9. import java.io.FileOutputStream;  
  10. import java.net.InetAddress;  
  11. import java.net.Socket;  
  12. import java.util.Scanner;  
  13.   
  14. public class FileClient {  
  15.   
  16.     private int ServerPort = 12345;  
  17.     private String ServerAddress = "127.0.0.1";  
  18.     private String CMD = "list";  
  19.     private String local_file = "";  
  20.     private String server_file = "";  
  21.   
  22.     class SocketThread extends Thread {  
  23.   
  24.         @Override  
  25.         public void run() {  
  26.             try {  
  27.                 File file = new File(local_file);   
  28.                 if (!file.exists() && CMD.equals("put")) {  
  29.                     System.out.println("本地沒有這個文件,無法上傳!");  
  30.                     return;  
  31.                 }  
  32.   
  33.                 InetAddress loalhost = InetAddress.getLocalHost();  
  34.                 Socket socket = new Socket(ServerAddress, ServerPort, loalhost,  
  35.                         44);  
  36.                 // 服務器IP地址 端口號 本機IP 本機端口號  
  37.                 DataInputStream dis = new DataInputStream(socket  
  38.                         .getInputStream());  
  39.                 DataOutputStream dos = new DataOutputStream(socket  
  40.                         .getOutputStream());  
  41.   
  42.                 // dos.writeUTF(GetOrPut+" "+server_file);//服務器端如果是do的socket,writeUTF和writeUTF對接  
  43.                 dos.write((CMD + " " + server_file).getBytes());  
  44.                 dos.flush();  
  45.   
  46.                 // String tempString = dis.writeUTF();  
  47.                 byte[] buf = new byte[1024];  
  48.                 int len = dis.read(buf);  
  49.                 String tempString = new String(buf, 0, len);// 服務器反饋的信息  
  50.   
  51.                 // System.out.println(tempString);  
  52.                 if (tempString.equals("no such file")) {  
  53.                     System.out.println("服務器沒有這個文件,無法下載!");  
  54.                     dos.close();  
  55.                     dis.close();  
  56.                     socket.close();  
  57.                     return;  
  58.                 }  
  59.   
  60.                 if (tempString.startsWith("開始下載")) {  
  61.                     DataOutputStream fileOut = new DataOutputStream(  
  62.                             new BufferedOutputStream(new FileOutputStream(file)));  
  63.   
  64.                     while ((len = dis.read(buf)) != -1) {  
  65.                         fileOut.write(buf, 0, len);  
  66.                     }  
  67.                     System.out.println("下載完畢!");  
  68.                     fileOut.close();  
  69.                     dos.close();  
  70.                     dis.close();  
  71.                     socket.close();  
  72.                 } else if (tempString.equals("開始上傳")) {  
  73.                     System.out.println("正在上傳文件.......");  
  74.                     DataInputStream fis = new DataInputStream(  
  75.                             new BufferedInputStream(new FileInputStream(file)));  
  76.   
  77.                     while ((len = fis.read(buf)) != -1) {  
  78.                         dos.write(buf, 0, len);  
  79.                     }  
  80.                     dos.flush();  
  81.                     System.out.println("上傳完畢!");  
  82.                     fis.close();  
  83.                     dis.close();  
  84.                     dos.close();  
  85.                     socket.close();  
  86.                 }  
  87.                 else if(tempString.equals("list files"))  
  88.                 {  
  89.                     len=dis.read(buf);  
  90.                     String temp=new String(buf,0,len);  
  91.                     String[] strs=temp.split(";");  
  92.                     System.out.println("文件列表");  
  93.                     for(String str:strs)  
  94.                     {  
  95.                         System.out.println(str);  
  96.                     }  
  97.                     dis.close();  
  98.                     dos.close();  
  99.                     socket.close();  
  100.                 }  
  101.   
  102.             } catch (Exception e) {  
  103.                 e.printStackTrace();  
  104.             }  
  105.         }  
  106.   
  107.     }  
  108.   
  109.     public boolean checkCommand(String command) {  
  110.         if (!command.startsWith("put") && !command.startsWith("get") && !command.equals("list")) {  
  111.             System.out.println("輸入命令錯誤");  
  112.             return false;  
  113.         }  
  114.   
  115.         int index = -1;  
  116.         String temp = "";  
  117.         String[] tempStrings = null;  
  118.   
  119.         if ((index = command.indexOf("-h")) > 0) {  
  120.             temp = command.substring(index + 3);  
  121.             temp = temp.substring(0, temp.indexOf(' '));  
  122.             ServerAddress = temp;  
  123.         }  
  124.         if ((index = command.indexOf("-p")) > 0) {  
  125.             temp = command.substring(index + 3);  
  126.             temp = temp.substring(0, temp.indexOf(' '));  
  127.             ServerPort = Integer.valueOf(temp);  
  128.         }  
  129.   
  130.         tempStrings = command.split(" ");  
  131.         if (command.startsWith("put")) {  
  132.             CMD = "put";  
  133.             local_file = tempStrings[tempStrings.length - 2];  
  134.             server_file = tempStrings[tempStrings.length - 1];  
  135.         } else if (command.startsWith("get")) {  
  136.             CMD = "get";  
  137.             local_file = tempStrings[tempStrings.length - 1];  
  138.             server_file = tempStrings[tempStrings.length - 2];  
  139.         }  
  140.         else  
  141.         {  
  142.             CMD = "list";  
  143.         }  
  144.   
  145.         return true;  
  146.     }  
  147.   
  148.     public static void main(String[] args) {  
  149.         FileClient client = new FileClient();  
  150.         Scanner sc = new Scanner(System.in);  
  151.         String commandString = "";  
  152.         do {  
  153.             System.out.println("請輸入命令:");  
  154.             commandString = sc.nextLine();  
  155.         } while (!client.checkCommand(commandString));  
  156.   
  157.         FileClient.SocketThread a = client.new SocketThread();  
  158.         a.start();  
  159.   
  160.     }  
  161. }  

      這裏我們提供了三種命令方式

1)list命令,列出服務器中所有可供下載的文件列表

2)get命令,下載服務器中的文件。示例:get test.txt d:\test2.txt

3)put命令,上傳文件至服務器。示例:put e:\hello.rar hello.rar

       4、將上述兩個java文件進行編譯,然後開啓兩個命令窗口進行測試

四、總結

       1、此文件服務器相對簡單,有很多可以擴展和進行改善的地方。比如可以根據需要對文件的上傳下載等功能可以開啓獨立的線程進行操作

       2、在文件的讀寫方面也可以考慮nio的方式進行






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章