diff --git a/client/pom.xml b/client/pom.xml index 342bc8c..eedcb66 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -34,5 +34,25 @@ 1.0-SNAPSHOT compile + + org.openjfx + javafx-base + 11.0.2 + + + org.openjfx + javafx-graphics + 11.0.2 + + + org.openjfx + javafx-controls + 11.0.2 + + + org.openjfx + javafx-fxml + 16-ea+1 + \ No newline at end of file diff --git a/client/src/main/java/ClientHandler.java b/client/src/main/java/ClientHandler.java new file mode 100644 index 0000000..e30fde3 --- /dev/null +++ b/client/src/main/java/ClientHandler.java @@ -0,0 +1,81 @@ +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; + +public class ClientHandler extends ChannelInboundHandlerAdapter { + + private int nextLength;//размер следующего куска ожидаемого файла + private long fileLength;//длина файла + private long receivedFileLength;//сколько байт уже получено + private BufferedOutputStream out;//запись байтов в файл + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + ByteBuf buf = ((ByteBuf) msg); + while (buf.readableBytes() > 0) {//если пришло сообщение, пытаемся понять что это за сообщение + byte readed = buf.readByte();//вычитываем первый сигнальный байт + if (readed == 25) {//если пришел сигнальный байт соответствующий получению файла + receivedFileLength = 0L;//считаем то не получили ни одного байта от присылаемого файла + System.out.println("STATE CL: Start file receiving");//сообщаем о старте передачи файла + gettingFileNameLength(buf);//получаем длину имени файла + byte[] fileName = getFileName(buf);//получаем имя файла + System.out.println("STATE CL: Filename received - " + new String(fileName, "UTF-8"));//собираем из байтового массива строку с именем файла + out = new BufferedOutputStream(new FileOutputStream("client_storage/" + new String(fileName)));//открываем BufferedOutputStream для получения данных файла + gettingFileLength(buf);//получаем длину файла + getFile(buf);//получаем файл + +// } else { +// System.out.println("ERROR CL: Invalid first byte - " + readed);//сообщаем об ошибке + } +// if (buf.readableBytes()==0){buf.release();} + } + } + + //получаем длину имени файла + private void gettingFileNameLength(ByteBuf buf) { + if (buf.readableBytes() >= 4) {//пришло более 4 байт (длина имени файла = 1 int) + System.out.println("STATE CL: Get filename length"); + nextLength = buf.readInt();//вычитываем из входящего буфера 1 int + } + } + + //получаем имя файла + private byte[] getFileName(ByteBuf buf) throws Exception { + byte[] fileName = new byte[nextLength];//формируем байтовый массив для имени + if (buf.readableBytes() >= nextLength) {//проверяем есть ли в буфере байтов столько сколько в длине имени что бы не прочитать только часть имени файла + buf.readBytes(fileName);//вычитываем из буфера байты в байтовый массив + } + return fileName; + } + + //получаем длину файла + private void gettingFileLength(ByteBuf buf) { + if (buf.readableBytes() >= 8) {//если пришло более 8 байт (длина файла = 1 long) + fileLength = buf.readLong();//вычитываем из буфера 1 long (длину файла) + System.out.println("STATE CL: File length received - " + fileLength); + } + } + + //получаем файл + + private void getFile(ByteBuf buf) throws Exception { + while (buf.readableBytes() > 0) {//если в буфере есть непрочитанные байты + out.write(buf.readByte());//пишем побайтово непрочитанные байты в файл + receivedFileLength++;//говорим что получили еще один байт (подсчитываем количество полученных байтов) + if (fileLength == receivedFileLength) {//если длина ожидаемого файла равна количеству полученных байтов + System.out.println("CL File received"); + out.close();//закрываем файл в который писали байты + break;//останавливаем получение + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + cause.printStackTrace(); + ctx.close(); + } +} diff --git a/client/src/main/java/Controller.java b/client/src/main/java/Controller.java index 34f0376..82967ca 100644 --- a/client/src/main/java/Controller.java +++ b/client/src/main/java/Controller.java @@ -9,10 +9,15 @@ import java.io.IOException; import java.net.URL; +import java.nio.channels.Channel; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.util.List; import java.util.ResourceBundle; +import java.util.concurrent.CountDownLatch; + +import static java.lang.Thread.currentThread; public class Controller implements Initializable { @FXML @@ -38,47 +43,89 @@ public void authorization(ActionEvent actionEvent) { } public void initialize(URL location, ResourceBundle resources) {//инплементированный метод - Network.start(); - Thread t = new Thread(() -> {//демон поток ожидающий файлы от сервера - try { - while (true) { - AbstractMessage am = Network.readObject();//ожидает любые сообщения от сервера - if (am instanceof FileMessage) {//если сервер прислал FileMessage - FileMessage fm = (FileMessage) am;//кастует полученное сообщение к FileMessage - Files.write(Paths.get("client_storage/" - + fm.getFilename()), fm.getData(), StandardOpenOption.CREATE);//записывает полученный файл в client_storage - refreshLocalFilesList();//обновляет список файлов в client_storage - } - } - } catch (ClassNotFoundException | IOException e) { - e.printStackTrace(); - } finally { - Network.stop(); - } - }); - t.setDaemon(true); - t.start(); + CountDownLatch networkStarter = new CountDownLatch(1);//создание защелки на 1 щелчок + new Thread(() -> Network.getInstance().start(networkStarter)).start();//запуск сети в отдельном потоке + try { + networkStarter.await();//ожидает открытия сетевого соединения + } catch (InterruptedException e) { + e.printStackTrace(); + } refreshLocalFilesList(); } - //действие по кнопке downloadServerButton. Загрузка файла на сервер - public void downloadFileServer (ActionEvent actionEvent) { - if (fileNameField.getLength() > 0) { - Network.sendMsg(new FileRequest(fileNameField.getText()));//клиент посылает серверу FileRequest с именем интересующего файла - fileNameField.clear();//очищает форму в интерфейсе - } + //Получение файла с сервера + public void uploadFileClient(ActionEvent actionEvent) throws Exception { + ProtoFileSender.requestFile(Paths.get(fileNameField.getText()), Network.getInstance().getCurrentChannel(), future -> { + if (!future.isSuccess()) {//действие при неудачном получении файла + future.cause().printStackTrace(); +// Network.getInstance().stop(); + } + if (future.isSuccess()) {//действие при удачном получении файла + System.out.println("Файл успешно получен"); +// Network.getInstance().stop(); + } + }); + fileNameField.clear();//очищает форму в интерфейсе } - //действие по кнопке uploadServerButton. Загрузка файла на клиент - public void uploadFileClient(ActionEvent actionEvent) throws IOException { - if (fileNameField.getLength() > 0) { - if (Files.exists(Paths.get("client_storage/" + fileNameField.getText()))) { - Network.sendMsg(new FileMessage(Paths.get("client_storage/" + fileNameField.getText()))); - fileNameField.clear(); + //Отправка файла на сервер + public void downloadFileServer(ActionEvent actionEvent) throws Exception { + //Действия по finishListener (из ProtoFileSender, метод sendFile) + ProtoFileSender.sendFile(Paths.get("client_storage/" + fileNameField.getText()), Network.getInstance().getCurrentChannel(), future -> {//указываем файл и сеть для отправки + if (!future.isSuccess()) {//действие при неудачной передаче файла + future.cause().printStackTrace(); +// Network.getInstance().stop(); } - } + if (future.isSuccess()) {//действие при удачной передаче файла + System.out.println("Файл успешно передан"); +// Network.getInstance().stop(); + } + }); + fileNameField.clear();//очищает форму в интерфейсе } + +// Network.start(); +// Thread t = new Thread(() -> {//демон поток ожидающий файлы от сервера +// try { +// while (true) { +// AbstractMessage am = Network.readObject();//ожидает любые сообщения от сервера +// if (am instanceof FileMessage) {//если сервер прислал FileMessage +// FileMessage fm = (FileMessage) am;//кастует полученное сообщение к FileMessage +// Files.write(Paths.get("client_storage/" +// + fm.getFilename()), fm.getData(), StandardOpenOption.CREATE);//записывает полученный файл в client_storage +// refreshLocalFilesList();//обновляет список файлов в client_storage +// } +// } +// } catch (ClassNotFoundException | IOException e) { +// e.printStackTrace(); +// } finally { +// Network.stop(); +// } +// }); +// t.setDaemon(true); +// t.start(); +// refreshLocalFilesList(); +// } + + // //действие по кнопке downloadServerButton. Загрузка файла на сервер +// public void downloadFileServer (ActionEvent actionEvent) { +// if (fileNameField.getLength() > 0) { +// Network.sendMsg(new FileRequest(fileNameField.getText()));//клиент посылает серверу FileRequest с именем интересующего файла +// fileNameField.clear();//очищает форму в интерфейсе +// } +// } +// +// //действие по кнопке uploadServerButton. Загрузка файла на клиент +// public void uploadFileClient(ActionEvent actionEvent) throws IOException { +// if (fileNameField.getLength() > 0) { +// if (Files.exists(Paths.get("client_storage/" + fileNameField.getText()))) { +// Network.sendMsg(new FileMessage(Paths.get("client_storage/" + fileNameField.getText()))); +// fileNameField.clear(); +// } +// } +// } +// //обновление списка локальных файлов public void refreshLocalFilesList() { Platform.runLater(() -> { @@ -93,5 +140,6 @@ public void refreshLocalFilesList() { } }); } - } + + diff --git a/client/src/main/java/Network.java b/client/src/main/java/Network.java index 7877d98..2972a5d 100644 --- a/client/src/main/java/Network.java +++ b/client/src/main/java/Network.java @@ -1,58 +1,58 @@ -import io.netty.handler.codec.serialization.ObjectDecoderInputStream; -import io.netty.handler.codec.serialization.ObjectEncoderOutputStream; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; import java.io.IOException; -import java.net.Socket; +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; public class Network { - private static Socket socket; - private static ObjectEncoderOutputStream out;//исходящий поток для отправки - private static ObjectDecoderInputStream in;//входящий поток для получения + private static Network ourInstance = new Network(); - public static void start() { - try { - socket = new Socket("localhost", 8189); - out = new ObjectEncoderOutputStream(socket.getOutputStream()); - in = new ObjectDecoderInputStream(socket.getInputStream(), 50 * 1024 * 1024); - } catch (IOException e) { - e.printStackTrace(); - } + public static Network getInstance() { + return ourInstance; } - public static void stop() { - try { - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } - try { - in.close(); - } catch (IOException e) { - e.printStackTrace(); - } - try { - socket.close(); - } catch (IOException e) { - e.printStackTrace(); - } + private Network() { + } + + private Channel currentChannel;//канал передачи + + public Channel getCurrentChannel() { + return currentChannel; } - //позволяет отправлять любые сообщения серверу - public static boolean sendMsg(AbstractMessage msg) { + public void start(CountDownLatch countDownLatch) { + EventLoopGroup group = new NioEventLoopGroup(); try { - out.writeObject(msg); - return true;// если успешная отправка сообщения - } catch (IOException e) { + Bootstrap clientBootstrap = new Bootstrap();//настройка клиента + clientBootstrap.group(group)//ссылка на пул потоков + .channel(NioSocketChannel.class)//работа через NioSocketChannel + .remoteAddress(new InetSocketAddress("localhost", 8189))//адрес соединения + .handler(new ChannelInitializer() { + protected void initChannel(SocketChannel socketChannel) throws Exception { + socketChannel.pipeline().addLast(new ClientHandler()); + currentChannel = socketChannel;//ссылка на socketChannel + } + }); + ChannelFuture channelFuture = clientBootstrap.connect().sync();//создается подключение + countDownLatch.countDown();//подключение открывается + channelFuture.channel().closeFuture().sync();//ожидание завершения подключения + } catch (Exception e) { e.printStackTrace(); + } finally { + try { + group.shutdownGracefully().sync();//завершает пул потоков + } catch (InterruptedException e) { + e.printStackTrace(); + } } - return false;//если не удалось отправить сообщение } - - //получение объектов от сервера - public static AbstractMessage readObject() throws ClassNotFoundException, IOException { - Object obj = in.readObject();//блокирующая операция, может ожидать посылки от сервера - return (AbstractMessage) obj;//возвращает полученный от сервера объект - } -} + public void stop() { + currentChannel.close(); + }//метод остановки сети +} \ No newline at end of file diff --git a/client/src/main/resources/sample.fxml b/client/src/main/resources/sample.fxml index ff73535..2ef7e00 100644 --- a/client/src/main/resources/sample.fxml +++ b/client/src/main/resources/sample.fxml @@ -7,12 +7,12 @@ -