diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a601a1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +### IntelliJ IDEA ### +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ +.idea + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/client/client.iml b/client/client.iml new file mode 100644 index 0000000..99e828d --- /dev/null +++ b/client/client.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/com/soroko/client/Client.java b/client/src/com/soroko/client/Client.java index 8dd8583..e37e045 100644 --- a/client/src/com/soroko/client/Client.java +++ b/client/src/com/soroko/client/Client.java @@ -1,7 +1,8 @@ package com.soroko.client; -import com.soroko.common.common.SendReceive; -import com.soroko.common.common.Message; +import com.soroko.common.FileMessage; +import com.soroko.common.SendReceive; +import com.soroko.common.Message; import java.io.IOException; import java.net.InetSocketAddress; @@ -12,40 +13,124 @@ public class Client { private InetSocketAddress address; private String username; private Scanner scanner; + private SendReceive connectionHandler; + private boolean FilesAreEmpty; public Client(InetSocketAddress address) { this.address = address; scanner = new Scanner(System.in); } + public void saveCommand() throws InterruptedException { + String filepath = ""; + String description = ""; + if (!FilesAreEmpty) { + System.out.println("Укажите папку, в которую необходимо загрузить файл из сервера"); + filepath = scanner.nextLine(); + System.out.println("Введите название файла из списка доступных файлов:"); + description = scanner.nextLine(); + } + FileMessage fileMessage = new FileMessage(description, filepath); + fileMessage.setFilePath(filepath); + try { + connectionHandler.sendFileDescription(fileMessage); + } catch (IOException e) { + connectionHandler.close(); + } + } + public void loadCommand() { + System.out.println("Введите путь, по которому необходимо загрузить файл на сервер"); + String filepath = scanner.nextLine(); + System.out.println("Введите описание файла:"); + String description = scanner.nextLine(); + System.out.println("Введите размер файла в мегабайтах:"); + int size = scanner.nextInt(); + FileMessage fileMessage = new FileMessage(description, size); + fileMessage.setFilePath(filepath); + try { + connectionHandler.sendFileDescription(fileMessage); + } catch (IOException e) { + connectionHandler.close(); + } + } - public void startClient() { - System.out.println("Введите имя"); - username = scanner.nextLine(); - while (true) { - System.out.println("Введите текст сообщения"); - String text = scanner.nextLine(); - if (text.equals("/exit")) break; - try (SendReceive connectionHandler - = new SendReceive(new Socket( - address.getHostName(), - address.getPort() - ))) { + private class Writer extends Thread { + public void run() { + boolean isLoadCommand = false; + boolean isSaveCommand = false; + while (true) { + if (isLoadCommand) { + loadCommand(); + } else if (isSaveCommand) { + try { + saveCommand(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } else System.out.println("Введите текст сообщения"); + isLoadCommand = false; + isSaveCommand = false; + String text = scanner.nextLine(); + if (text.equalsIgnoreCase("/loadfile")) isLoadCommand = true; + if (text.equalsIgnoreCase("/savefile")) isSaveCommand = true; + if (text.equalsIgnoreCase("/exit")) { + System.out.println("Соединение прекращено"); + connectionHandler.close(); + break; + } Message message = new Message(username); message.setText(text); try { connectionHandler.send(message); - Message fromServer = connectionHandler.receive(); - System.out.println(fromServer.getText()); - } catch (IOException e) { + } catch (IOException ignored) { + connectionHandler.close(); } + } + } + } - } catch (Exception e) { + private class Reader extends Thread { + public void run() { + while (true) { + Message message; + try { + message = connectionHandler.receive(); + FilesAreEmpty = message.getFilesAreEmpty(); + if (message.getText().equalsIgnoreCase("/exit")) { + connectionHandler.close(); + break; + } + } catch (IOException ignored) { + connectionHandler.close(); + break; + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + System.out.println(message.getText()); } } } + + public void createConnection() throws IOException { + connectionHandler = new SendReceive( + new Socket(address.getHostName(), address.getPort())); + } + + public void startClient() { + System.out.println("Введите имя"); + username = scanner.nextLine(); + try { + createConnection(); + } catch (IOException e) { + throw new RuntimeException(e); + } + new Writer().start(); + new Reader().start(); + } } + + diff --git a/common/common.iml b/common/common.iml new file mode 100644 index 0000000..c90834f --- /dev/null +++ b/common/common.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/common/src/com/soroko/common/FileMessage.java b/common/src/com/soroko/common/FileMessage.java new file mode 100644 index 0000000..7c5fda1 --- /dev/null +++ b/common/src/com/soroko/common/FileMessage.java @@ -0,0 +1,45 @@ +package com.soroko.common; + +import java.io.Serializable; +import java.nio.file.Paths; + +public class FileMessage implements Serializable { + private String description; + private int size; + private String filePath; + + + public FileMessage(String description, String filepath) { + this.description = description; + this.filePath = filepath; + } + + public FileMessage(String description, int size) { + this.description = description; + this.size = size; + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public String getDescription() { + return description; + } + + public int getSize() { + return size; + } + + @Override + public String toString() { + String fileName = Paths.get(filePath).getFileName().toString(); + return "\n" + "название: " + fileName + + ", описание: " + description + + ", размер в мб: " + size; + } +} diff --git a/common/src/com/soroko/common/common/Message.java b/common/src/com/soroko/common/Message.java similarity index 51% rename from common/src/com/soroko/common/common/Message.java rename to common/src/com/soroko/common/Message.java index 7dde2df..b18f7ce 100644 --- a/common/src/com/soroko/common/common/Message.java +++ b/common/src/com/soroko/common/Message.java @@ -1,12 +1,12 @@ -package com.soroko.common.common; +package com.soroko.common; import java.io.Serializable; -import java.time.LocalDateTime; public class Message implements Serializable { - String sender; - String text; - LocalDateTime sentAt; + private String sender; + private String text; + private String sentAt; + private boolean FilesAreEmpty; public Message(String sender) { this.sender = sender; @@ -16,10 +16,6 @@ public String getSender() { return sender; } - public void setSender(String sender) { - this.sender = sender; - } - public String getText() { return text; } @@ -28,11 +24,19 @@ public void setText(String text) { this.text = text; } - public LocalDateTime getSentAt() { + public String getSentAt() { return sentAt; } - public void setSentAt(LocalDateTime sentAt) { + public void setSentAt(String sentAt) { this.sentAt = sentAt; } + + public boolean getFilesAreEmpty() { + return FilesAreEmpty; + } + + public void setFilesAreEmpty(boolean filesAreEmpty) { + FilesAreEmpty = filesAreEmpty; + } } diff --git a/common/src/com/soroko/common/SendReceive.java b/common/src/com/soroko/common/SendReceive.java new file mode 100644 index 0000000..b522159 --- /dev/null +++ b/common/src/com/soroko/common/SendReceive.java @@ -0,0 +1,63 @@ +package com.soroko.common; + +import java.io.*; +import java.net.Socket; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +public class SendReceive implements AutoCloseable { + private ObjectOutputStream outputStream; + private ObjectInputStream inputStream; + private Socket socket; + + public SendReceive(Socket socket) throws IOException { + this.socket = Objects.requireNonNull(socket); + outputStream = new ObjectOutputStream(socket.getOutputStream()); + inputStream = new ObjectInputStream(socket.getInputStream()); + } + + public void send(Message message) throws IOException { + DateTimeFormatter formatTime = DateTimeFormatter.ofPattern("HH:mm:ss"); + LocalTime localTime = LocalTime.now(); + message.setSentAt(localTime.format(formatTime)); + outputStream.writeObject(message); + outputStream.flush(); + } + + public void sendFileDescription(FileMessage fileMessage) throws IOException { + outputStream.writeObject(fileMessage); + outputStream.flush(); + } + + public Message receive() throws IOException, ClassNotFoundException { + try { + return (Message) inputStream.readObject(); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + public FileMessage receiveFileDescription() throws IOException, ClassNotFoundException { + try { + return (FileMessage) inputStream.readObject(); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + @Override + public void close() { + try { + if (!socket.isClosed()) { + inputStream.close(); + outputStream.close(); + socket.close(); + } + } catch (IOException ignored) { + System.out.println("Проблема при закрытии потоков"); + } + + } +} diff --git a/common/src/com/soroko/common/WriteMessage.java b/common/src/com/soroko/common/WriteMessage.java new file mode 100644 index 0000000..faa31e6 --- /dev/null +++ b/common/src/com/soroko/common/WriteMessage.java @@ -0,0 +1,41 @@ +package com.soroko.common; + +import java.io.IOException; +import java.util.Scanner; + +public class WriteMessage extends Thread { + private SendReceive connectionHandler; + private String username; + private Scanner scanner; + private int counter; + + public WriteMessage(SendReceive sendReceive, String username, Scanner scanner) { + this.connectionHandler = sendReceive; + this.username = username; + this.scanner = scanner; + } + + @Override + public void run() { + System.out.println("Введите имя"); + username = scanner.nextLine(); + + while (true) { + System.out.println("Введите текст сообщения"); + String text = scanner.nextLine(); + if (text.equals("/exit")) break; + Message message = new Message(username); + message.setText(text); + try { + Message fromServer; + try { + fromServer = connectionHandler.receive(); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + System.out.println(fromServer.getText()); + } catch (IOException e) { + } + } + } +} diff --git a/common/src/com/soroko/common/common/SendReceive.java b/common/src/com/soroko/common/common/SendReceive.java deleted file mode 100644 index 9de2152..0000000 --- a/common/src/com/soroko/common/common/SendReceive.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.soroko.common.common; - -import java.io.*; -import java.net.Socket; -import java.time.Instant; -import java.time.LocalDateTime; - -public class SendReceive implements AutoCloseable { - private ObjectOutputStream outputStream; - private ObjectInputStream inputStream; - private Socket socket; - - - - public SendReceive(Socket socket) throws IOException { - outputStream = new ObjectOutputStream(socket.getOutputStream()); - inputStream = new ObjectInputStream(socket.getInputStream()); - } - - public void send(Message message) throws IOException { - message.setSentAt(LocalDateTime.now()); - outputStream.writeObject(message); - outputStream.flush(); - } - - public Message receive() throws IOException, ClassNotFoundException { - try { - return (Message) inputStream.readObject(); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - - @Override - public void close() throws Exception { - inputStream.close(); - outputStream.close(); - socket.close(); - } -} diff --git a/common/src/module-info.java b/common/src/module-info.java index bb555eb..7483ae2 100644 --- a/common/src/module-info.java +++ b/common/src/module-info.java @@ -1,3 +1,3 @@ module common { - exports com.soroko.common.common; + exports com.soroko.common; } \ No newline at end of file diff --git a/server/server.iml b/server/server.iml new file mode 100644 index 0000000..99e828d --- /dev/null +++ b/server/server.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/server/src/com/soroko/server/Server.java b/server/src/com/soroko/server/Server.java index dbc1fe5..fc88f2a 100644 --- a/server/src/com/soroko/server/Server.java +++ b/server/src/com/soroko/server/Server.java @@ -1,57 +1,33 @@ package com.soroko.server; +import com.soroko.common.*; -import com.soroko.common.common.Message; -import com.soroko.common.common.SendReceive; - -import java.io.IOException; +import java.io.*; import java.net.ServerSocket; import java.net.Socket; -import java.util.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; public class Server { - private int port; - private int reqCounter = 0; - private long elapsedTime; - private int helpCounter = 0; - private int pingCounter = 0; - private int requestsCounter = 0; - private int popularCounter = 0; - - private Map requests = new HashMap<>(); + public static final String SERVER_STORAGE_LOCATION + = "C:\\Users\\yuriy\\IdeaProjects\\socketLesson\\server\\src\\com\\soroko\\server\\"; + private final int port; + private final List messages = new CopyOnWriteArrayList<>(); + private final List connectionHandlers = new CopyOnWriteArrayList<>(); + private final List fileMessages = new CopyOnWriteArrayList<>(); + private final int fileSize; + private final int amountOfSymbols; public Server(int port) { this.port = port; - } - - public String makeRequest(String text) { - if (text.equals("/help")) { - reqCounter++; - requests.put("/help", ++helpCounter); - return "/help - список доступных запросов и их описание\n" + - "/ping - время ответа сервера\n" + - "/requests - количество успешно обработанных запросов\n" + - "/popular - название самого популярного запроса\n" + - "/exit - завершить соединение с сервером"; - } else if (text.equals("/ping")) { - reqCounter++; - requests.put("ping", ++pingCounter); - return elapsedTime + " ms"; - } else if (text.equals("/requests")) { - reqCounter++; - requests.put("requests", ++requestsCounter); - return String.valueOf(reqCounter); - } else if (text.equals("/popular")) { - reqCounter++; - requests.put("popular", ++popularCounter); - return requests.entrySet().stream() - .max(Comparator.comparingInt(Map.Entry::getValue)) - .orElseGet(null) - .toString(); - } else { - return "Невозможно обработать запрос"; - } + fileSize = 10; + amountOfSymbols = 200; } public void startServer() { @@ -59,20 +35,179 @@ public void startServer() { while (true) { try { Socket socket = serverSocket.accept(); - long startTime = System.nanoTime(); - SendReceive connetionHandler = new SendReceive(socket); - Message fromClient = connetionHandler.receive(); - String fromServer = makeRequest(fromClient.getText()); - Message message = new Message("server"); - message.setText(fromServer); - connetionHandler.send(message); - elapsedTime = (System.nanoTime() - startTime) / 1_000_000L; + SendReceive connectionHandler = new SendReceive(socket); + connectionHandlers.add(connectionHandler); + new ThreadForClient(connectionHandler).start(); } catch (Exception e) { - System.out.println("Проблема с соединением"); + System.out.println("Проблема с установкой нового соединения"); } } } catch (IOException e) { System.out.println("Ошибка запуска сервера"); + throw new RuntimeException(e); + } + } + + public static void copy(File source, File destination) throws IOException { + Files.copy(source.toPath(), destination.toPath()); + } + + private class ThreadForClient extends Thread { + private final SendReceive connectionHandler; + private boolean selfMessageIsActive; + private boolean loadFileFlag; + + public ThreadForClient(SendReceive connectionHandler) { + this.connectionHandler = connectionHandler; + } + + public synchronized void showFiles() { + Message message = new Message("server"); + String intro = "Список доступных файлов:"; + String fileInformation = fileMessages.stream() + .map(FileMessage::toString) + .collect(Collectors.joining(", ")); + if (fileMessages.isEmpty()) { + message.setFilesAreEmpty(true); + message.setText("Доступных для скачивания файлов не обнаружено"); + } else { + message.setText(intro + fileInformation); + } + try { + connectionHandler.send(message); + } catch (IOException e) { + connectionHandler.close(); + } + } + + public synchronized void loadFile(FileMessage fileMessage) { + int randomName = (int) (Math.random() * 1000); + char[] descriptionChars = fileMessage.getDescription().toCharArray(); + File fileSource = new File(fileMessage.getFilePath()); + String fileName = SERVER_STORAGE_LOCATION + fileSource.getName(); + String answer; + File fileDestination; + Path path = Paths.get(fileName); + if (Files.exists(path)) { + fileDestination = new File((SERVER_STORAGE_LOCATION + randomName + fileSource.getName())); + } else { + fileDestination = new File(fileName); + } + Message message = new Message("server"); + if (!fileSource.isDirectory() && fileSource.exists()) { + if (fileMessage.getSize() <= fileSize && descriptionChars.length <= amountOfSymbols) { + try { + copy(fileSource, fileDestination); + if (fileDestination.isFile()) { + fileMessage.setFilePath(fileDestination.getName()); + fileMessages.add(fileMessage); + message.setFilesAreEmpty(false); + } + answer = "Файл " + fileDestination.getName() + " был успешно загружен"; + message.setText(answer); + loadFileFlag = true; + selfMessageIsActive = true; + messages.add(message); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } else { + answer = "Файл по указанному пути не найден" + + " или содержит слишком большой объем информации"; + message.setText(answer); + try { + connectionHandler.send(message); + } catch (IOException e) { + connectionHandler.close(); + } + } + } + + synchronized void saveFile(FileMessage fileMessage) { + File fileSource = new File((SERVER_STORAGE_LOCATION + fileMessage.getDescription())); + File fileDestination = new File(fileMessage.getFilePath() + fileMessage.getDescription()); + String answer; + Message message = new Message("server"); + if (fileMessages.isEmpty()) message.setFilesAreEmpty(true); + try { + copy(fileSource, fileDestination); + if (fileDestination.isFile()) { + answer = "Файл " + fileDestination.getName() + " был успешно сохранен"; + message.setText(answer); + connectionHandler.send(message); + } + } catch (IOException e) { + answer = "Неверное имя файла или файла нет в списке, " + + "либо файл с таким именем уже существует"; + message.setText(answer); + try { + connectionHandler.send(message); + } catch (IOException ex) { + connectionHandler.close(); + } + } + } + + public FileMessage createFileMessage() { + FileMessage fileMessage; + try { + fileMessage = connectionHandler.receiveFileDescription(); + } catch (IOException e) { + connectionHandlers.remove(connectionHandler); + return null; + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + return fileMessage; + } + + @Override + public void run() { + while (true) { + Message fromClient; + try { + fromClient = connectionHandler.receive(); + } catch (IOException e) { + connectionHandlers.remove(connectionHandler); + return; + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + if (fromClient != null && !fromClient.getText().equals("/files") && + !fromClient.getText().equals("/loadfile") && !fromClient.getText().equals("/savefile") && + !fromClient.getText().isEmpty()) { + Message message = new Message("server: " + fromClient.getSender()); + message.setText(fromClient.getSentAt() + " " + fromClient.getSender() + ": " + fromClient.getText()); + messages.add(message); + } else if (Objects.requireNonNull(fromClient).getText().equals("/files")) { + selfMessageIsActive = true; + showFiles(); + } else if (fromClient.getText().equals("/loadfile")) { + selfMessageIsActive = true; + FileMessage fileMessage = createFileMessage(); + loadFile(Objects.requireNonNull(fileMessage)); + } else if (fromClient.getText().equals("/savefile")) { + selfMessageIsActive = true; + showFiles(); + FileMessage fileMessage = createFileMessage(); + saveFile(Objects.requireNonNull(fileMessage)); + } + Message message = null; + if (!messages.isEmpty()) message = messages.getLast(); + for (SendReceive handler : connectionHandlers) { + try { + if ((handler != this.connectionHandler && !this.selfMessageIsActive) || + (handler == this.connectionHandler && this.loadFileFlag)) { + handler.send(Objects.requireNonNull(message)); + } + } catch (IOException e) { + connectionHandlers.remove(handler); + } + } + selfMessageIsActive = false; + loadFileFlag = false; + } } } -} +} \ No newline at end of file