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

Melanjutkan rangkaian artikel yang ditujukan untuk implementasi khusus utilitas konsol Windows, kami tidak bisa tidak menyentuh TFTP (Trivial File Transfer Protocol) - protokol transfer file sederhana.

Seperti terakhir kali, mari kita bahas teorinya secara singkat, lihat kode yang mengimplementasikan fungsionalitas serupa dengan yang diperlukan, dan menganalisisnya. Lebih detailnya - di bawah potongan

Saya tidak akan menyalin-menempelkan informasi referensi, tautan yang biasanya dapat ditemukan di akhir artikel, saya hanya akan mengatakan bahwa pada intinya, TFTP adalah variasi sederhana dari protokol FTP, di mana pengaturan kontrol akses memiliki telah dihapus, dan sebenarnya tidak ada apa pun di sini kecuali perintah untuk menerima dan mentransfer file. Namun, untuk membuat implementasi kami sedikit lebih elegan dan disesuaikan dengan prinsip penulisan kode saat ini, sintaksnya telah sedikit diubah - ini tidak mengubah prinsip operasi, tetapi antarmuka, IMHO, menjadi sedikit lebih logis dan menggabungkan aspek positif dari FTP dan TFTP.

Khususnya, saat diluncurkan, klien meminta alamat IP server dan port tempat TFTP khusus terbuka (karena ketidakcocokan dengan protokol standar, saya menganggap pantas untuk membiarkan pengguna memiliki kemampuan untuk memilih port), setelah itu a koneksi terjadi, sebagai akibatnya klien dapat mengirim salah satu perintah - dapatkan atau letakkan, untuk menerima atau mengirim file ke server. Semua file dikirim dalam mode biner untuk menyederhanakan logika.

Untuk mengimplementasikan protokol, saya biasanya menggunakan 4 kelas:

  • Klien TFTPC
  • Server TFTPS
  • Penguji Klien TFTPC
  • Penguji TFTPServer

Karena kenyataan bahwa kelas pengujian hanya ada untuk men-debug kelas utama, saya tidak akan menganalisisnya, tetapi kodenya akan ada di repositori; tautan ke sana dapat ditemukan di akhir artikel. Sekarang saya akan melihat kelas utama.

Klien TFTPC

Tugas kelas ini adalah menyambung ke server jarak jauh berdasarkan ip dan nomor portnya, membaca perintah dari aliran input (dalam hal ini, keyboard), menguraikannya, mentransfernya ke server, dan, bergantung pada apakah Anda perlu mengirim atau menerima file, mentransfer atau menerima.

Kode untuk meluncurkan klien untuk terhubung ke server dan menunggu perintah dari aliran input terlihat seperti ini. Sejumlah variabel global yang digunakan di sini dijelaskan di luar artikel, di teks lengkap program. Karena sepele, saya tidak mengutipnya 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 dipanggil dalam blok kode ini:

Di sini file dikirim - menggunakan pemindai, kami menyajikan konten file sebagai array byte, yang kami tulis satu per satu ke soket, setelah itu kami menutupnya dan membukanya lagi (bukan solusi yang paling jelas, tapi itu menjamin pelepasan sumber daya), setelah itu kami menampilkan pesan tentang transfer yang berhasil.

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("nDonen");
            }

        catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

Fragmen kode ini menjelaskan pengambilan data dari server. Semuanya lagi-lagi sepele, hanya blok kode pertama yang menarik. Untuk memahami dengan tepat berapa byte yang perlu dibaca dari soket, Anda perlu mengetahui berapa berat file yang ditransfer. Ukuran file di server direpresentasikan sebagai bilangan bulat panjang, sehingga 4 byte diterima di sini, yang kemudian diubah menjadi satu angka. Ini bukan pendekatan Java, agak mirip dengan SI, namun menyelesaikan masalahnya.

Maka semuanya sepele - kami menerima sejumlah byte yang diketahui dari soket dan menuliskannya ke file, setelah itu kami 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("nDONEn");
       }
       catch (Exception e){
            System.out.println(e.getMessage());
       }
    }

Jika perintah selain get atau put dimasukkan ke jendela klien, fungsi showErrorMessage akan dipanggil, yang menunjukkan bahwa inputnya salah. Karena hal sepele, saya tidak akan mengutipnya. Yang lebih menarik adalah fungsi menerima dan memisahkan string input. Kami memasukkan pemindai ke dalamnya, yang darinya kami berharap 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β€”mentransmisikan 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 program tergantung pada string yang dimasukkan. Segala sesuatu di sini tidak terlalu indah dan trik yang digunakan bukan yang terbaik dengan keluar paksa di luar blok kode, tetapi alasan utama untuk ini adalah tidak adanya beberapa hal di Java, seperti delegasi di C#, penunjuk fungsi dari C++, atau di setidaknya goto yang mengerikan dan mengerikan, yang memungkinkan Anda mengimplementasikan ini dengan indah. Jika Anda tahu cara membuat kodenya sedikit lebih elegan, saya menerima kritik di komentar. Tampaknya bagi saya kamus delegasi String 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);
    }
}

Server TFTPS

Fungsionalitas server berbeda dari fungsionalitas klien, pada umumnya, hanya karena perintah diterima bukan dari keyboard, tetapi dari soket. Beberapa cara umumnya sama, jadi saya tidak akan mengutipnya, saya hanya akan menyentuh perbedaannya saja.

Untuk memulai, metode run digunakan, yang menerima port sebagai input dan memproses data input dari soket dalam loop abadi.

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

Metode put, yang menggabungkan metode writeToFileFromSocket yang membuka aliran tulis ke file dan menulis semua byte masukan dari soket, menampilkan pesan yang menunjukkan keberhasilan penyelesaian transfer ketika penulisan selesai.

    private  void put(String source, String dest){
            writeToFileFromSocket();
            System.out.print("nDonen");
    };
    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 mengambil file server. Seperti yang telah disebutkan di bagian sisi klien program, agar berhasil mentransfer file Anda perlu mengetahui ukurannya, disimpan dalam bilangan bulat panjang, jadi saya membaginya menjadi array 4 byte, mentransfernya byte demi byte ke soket, dan kemudian, setelah menerima dan merakitnya di klien menjadi nomor kembali, saya mentransfer semua byte yang membentuk file, membaca 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 seperti di klien, satu-satunya perbedaan adalah metode ini membaca data dari soket, bukan dari keyboard. Kodenya ada di repositori, sama seperti pemilih.
Dalam hal ini, inisialisasi ditempatkan dalam blok kode yang terpisah, karena dalam implementasi ini, setelah transfer selesai, sumber daya dilepaskan dan digunakan kembali - lagi untuk 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());
        }
    }

Untuk meringkas:

Kami baru saja menulis variasi kami sendiri pada protokol transfer data sederhana dan mengetahui cara kerjanya. Pada prinsipnya, saya tidak menemukan Amerika di sini dan tidak menulis banyak hal baru, tetapi tidak ada artikel serupa di HabrΓ©, dan sebagai bagian dari penulisan serangkaian artikel tentang utilitas cmd, tidak mungkin untuk tidak menyentuhnya.

Бсылки:

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

Sumber: www.habr.com

Tambah komentar