Windows клиент-сервер утилиттеринин функционалдуулугу менен программалык камсыздоону жазуу, 02-бөлүк

Windows консолунун утилиталарын ыңгайлаштырууга арналган макалалардын үзгүлтүксүз сериясын улантуу менен, биз TFTP (Trivial File Transfer Protocol) - жөнөкөй файлдарды өткөрүп берүү протоколуна токтоло албайбыз.

Акыркы жолу, келгиле, теорияны кыскача карап көрөлү, талап кылынган функцияга окшош функцияларды ишке ашырган кодду көрүп, аны талдап көрөлү. Көбүрөөк маалымат - кесип астында

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

Атап айтканда, ишке киргизилгенде кардар сервердин IP дарегин жана ыңгайлаштырылган TFTP ачык турган портту сурайт (стандарттык протоколго туура келбегендиктен, мен колдонуучуга портту тандоо мүмкүнчүлүгүн калтырууну туура деп эсептейм), андан кийин байланыш пайда болот, анын натыйжасында кардар буйруктардын бирин жөнөтө алат - алуу же коюу, файлды алуу же серверге жөнөтүү. Логиканы жөнөкөйлөтүү үчүн бардык файлдар бинардык режимде жөнөтүлөт.

Протоколду ишке ашыруу үчүн мен салттуу түрдө 4 классты колдондум:

  • TFTPClient
  • TFTPServer
  • TFTPClientTester
  • TFTPServerTester

Тестирлөө класстары негизги класстарды оңдоо үчүн гана бар болгондуктан, мен аларды талдабайм, бирок код репозиторийде болот, ага шилтемени макаланын аягында тапса болот. Эми мен негизги сабактарды карайм.

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

Эгерде кардар терезесине алуу же коюудан башка команда киргизилген болсо, киргизүү туура эмес болгонун көрсөтүү менен 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++ функциясынын көрсөткүчтөрү сыяктуу нерселердин жоктугу. жок дегенде, коркунучтуу жана коркунучтуу гото, бул сонун ишке ашырууга мүмкүндүк берет. Эгер сиз кодду кантип бир аз жарашыктуу кылууну билсеңиз, комментарийлерде сынды кабыл алам. Мага бул жерде 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);
    }
}

TFTPServer

Сервердин функционалдуулугу кардардын функционалдуулугунан чоңураак айырмаланат, ага командалар клавиатурадан эмес, розеткадан келгени менен гана айырмаланат. Кээ бир ыкмалар жалпысынан бирдей, ошондуктан мен аларды келтирбей эле коёюн, мен айырмачылыктарга гана токтолоюн.

Баштоо үчүн иштетүү ыкмасы колдонулат, ал портту киргизүү катары кабыл алат жана розеткадан киргизилген маалыматтарды түбөлүк циклде иштетет.

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

Файлга жазуу агымын ачуучу жана розеткадан бардык киргизүү байттарын жаза турган writeToFileFromSocket ыкмасын ороп коюу ыкмасы жазуу аяктаганда өткөрүп берүүнүн ийгиликтүү аяктаганын көрсөткөн билдирүүнү көрсөтөт.

    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 жөнүндө кыскача
Ошол эле нерсе, бирок орус тилинде

Source: www.habr.com

Комментарий кошуу