విండోస్ క్లయింట్-సర్వర్ యుటిలిటీల ఫంక్షనాలిటీతో రైటింగ్ సాఫ్ట్‌వేర్, పార్ట్ 02

విండోస్ కన్సోల్ యుటిలిటీల అనుకూల అమలులకు అంకితమైన కథనాల సిరీస్‌ను కొనసాగిస్తూ, మేము TFTP (ట్రివియల్ ఫైల్ ట్రాన్స్‌ఫర్ ప్రోటోకాల్) - ఒక సాధారణ ఫైల్ బదిలీ ప్రోటోకాల్‌ను తాకకుండా ఉండలేము.

చివరిసారిగా, సిద్ధాంతాన్ని క్లుప్తంగా పరిశీలిద్దాం, అవసరమైన దానితో సమానమైన కార్యాచరణను అమలు చేసే కోడ్‌ను చూడండి మరియు దానిని విశ్లేషించండి. మరిన్ని వివరాలు - కట్ కింద

నేను రిఫరెన్స్ సమాచారాన్ని కాపీ-పేస్ట్ చేయను, వాటికి లింక్‌లు సాంప్రదాయకంగా వ్యాసం చివరలో కనిపిస్తాయి, TFTP అనేది FTP ప్రోటోకాల్ యొక్క సరళీకృత వైవిధ్యం, దీనిలో యాక్సెస్ కంట్రోల్ సెట్టింగ్ అని నేను చెబుతాను. తీసివేయబడింది మరియు వాస్తవానికి ఫైల్‌ను స్వీకరించడానికి మరియు బదిలీ చేయడానికి ఆదేశాలు తప్ప ఇక్కడ ఏమీ లేవు . అయినప్పటికీ, మా అమలును కొంచెం సొగసైనదిగా మరియు ప్రస్తుత కోడ్ వ్రాసే సూత్రాలకు అనుగుణంగా చేయడానికి, వాక్యనిర్మాణం కొద్దిగా మార్చబడింది - ఇది ఆపరేషన్ సూత్రాలను మార్చదు, కానీ ఇంటర్‌ఫేస్, IMHO, కొంచెం లాజికల్‌గా మారుతుంది మరియు FTP మరియు TFTP యొక్క సానుకూల అంశాలను మిళితం చేస్తుంది.

ప్రత్యేకించి, ప్రారంభించినప్పుడు, క్లయింట్ సర్వర్ యొక్క IP చిరునామా మరియు కస్టమ్ TFTP తెరిచిన పోర్ట్‌ను అభ్యర్థిస్తుంది (ప్రామాణిక ప్రోటోకాల్‌తో అననుకూలత కారణంగా, వినియోగదారుకు పోర్ట్‌ను ఎంచుకునే సామర్థ్యాన్ని వదిలివేయడం సముచితమని నేను భావించాను), ఆ తర్వాత ఒక కనెక్షన్ ఏర్పడుతుంది, దీని ఫలితంగా క్లయింట్ ఆదేశాలలో ఒకదాన్ని పంపవచ్చు - పొందండి లేదా ఉంచండి, సర్వర్‌కు ఫైల్‌ను స్వీకరించడానికి లేదా పంపడానికి. తర్కాన్ని సులభతరం చేయడానికి అన్ని ఫైల్‌లు బైనరీ మోడ్‌లో పంపబడతాయి.

ప్రోటోకాల్‌ను అమలు చేయడానికి, నేను సాంప్రదాయకంగా 4 తరగతులను ఉపయోగించాను:

  • TFTP క్లయింట్
  • TFTPS సర్వర్
  • TFTPClientTester
  • TFTPS సర్వర్ టెస్టర్

ప్రధానమైన వాటిని డీబగ్గింగ్ చేయడానికి మాత్రమే పరీక్షా తరగతులు ఉన్నందున, నేను వాటిని విశ్లేషించను, కానీ కోడ్ రిపోజిటరీలో ఉంటుంది, దానికి సంబంధించిన లింక్‌ను వ్యాసం చివరిలో చూడవచ్చు. ఇప్పుడు నేను ప్రధాన తరగతులను చూస్తాను.

TFTP క్లయింట్

ఈ తరగతి యొక్క పని ఏమిటంటే, దాని 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 బైట్‌లు ఇక్కడ ఆమోదించబడతాయి, అవి తర్వాత ఒక సంఖ్యగా మార్చబడతాయి. ఇది చాలా జావా విధానం కాదు, ఇది 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());
        }
    }

సెలెక్టర్ అనేది ఎంటర్ చేసిన స్ట్రింగ్‌పై ఆధారపడి ప్రోగ్రామ్ యొక్క చర్యలను నిర్ణయించే ఒక ఫంక్షన్. ఇక్కడ ఉన్న ప్రతిదీ చాలా అందంగా లేదు మరియు కోడ్ బ్లాక్ వెలుపల బలవంతంగా నిష్క్రమించడంతో ఉపయోగించిన ట్రిక్ ఉత్తమమైనది కాదు, అయితే దీనికి ప్రధాన కారణం C#లోని డెలిగేట్‌లు, C++ నుండి ఫంక్షన్ పాయింటర్లు లేదా వద్ద వంటి కొన్ని విషయాలు జావాలో లేకపోవడమే. కనీసం భయంకరమైన మరియు భయంకరమైన గోటో, ఇది అందంగా అమలు చేయడానికి మిమ్మల్ని అనుమతిస్తుంది. కోడ్‌ను మరింత సొగసైనదిగా ఎలా చేయాలో మీకు తెలిస్తే, వ్యాఖ్యలలో విమర్శలను నేను స్వాగతిస్తున్నాను. ఇక్కడ స్ట్రింగ్-డెలిగేట్ నిఘంటువు అవసరమని నాకు అనిపిస్తోంది, కానీ ప్రతినిధి లేరు...

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

ఫైల్‌కి రైట్ స్ట్రీమ్‌ను తెరిచే మరియు సాకెట్ నుండి అన్ని ఇన్‌పుట్ బైట్‌లను వ్రాసే రైట్‌టోఫైల్‌ఫ్రోమ్‌సాకెట్ పద్ధతిని చుట్టే పుట్ మెథడ్, వ్రాత పూర్తయినప్పుడు బదిలీ విజయవంతంగా పూర్తయినట్లు సూచించే సందేశాన్ని ప్రదర్శిస్తుంది.

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

గెట్ మెథడ్ సర్వర్ ఫైల్‌ను తిరిగి పొందుతుంది. ప్రోగ్రామ్ యొక్క క్లయింట్ వైపు విభాగంలో ఇప్పటికే పేర్కొన్నట్లుగా, ఫైల్‌ను విజయవంతంగా బదిలీ చేయడానికి మీరు దాని పరిమాణాన్ని తెలుసుకోవాలి, సుదీర్ఘ పూర్ణాంకంలో నిల్వ చేయబడుతుంది, కాబట్టి నేను దానిని 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

ఒక వ్యాఖ్యను జోడించండి