Menulis perangkat lunak dengan fungsi utilitas klien-server Windows, bagian 02

Melanjutkan serangkaian artikel tentang implementasi kustom utilitas konsol di Windows, TFTP (Trivial File Transfer Protocol) adalah protokol transfer file sederhana.

Seperti terakhir kali, kita membahas teori secara singkat, melihat kode yang mengimplementasikan fungsional yang mirip dengan yang diperlukan, dan menganalisisnya. Baca lebih lanjut - di bawah potongan

Saya tidak akan menyalin-rekatkan informasi rujukan, tautan yang secara tradisional dapat ditemukan di akhir artikel, saya hanya akan mengatakan bahwa TFTP pada dasarnya adalah variasi yang disederhanakan dari protokol FTP, yang menghilangkan pengaturan kontrol akses, dan sebenarnya tidak ada apa-apa di sini selain perintah untuk menerima dan mentransfer file. . Namun, untuk membuat implementasi kami sedikit lebih elegan dan disesuaikan dengan prinsip-prinsip penulisan kode saat ini, sintaks sedikit berubah - itu tidak mengubah prinsip kerja, tetapi antarmuka, IMHO, menjadi sedikit lebih logis dan menggabungkan aspek positif dari FTP dan TFTP.

Secara khusus, ketika memulai, klien meminta alamat ip server dan port tempat custom TFTP terbuka (karena ketidakcocokan dengan protokol standar, saya menganggap pantas untuk meninggalkan opsi untuk memilih port ke pengguna), setelah itu terjadi koneksi, akibatnya klien dapat mengirim salah satu perintah - dapatkan atau masukkan, untuk menerima atau mengirim file ke server. Semua file dikirim dalam mode biner - untuk menyederhanakan logika.

Untuk implementasi protokol, saya secara tradisional menggunakan 4 kelas:

  • TFTPClient
  • TFTPServer
  • TFTPClientTester
  • TFTPServerTester

Karena fakta bahwa kelas pengujian hanya ada untuk debugging yang utama, saya tidak akan menganalisisnya, tetapi kode akan berada di repositori, tautan ke sana dapat ditemukan di akhir artikel. Dan sekarang saya akan mengerti kelas utama.

TFTPClient


Tugas kelas ini adalah untuk menyambung ke server jarak jauh dengan nomor ip dan port-nya, membaca perintah dari aliran input (dalam hal ini, keyboard), menguraikannya, mentransfernya ke server, dan tergantung pada apakah Anda ingin mentransfer atau menerima file, mentransfernya atau untuk menerima.

Kode peluncuran klien untuk menghubungkan ke server dan menunggu perintah dari aliran input terlihat seperti ini. Sejumlah variabel global yang digunakan di sini dijelaskan di luar artikel, dalam teks lengkap program. Karena kesederhanaan mereka, saya tidak mengutip agar tidak membebani artikel.

public void run(String ip, int port) { this.ip = ip; this.port = port; try { inicialization(); Scanner keyboard = new Scanner(System.in); while (isRunning) { getAndParseInput(keyboard); sendCommand(); selector(); } } catch (Exception e) { System.out.println(e.getMessage()); } } 

Mari kita membahas metode yang disebut dalam blok kode ini:

Di sini file dikirim - menggunakan pemindai, kami menyajikan konten file sebagai array byte, yang kami tulis ke soket satu per satu, lalu tutup dan buka kembali (bukan solusi yang paling jelas, tetapi menjamin pelepasan sumber daya), setelah itu kami menampilkan pesan tentang kesuksesan transmisi.

 private void put(String sourcePath, String destPath) { File src = new File(sourcePath); try { InputStream scanner = new FileInputStream(src); byte[] bytes = scanner.readAllBytes(); for (byte b : bytes) sout.write(b); sout.close(); inicialization(); System.out.println("\nDone\n"); } catch (Exception e) { System.out.println(e.getMessage()); } } 

Fragmen kode ini menjelaskan penerimaan data dari server. Semuanya sepele lagi, hanya blok kode pertama yang menarik. Untuk memahami dengan tepat berapa banyak byte yang perlu Anda baca dari soket, Anda perlu tahu berapa berat file yang ditransfer. Ukuran file di server tampaknya bilangan bulat panjang, jadi 4 byte diterima di sini, yang kemudian dikonversi ke satu nomor. Ini bukan pendekatan yang sangat Java, ini agak mirip untuk SI, tetapi menyelesaikan masalahnya.

Kemudian semuanya sepele - kita mendapatkan jumlah byte yang diketahui dari soket dan menuliskannya ke file, setelah itu kita menampilkan pesan sukses.

  private void get(String sourcePath, String destPath){ long sizeOfFile = 0; try { byte[] sizeBytes = new byte[Long.SIZE]; for (int i =0; i< Long.SIZE/Byte.SIZE; i++) { sizeBytes[i] = (byte)sin.read(); sizeOfFile*=256; sizeOfFile+=sizeBytes[i]; } FileOutputStream writer = new FileOutputStream(new File(destPath)); for (int i =0; i < sizeOfFile; i++) { writer.write(sin.read()); } writer.close(); System.out.println("\nDONE\n"); } catch (Exception e){ System.out.println(e.getMessage()); } } 

Jika perintah selain get atau put dimasukkan ke dalam jendela klien, fungsi showErrorMessage akan dipanggil, menunjukkan kesalahan input. Karena hal-hal sepele - Saya tidak mengutip. Yang agak lebih menarik adalah fungsi mendapatkan dan memisahkan string input. Kami melewati pemindai untuk itu, dari mana kami berharap untuk menerima garis yang dipisahkan oleh dua spasi dan berisi perintah, alamat sumber, dan alamat tujuan.

  private void getAndParseInput(Scanner scanner) { try { input = scanner.nextLine().split(" "); typeOfCommand = input[0]; sourcePath = input[1]; destPath = input[2]; } catch (Exception e) { System.out.println("Bad input"); } } 

Mengirim perintah - mengirim perintah yang dimasukkan dari pemindai ke soket dan memaksanya untuk dikirim

  private void sendCommand() { try { for (String str : input) { for (char ch : str.toCharArray()) { sout.write(ch); } sout.write(' '); } sout.write('\n'); } catch (Exception e) { System.out.print(e.getMessage()); } } 

Selector adalah fungsi yang menentukan tindakan suatu program tergantung pada string input. Semuanya tidak terlalu indah di sini dan trik yang tidak begitu baik dengan memaksa blok kode yang digunakan digunakan, tetapi alasan utama untuk ini adalah tidak adanya di Jawa beberapa hal, seperti delegasi dalam C #, petunjuk ke fungsi dari C ++, atau setidaknya goto menakutkan dan mengerikan, yang biarkan Anda menyadarinya dengan indah. Jika Anda tahu cara membuat kode sedikit lebih elegan, saya menunggu kritik di komentar. Tampaknya bagi saya bahwa kamus String-delegate diperlukan di sini, tetapi tidak ada delegasi ...

  private void selector() { do{ if (typeOfCommand.equals("get")){ get(sourcePath, destPath); break; } if (typeOfCommand.equals("put")){ put(sourcePath, destPath); break; } showErrorMessage(); } while (false); } } 

TFTPServer


Fungsionalitas server berbeda dari fungsionalitas klien pada umumnya hanya dalam perintah yang datang bukan dari keyboard, tetapi dari soket. Beberapa metode bertepatan, jadi saya tidak akan memberikannya, saya hanya akan menyebutkan perbedaannya.

Untuk memulai di sini, metode jalankan digunakan, yang menerima port untuk input dan memproses data input dari soket dalam siklus abadi.

  public void run(int port) { this.port = port; incialization(); while (true) { getAndParseInput(); selector(); } } 

Metode put, yang merupakan pembungkus metode writeToFileFromSocket, yang membuka aliran penulisan ke file dan menulis semua byte input dari soket, setelah perekaman selesai, menampilkan pesan tentang keberhasilan penyelesaian transfer.

  private void put(String source, String dest){ writeToFileFromSocket(); System.out.print("\nDone\n"); }; private void writeToFileFromSocket() { try { FileOutputStream writer = new FileOutputStream(new File(destPath)); byte[] bytes = sin.readAllBytes(); for (byte b : bytes) { writer.write(b); } writer.close(); } catch (Exception e){ System.out.println(e.getMessage()); } } 

Metode get menyediakan file server. Seperti yang telah disebutkan di bagian sisi klien dari program, untuk berhasil mentransfer file, Anda perlu tahu ukurannya, disimpan dalam bilangan bulat panjang, jadi saya membaginya menjadi array 4 byte, mentransfernya ke soket byte, dan kemudian, setelah menerima dan mengumpulkannya di klien kembali ke nomor, saya mentransfer semua byte yang membentuk file, baca dari aliran input dari file.

 private void get(String source, String dest){ File sending = new File(source); try { FileInputStream readFromFile = new FileInputStream(sending); byte[] arr = readFromFile.readAllBytes(); byte[] bytes = ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(sending.length()).array(); for (int i = 0; i<Long.SIZE / Byte.SIZE; i++) sout.write(bytes[i]); sout.flush(); for (byte b : arr) sout.write(b); } catch (Exception e){ System.out.println(e.getMessage()); } }; 

Metode getAndParseInput sama dengan di klien, satu-satunya perbedaan adalah bahwa ia membaca data dari soket, dan bukan dari keyboard. Kode dalam repositori, seperti pemilih.
Dalam hal ini, inisialisasi dibuat dalam blok kode yang terpisah, karena dalam kerangka implementasi ini, setelah transfer selesai, sumber daya dibebaskan dan ditempati lagi, lagi dengan tujuan memberikan perlindungan terhadap kebocoran memori.

  private void incialization() { try { serverSocket = new ServerSocket(port); socket = serverSocket.accept(); sin = socket.getInputStream(); sout = socket.getOutputStream(); } catch (Exception e) { System.out.print(e.getMessage()); } } 

Singkatnya:

Kami baru saja menulis variasi kami pada protokol transfer data sederhana dan menemukan cara kerjanya. Pada prinsipnya, saya tidak menemukan Amerika dan tidak menulis banyak hal baru, tetapi - tidak ada artikel serupa tentang HabrΓ©, dan sebagai bagian dari menulis serangkaian artikel tentang utilitas CMD, tidak mungkin untuk tidak menyentuh dia.

Referensi:

Repositori kode sumber
Secara singkat tentang TFTP
Hal yang sama, tetapi dalam bahasa Rusia

Source: https://habr.com/ru/post/id461083/


All Articles