NIO Socket實現檔案伺服器

cyxlzzs發表於2013-02-26

一、背景

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

版權宣告:本文為博主原創文章,未經博主允許不得轉載。

相關文章