Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@ language: java
sudo: false

notifications:
email: false
email: false

env:
- TEST_DIR=SimpleFTP
script: cd $TEST_DIR && ./gradlew clean check
14 changes: 14 additions & 0 deletions SimpleFTP/FTPClient/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
group 'ru.spbau.dkaznacheev'
version '1.0-SNAPSHOT'

apply plugin: 'java'

sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package ru.spbau.dkaznacheev.simpleftp;

import java.io.*;
import java.net.ConnectException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class Client {

/**
* Downloads a file from sever
* @param in inputstream of a socket
* @param name name of the file
*/
private static void receiveFile(DataInputStream in, String name) throws IOException{
long length = in.readLong();
if (length == 0) {
System.out.println("No such file");
return;
}
try (FileOutputStream out = new FileOutputStream(simpleName(name))) {
byte[] buffer = new byte[4096];
long remaining = length;
int read;
while ((read = in.read(buffer, 0, (int) Math.min(buffer.length, remaining))) > 0) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вместо этого лучше воспользоваться IOUtils из common-io

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Пока -1

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

попробовал IOUtils.copy, получилось удобно, но, к сожалению, так можно передать только один файл, а потом стрим нужно закрыть, соответственно, придется закрывать соединение. Поискал способы передавать файлы, не закрывая стрим, рекомендуют мой старый способ. Поэтому оставляю старый

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не очень понял, какой стрим должен закрыться
https://commons.apache.org/proper/commons-io/javadocs/api-2.5/org/apache/commons/io/IOUtils.html#copy(java.io.InputStream,%20java.io.OutputStream)
Здесь вроде про это не написано и в коде реализации я не вижу ничего про закрытие

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Как IOUtils.copy на клиенте поймёт, что приём файла закончился?
Возможно, можно отправлять размер файла, но copy просто считывает входной стрим до конца, а копирования стольки-то байт я не нашёл.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remaining -= read;
out.write(buffer);
}
}
}

/**
* Returns simple file name of the filepath.
* @param path path to file
* @return filename
*/
private static String simpleName(String path) {
return new File(path).getName();
}

public static void main(String[] args) {
try (
Socket socket = new Socket("127.0.0.1", 8080);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
DataInputStream in = new DataInputStream(socket.getInputStream());
Scanner stdIn = new Scanner(System.in)
) {
ResponseCode fromServer;
String fromUser;
boolean exit = false;
while (!exit) {
fromUser = stdIn.nextLine();
if (fromUser != null) {
out.println(fromUser);
}
fromServer = ResponseCode.values()[in.readInt()];
switch (fromServer) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Предлагаю воспользоваться технологиями ООП и вынести логику клиента в отдельный класс, отделив ее от работы с консольным вводом-выводом.
Это автоматически поможет с тестами.
Пока -1

case CLOSE_CONNECTION: {
exit = true;
break;
}
case INVALID_COMMAND: {
System.out.println("Invalid command");
break;
}
case FOLDER_DESCRIPTION: {
FolderDescription description = FolderDescription.read(in);
description.print();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я бы предпочел, чтобы FolderDescription просто возвращал описание файлов в удобном для принтинга виде.
Тогда в тот момент, когда мы добавим GUI, его не придется переписывать.
Пока -0.5

break;
}
case FILE_SEND: {
String[] parts = fromUser.split(" ");
receiveFile(in, parts[1]);
break;
}
}
}

} catch (UnknownHostException e) {
System.err.println("Unknown host");
} catch (ConnectException e) {
System.err.println("Connection refused");
} catch (IOException e) {
e.printStackTrace();
}
}
}

14 changes: 14 additions & 0 deletions SimpleFTP/FTPServer/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
group 'ru.spbau.dkaznacheev'
version '1.0-SNAPSHOT'

apply plugin: 'java'

sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package ru.spbau.dkaznacheev.simpleftp;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

import static ru.spbau.dkaznacheev.simpleftp.ResponseCode.*;

/**
* Simple FTP server that can handle multiple clients and send files over sockets.
*/
public class Server {

/**
* Sends file over ServerSocket's DataOutputStream.
* @param name filename
* @param out output stream
*/
private static void sendFile(String name, DataOutputStream out) throws IOException{
File file = new File(name);
if (!file.exists()) {
out.writeLong(0);
return;
}
out.writeLong(file.length());
byte[] buffer = new byte[4096];
int read;
try (FileInputStream in = new FileInputStream(file)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

То же самое про IOUtils

while ((read = in.read(buffer)) > -1) {
out.write(buffer, 0, read);
}
}

}

/**
* Client handler, runs in a separate thread as long as there is a connectin with a client.
*/
private static class FTPThread extends Thread {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вместо наследования потока правильней передать в конструктор реализацию Runnable.
Пока -1


/**
* Socket f server-client connection
*/
private Socket socket;

public FTPThread(Socket socket) {
this.socket = socket;
}

@Override
public void run() {
try (
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()))
) {
String inputLine;
while ((inputLine = in.readLine()) != null) {
if (inputLine.equals("0")) {
out.writeInt(CLOSE_CONNECTION.ordinal());
break;
}

String[] parts = inputLine.split(" ");
if (parts.length != 2) {
out.writeInt(INVALID_COMMAND.ordinal());
continue;
}
String command = parts[0];
String path = parts[1];

if (command.equals("1")) {
FolderDescription description = FolderDescription.describeFolder(path);
out.writeInt(FOLDER_DESCRIPTION.ordinal());
description.write(out);
} else if (command.equals("2")) {
out.writeInt(FILE_SEND.ordinal());
sendFile(path, out);
} else {
out.writeInt(INVALID_COMMAND.ordinal());
}
}
socket.close();
} catch (SocketException e) {
System.out.println("Goodbye");
}
catch (IOException e) {
System.err.println("IOException on thread " + Thread.currentThread().getName());
}
}
}

public static void main(String[] args) {
ServerSocket serverSocket;
try {
serverSocket = new ServerSocket(8080);
} catch (IOException e) {
System.err.println("Error creating server");
return;
}
while (true) {
try {
Socket clientSocket = serverSocket.accept();
new FTPThread(clientSocket).start();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вообще, ручное создание потоков инстансов Thread -- сомнительная практика. В частности это может привести к тому, что будет создано неконтролируемое число потоков, что приведет к деградации производительности JVM.
Проще всего воспользоваться тредпулом из java.util.concurrent.
Пока -1

} catch (IOException e) {
System.err.println("Exception on client connecting");
}
}
}
}
14 changes: 14 additions & 0 deletions SimpleFTP/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
group 'ru.spbau.dkaznacheev.simpleftp'
version '1.0-SNAPSHOT'

apply plugin: 'java'

sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
Binary file added SimpleFTP/gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
6 changes: 6 additions & 0 deletions SimpleFTP/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#Wed May 30 23:52:07 MSK 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-rc-2-bin.zip
Loading