Windows клиент-серверийн хэрэгслүүдийн функц бүхий програм хангамж бичих, 02-р хэсэг

Windows консолын хэрэглүүрийг тусгайлан хэрэгжүүлэхэд зориулагдсан цуврал нийтлэлийг үргэлжлүүлэхийн тулд бид энгийн файл дамжуулах протокол болох TFTP (Trivial File Transfer Protocol)-ийн талаар ярихгүй байхын аргагүй юм.

Сүүлчийн удаад онолыг товчхон авч үзэж, шаардлагатай кодтой төстэй функцийг хэрэгжүүлдэг кодыг харж, дүн шинжилгээ хийцгээе. Илүү дэлгэрэнгүй мэдээлэл - зүсэлтийн доор

Өгүүллийн төгсгөлд линкийг нь олж авах боломжтой лавлагаа мэдээллийг би хуулж буулгахгүй, зөвхөн TFTP нь FTP протоколын хялбаршуулсан хувилбар бөгөөд хандалтын хяналтын тохиргоонд байдаг гэдгийг л хэлье. устгагдсан бөгөөд файлыг хүлээн авах, шилжүүлэх командуудаас өөр зүйл алга. Гэсэн хэдий ч бидний хэрэгжилтийг бага зэрэг гоёмсог, код бичих одоогийн зарчимд нийцүүлэхийн тулд синтаксийг бага зэрэг өөрчилсөн - энэ нь үйл ажиллагааны зарчмыг өөрчлөхгүй, харин IMHO интерфейс нь арай логик болж хувирсан. FTP болон TFTP-ийн эерэг талуудыг хослуулсан.

Ялангуяа, эхлүүлэх үед үйлчлүүлэгч серверийн IP хаяг болон захиалгат TFTP нээлттэй портыг хүсдэг (стандарт протоколд нийцэхгүй байгаа тул порт сонгох боломжийг хэрэглэгчдэд үлдээх нь зүйтэй гэж үзсэн), үүний дараа холболт үүсдэг бөгөөд үүний үр дүнд үйлчлүүлэгч файл хүлээн авах эсвэл сервер рүү илгээх командуудын аль нэгийг илгээж болно - авах, тавих. Логикийг хялбарчлахын тулд бүх файлыг хоёртын горимд илгээдэг.

Протоколыг хэрэгжүүлэхийн тулд би уламжлал ёсоор 4 анги ашигласан.

  • TFTPClient
  • TFTPS сервер
  • TFTPClientTester
  • TFTPS ServerTester

Туршилтын ангиуд нь зөвхөн үндсэн ангиудыг дибаг хийхэд зориулагдсан тул би тэдгээрийг шинжлэхгүй, харин код нь хадгалах газарт байх болно, үүнтэй холбогдох холбоосыг нийтлэлийн төгсгөлд авах боломжтой. Одоо би үндсэн ангиудыг харах болно.

TFTPClient

Энэ ангийн даалгавар бол IP болон портын дугаараар нь алсын сервертэй холбогдож, оролтын урсгалаас командыг (энэ тохиолдолд гар) уншиж, задлан шинжилж, сервер рүү шилжүүлэх, мөн эсэхээс хамаарна. файл илгээх, хүлээн авах, шилжүүлэх эсвэл авах шаардлагатай.

Үйлчлүүлэгчийг серверт холбож, оролтын урсгалаас команд хүлээхийг ажиллуулах код нь иймэрхүү харагдаж байна. Энд ашиглагдаж буй хэд хэдэн глобал хувьсагчдыг нийтлэлийн гадна, програмын бүрэн текстээр тайлбарласан болно. Тэдний өчүүхэн байдлаас болж би нийтлэлийг хэт ачаалахгүйн тулд иш татдаггүй.

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

Энэ кодын блокт дуудагдсан аргуудыг авч үзье:

Энд файлыг илгээсэн - сканнер ашиглан бид файлын агуулгыг байтуудын массив хэлбэрээр танилцуулж, тэдгээрийг нэг нэгээр нь залгуур руу бичиж, дараа нь хаагаад дахин нээнэ (хамгийн ойлгомжтой шийдэл биш, гэхдээ гэхдээ. Энэ нь нөөцийг чөлөөлөхийг баталгаажуулдаг), үүний дараа бид амжилттай шилжүүлгийн тухай мессежийг харуулдаг.

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

Энэ кодын хэсэг нь серверээс өгөгдөл авахыг тайлбарладаг. Бүх зүйл дахин өчүүхэн, зөвхөн эхний кодын блок нь сонирхол татдаг. Сокетоос яг хэдэн байт унших шаардлагатайг ойлгохын тулд шилжүүлсэн файл хэр жинтэй болохыг мэдэх хэрэгтэй. Сервер дээрх файлын хэмжээг урт бүхэл тоогоор илэрхийлдэг тул энд 4 байтыг хүлээн авч, дараа нь нэг тоо болгон хувиргадаг. Энэ нь маш Java арга биш бөгөөд SI-тэй төстэй боловч энэ нь асуудлыг шийддэг.

Дараа нь бүх зүйл өчүүхэн байдаг - бид залгуураас мэдэгдэж буй тооны байтыг хүлээн авч файл руу бичдэг бөгөөд үүний дараа бид амжилтын тухай мессежийг харуулдаг.

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

Хэрэв үйлчлүүлэгчийн цонхонд get эсвэл put-аас өөр команд орсон бол showErrorMessage функц дуудагдах бөгөөд энэ нь оролт буруу байсныг илтгэнэ. Өчүүхэн зүйлээс болоод би иш татахгүй. Илүү сонирхолтой зүйл бол оролтын мөрийг хүлээн авах, хуваах функц юм. Бид сканнерыг түүн рүү дамжуулдаг бөгөөд үүнээс хоёр зайгаар тусгаарлагдсан, тушаал, эх хаяг, очих хаягийг агуулсан мөрийг хүлээн авах болно.

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

Тушаал илгээх - сканнераас оруулсан командыг залгуур руу дамжуулж, илгээхийг албаддаг

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

Сонгогч нь оруулсан мөрөөс хамааран програмын үйлдлийг тодорхойлох функц юм. Энд байгаа бүх зүйл тийм ч үзэсгэлэнтэй биш бөгөөд ашигласан заль мэх нь кодын блокоос гадуур албадан гарах хамгийн сайн арга биш боловч үүний гол шалтгаан нь Java-д C# хэл дээрх төлөөлөгчид, C++-ийн функцийн заагч, эсвэл бусад зүйлс байхгүй байгаа явдал юм. наад зах нь аймшигтай, аймшигтай goto, энэ нь танд сайхан хэрэгжүүлэх боломжийг олгодог. Хэрэв та кодыг хэрхэн илүү гоёмсог болгохыг мэддэг бол сэтгэгдэл дээр шүүмжлэлд баяртай байна. Надад String-delegate толь бичиг хэрэгтэй юм шиг санагдаж байна, гэхдээ төлөөлөгч байхгүй ...

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

TFTPS сервер

Серверийн үйл ажиллагаа нь үйлчлүүлэгчийн үйл ажиллагаанаас ялгаатай бөгөөд зөвхөн командууд нь гарнаас биш, харин залгуураас ирдэг. Зарим аргууд нь ерөнхийдөө ижил байдаг тул би тэдгээрийг иш татахгүй, зөвхөн ялгааг нь хөндөх болно.

Эхлэхийн тулд портыг оролт болгон хүлээн авч, залгуураас оролтын өгөгдлийг мөнхийн гогцоонд боловсруулдаг ажиллуулах аргыг ашигладаг.

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

Файл руу бичих урсгалыг нээж, бүх оролтын байтыг залгуураас бичдэг writeToFileFromSocket аргыг ороосон put арга нь бичиж дуусах үед дамжуулалт амжилттай дууссаныг харуулсан мессежийг харуулдаг.

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

Get арга нь серверийн файлыг татаж авдаг. Програмын үйлчлүүлэгчийн хэсэгт аль хэдийн дурдсанчлан файлыг амжилттай шилжүүлэхийн тулд урт бүхэл тоонд хадгалагдсан хэмжээг нь мэдэх шаардлагатай тул би үүнийг 4 байт массив болгон хувааж, тэдгээрийг байтаар шилжүүлээрэй. залгуур руу залгаад дараа нь үйлчлүүлэгч дээр хүлээн авч, буцааж дугаар болгон угсарсны дараа би файлын оролтын урсгалаас уншсан файлыг бүрдүүлдэг бүх байтыг шилжүүлдэг.


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

getAndParseInput арга нь үйлчлүүлэгчийнхтэй адил бөгөөд цорын ганц ялгаа нь гарнаас бус залгуураас өгөгдлийг уншдаг явдал юм. Код нь сонгогчтой адил репозиторт байдаг.
Энэ тохиолдолд эхлэлийг кодын тусдаа блокт байрлуулна, учир нь Энэ хэрэгжилтийн хүрээнд, шилжүүлэг дууссаны дараа нөөцүүдийг суллаж, дахин эзэлдэг - дахин санах ой алдагдахаас хамгаалах болно.

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

Нэгтгэн дүгнэхэд:

Бид зүгээр л энгийн өгөгдөл дамжуулах протокол дээр өөрсдийн хувилбараа бичиж, энэ нь хэрхэн ажиллах ёстойг олж мэдэв. Зарчмын хувьд би энд Америкийг олж нээгээгүй, нэг их шинэ зүйл бичээгүй, гэхдээ Хабрегийн талаар үүнтэй төстэй нийтлэл байгаагүй бөгөөд cmd хэрэгслүүдийн талаар цуврал нийтлэл бичихийн тулд үүнийг хөндөхгүй байх боломжгүй байв.

Ашигласан материал:

Эх кодын хадгалах газар
TFTP-ийн талаар товчхон
Үүнтэй ижил зүйл, гэхдээ орос хэл дээр

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх