Jätkates Windowsi käsurea utiliitide kohandatud rakendustele pühendatud artiklite tsüklit, ei saa jätta mainimata TFTP-d (Trivial File Transfer Protocol) — lihtsat failide edastamise protokolli.
Nagu eelmisel korral, vaatame lühidalt teooriat, tutvume koodiga, mis rakendab sarnast funktsionaalsust, ja analüüsime seda. Rohkem infot — allpool.
Ma ei hakka kopeerima tugiteavet, mille lingid on traditsiooniliseltartikli lõpus, ütlen vaid, et TFTP on oma olemuselt FTP protokolli lihtsustatud variatsioon, milles on eemaldatud juurdepääsu kontrolli seadistus ja tegelikult ei ole siin muud peale failide saamise ja edastamise käskude. Kuid meie rakenduse konkurentsivõimelisema ja tänapäevaste koodikirjutamise põhimõtetega paremini sobiva vormi saavutamiseks on süntaks veidi muudetud — see ei muuda tööpõhimõtteid, kuid kasutajaliides, IMHO, muutub veidi loogilisemaks ja ühendab endas FTP ja TFTP positiivsed küljed.
Kliendi käivitamisel küsib ta serveri IP-aadressi ja porti, millel on avatud kohandatud TFTP. (Kuna standardprotokolliga on ühilduvuse probleem, arvasin, et on mõistlik jätta kasutajale valikuprogramm.) Pärast seda toimub ühendamine, mille tulemusel saab klient edastada ühe käsu - get või put, faili serverisse saamiseks või saatmiseks. Kõik failid saadetakse binaarses režiimis, et lihtsustada loogikat.
Protokolli rakendamiseks kasutasin traditsiooniliselt nelja klassi:
- TFTPClient
- TFTPServer
- TFTPClientTester
- TFTPServerTester
Kuna testimisklassid eksisteerivad vaid põhiüksuste tõrkeotsinguks, ei hakka ma neid arutama, kuid kood on saadaval repois. Link sellele on artikli lõpus. Peamised klassid arutan praegu.
TFTPClient
Selle klassi ülesanne on luua ühendus kaugserveriga tema IP-aadressi ja porti kaudu, lugeda sissetulevat andmevoogu (antud juhul - klaviatuurilt) käsk, analüüsida see, edastada serverile ning sõltuvalt failide saatmise vajadusest edastada või saada see.
Klientprogrammi käivituskood serveriga ühenduse loomiseks ja sisendvoo käsku ootamiseks näeb välja selline. Siin kasutatavaid globaalsete muutujaid kirjeldatakse artikli väliselt, täielikus programmis. Kuna need on triviaalset laadi, ei too ma neid välja, et artiklit mitte üle koormata.
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());
}
}Vaatame lähemalt meetodeid, mida selles koodiblokis kutsutakse:
Siin toimub faili saatmine — skanneri kaudu esindame faili sisu baitide massiivina, mida järk-järgult kirjutame soketisse, seejärel sulgeme selle ja avame uuesti (mitte kõige ilmselgem lahendus, kuid see tagab ressursside vabastamise), pärast mida kuvame ekraanile sõnum eduka edastamise kohta.
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());
}
}See koodijupp kirjeldab andmete saamist serverilt. Kõik on taas triviaalne, huvitav on vaid esimene koodiblokk. Et mõista, kui palju baitide arvu tuleb soketist lugeda, tuleb teada, kui palju küsitav fail kaalub. Faili suurus serveris esitatakse pika täisarvuna, seega siin kasutatakse 4 bait, mis hiljem konverteeritakse üheks arvuks. See ei ole väga Java-esk, pigem on see sarnane C keelele, kuid lahendab oma ülesande.
Edasi on kõik triviaalne — saame teadaoleva baitide arvu soketist ja kirjutame need faili, pärast seda anname välja õnnestumise teate.
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());
}
}Kui kliendi aknasse on sisestatud käsk, mis erineb käskudest get või put, kutsutakse esile funktsioon showErrorMessage, mis kuvab sisendi vale olemuse. Tühi — ei too välja. Veidi huvitavam on sisendstringi saamise ja jagamise funktsioon. Sellele anname skänneri, millest ootame kaht vahemaa vahel räsi, mis sisaldab käsku, allika aadressi ja sihtaadressi.
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("Vale sisend");
}
}Käskude saatmine — sisestatud käsku skännerist edastamine soketisse ja selle sundimine saatma
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());
}
}Valija — funktsioon, mis määrab programmi tegevused vastavalt sisestatud stringile. Siin ei ole kõik kuigi ilus ja kasutatakse mitte kõige paremat tehnikat, sunniviisilise väljumisega koodiploki piiridest, kuid peamine põhjus on see, et Java-s puuduvad teatud asjad, nagu C#-s delegaadid, C++-st funktsioonipunktid või vähemalt hirmsad ja õudsed goto-käsklused, mis võimaldaksid seda ilusamalt rakendada. Kui tead, kuidas koodi veidi elegantsemaks teha — ootan kriitikat kommentaarides. Tundub, et siin on vaja String-delegaadi sõnastikku, kuid delegaate pole...
private void selector()
{
do{
if (typeOfCommand.equals("get")){
get(sourcePath, destPath);
break;
}
if (typeOfCommand.equals("put")){
put(sourcePath, destPath);
break;
}
showErrorMessage();
}
while (false);
}
}TFTPServer
Serveri funktsioon erineb kliendi omadusest peamiselt seetõttu, et käsud saadetakse mitte klaviatuurilt, vaid soketist. Osa meetoditest kattub täielikult, seetõttu ei hakka ma neid loetlema, vaid käsitlen ainult erinevusi.
Siin kasutatakse meetodit run, mis võtab sisendiks porti ja töötleb sissetulevaid andmeid soketist pidevas tsüklis.
public void run(int port) {
this.port = port;
incialization();
while (true) {
getAndParseInput();
selector();
}
}Meetod put, mis on meetodi writeToFileFromSocket ümber, avab faili kirjutamise voog ja salvestab kõik soketist saadud baitid, pärast kirjutamise lõpetamist väljastab sõnum eduka edastamise kohta.
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 meetod võimaldab serverifaili hankimist. Nagu juba mainitud kliendipoolse programmi jaotises, on faili edastamiseks edukalt vajalik teada selle suurust, mis on salvestatud pika täisarvuna, seega jagan selle 4-baidiste massiivideks, edastan need baidid soketisse ja seejärel, saades ja kogudes need tagasi kliendisse numbriks, edastan kõik baidid, mis moodustavad faili, loetuna sisendivoogudest.
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 meetod vastab samale, mis on kliendi pool, vaid erinevusega, et see loeb andmeid soketist, mitte klaviatuurilt. Kood on hoidlates, nagu ka selector.
Antud juhul on initsialiseerimine viidatud eraldi koodiblokki, kuna selle teostuse raames vabastatakse ressursid pärast edastuse lõppu ja võetakse taas kasutusele — taas kaitse tagamiseks mälulekke eest.
private void initsialiseerimine()
{
try {
serverSocket = new ServerSocket(port);
socket = serverSocket.accept();
sin = socket.getInputStream();
sout = socket.getOutputStream();
}
catch (Exception e) {
System.out.print(e.getMessage());
}
}Kokkuvõtteks:
Just kirjutasime oma versiooni lihtsast andmeedastusprotokollist ja selgitasime, kuidas see peaks toimima. Põhimõtteliselt ei avastanud ma siin Ameerikat ja ei kirjutanud midagi väga uudset, kuid — sarnaseid artikleid ei olnud Habras ja cmd-utiliitide artiklite seeria kirjutamise raames ei saanud seda küsimust mööda vaadata.
Lingid:
Allikas: habr.com
