JĂ€tkates Windowsi kĂ€surea utiliitide kohandatud rakendustele pĂŒhendatud artiklite seeriat, on vĂ”imatu mitte mainida TFTP-d (Trivial File Transfer Protocol) â lihtsat failide edastamise protokolli.
Nii nagu eelmisel korral, vaatame lĂŒhidalt teooriat, nĂ€eme koodi, mis rakendab sarnast funktsionaalsust ning analĂŒĂŒsime seda. Ăksikasjad â allpool.
Ma ei hakka kopeerima abiteavet, mille lingid on traditsiooniliselt artikli lĂ”pus, vaid ĂŒtlen lihtsalt, et TFTP on oma olemuselt FTP protokolli lihtsustatud versioon, kus on eemaldatud juurdepÀÀsu juhtimise seaded ning siin pole tegelikult midagi peale faili vastuvĂ”tmise ja saatmise kĂ€skude. Kuid et muuta meie rakendus veidi elegantsesksemaks ja kohandatumaks praeguste koodi kirjutamise pĂ”himĂ”tetega, on sĂŒntaks veidi muudetud â tööpĂ”himĂ”tted ei muutu, kuid liides, IMHO, muutub veidi loogilisemaks ja ĂŒhendab enda sees FTP ja TFTP positiivsed kĂŒljed.
Konkreetsemalt, kui klient kĂ€ivitub, kĂŒsib ta serveri IP-aadressi ja pordi, millel on avatud kohandatud TFTP (tĂ€nu ĂŒhilduvuse puudumisele standardse protokolliga, leidsin, et on mĂ”istlik jĂ€tta kasutajale vĂ”imalus pordi valimine), pĂ€rast mida toimub ĂŒhendamine, mille tulemusena saab klient saata ĂŒhe kĂ€sku â get vĂ”i put, et faili serverisse saada vĂ”i sealt edastada. KĂ”ik failid saadetakse binaarses reĆŸiimis â loogika lihtsustamiseks.
Protokolli rakendamiseks kasutasin traditsiooniliselt 4 klassi:
- TFTPClient
- TFTPServer
- TFTPClientTester
- TFTPServerTester
Kuna testimisklassid eksisteerivad ainult peamiste klasside silumiseks, ei hakka ma neid kĂ€sitlema, kuid kood on saadaval repos, mille link on artikli lĂ”pus. Peamised klassid kĂ€sitlen nĂŒĂŒd.
TFTPClient
Selle klassi ĂŒlesanne on luua ĂŒhendus kaugarvutiga tema IP-aadressi ja pordi numbri kaudu, lugeda sisendvoost (sel juhul â klaviatuurilt) kĂ€su, selle tĂ”lgendada, edastada serverile ning sĂ”ltuvalt sellest, kas on vajalik faili saatmine vĂ”i vastuvĂ”tmine, edastada see vĂ”i saada.
Kood kliendi kĂ€ivitamiseks serveriga ĂŒhenduse loomiseks ja kĂ€su ootamiseks sisendivoolust nĂ€eb vĂ€lja jĂ€rgmine. Rida globaalsetest muutujatest, mida siin kasutatakse, kirjeldatakse artikli piiridest vĂ€ljaspool, tĂ€ielikus programmi tekstis. TĂ€nu nende triviaalusele ei tohi ma neid esitada, et mitte koormata artiklit.
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 meetodeid, mida kutsutakse ĂŒles selles koodiblokis:
Siin toimub faili saatmine â skanneri abil esitame faili sisu baitide massiivina, mille kirjutame jĂ€rjestikku soketti, pĂ€rast mida sulgeme selle ja avame uuesti (mitte kĂ”ige ilmsem lahendus, aga see garanteerib ressursside vabastamise), seejĂ€rel kuvame ekraanile teate eduka ĂŒlekande 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 koodifragmendi kirjeldab andmete hankimist serverist. KĂ”ik on jĂ€lle triviaalne, huvi pakub ainult esimene koodiblokk. Selleks, et mĂ”ista, kui palju baiti soketist lugeda, tuleb teada, kui suur on saadetavfail. Faili suurus serveris esitatakse pika tĂ€isarvuna, seega vaatame siin 4 baiti, mis hiljem konverteeritakse ĂŒheks numbriks. See pole kuigi Java-sarnane lĂ€henemine, pigem sobib see C-le, aga see lahendab oma ĂŒlesande.
Edasi on kĂ”ik triviaalne â saame teadaoleva arvu balade soketist ja kirjutame need faili, pĂ€rast mida kuvame edusammudest 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 kliendiakenisse sisestatakse kĂ€sk, mis erineb get vĂ”i put kĂ€skudest, kĂ€ivitatakse funktsioon showErrorMessage, mis nĂ€itab sisendi vigasust. Kuna see on triviaalne, ei too ma seda siin vĂ€lja. Statistiliselt huvitavam on funktsioon sisendstringi saamiseks ja jagamiseks. Edastame sinna skanneri, kellelt ootame kahtelise tĂŒhikuga eraldatud stringi, 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Ă€sku edastamine â skannerist sisestatud kĂ€su edastamine soketisse ja selle kohustuslik saatmine.
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 sĂ”ltuvalt sisestatud stringist. Siin ei ole kĂ”ik vĂ€ga ilus ja kasutatakse mitte kĂ”ige paremat lĂ€henemist sunnitud vĂ€ljumisega koodiplokist, kuid peamine pĂ”hjus selle taga on Java puudumine mĂ”ningatest asjadest, nagu C# delegaadid, C++ funktsiooninĂ€itajad vĂ”i vĂ€hemalt hirmutav ja kole goto, mis vĂ”imaldavad selle elegantselt ellu viia. Kui teate, kuidas koodi veidi elegantsimaks teha â ootan kriitikat kommentaarides. Minu arvates peaks siin olema sĂ”nastik String-delegeeritud, kuid delegaat puudub...
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 funktsionaalsus erineb kliendi funktsionaalsusest pĂ”himĂ”tteliselt ainult selle poolest, et kĂ€skude edastamine toimub mitte klaviatuurilt, vaid soketist. Osa meetodeid on ĂŒldse sarnased, seetĂ”ttu ei tooda ma neid vĂ€lja, vaid kĂ€sitlen ainult erinevusi.
Selleks, et see kĂ€ivitada, kasutatakse meetodit run, mis vĂ”tab sisendiks pordi ja töötleb soketist saabunud andmeid lĂ”putus tsĂŒklis.
public void run(int port) {
this.port = port;
incialization();
while (true) {
getAndParseInput();
selector();
}
}Put meetod, mis on wrappe kirjutamisprotseduurist writeToFileFromSocket, avab kirjutamistrandi faili ning salvestab kÔik soketist saadud baitid; pÀrast kirjutamise lÔpetamist kuvab see eduka edastamise teadet.
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 serveri faili kĂ€tte saada. Nagu juba mainitud programmi kliendi poole osas, peab faili edastamiseks teadma selle suurust, mis on salvestatud pika tĂ€isarvuna, seetĂ”ttu jagan selle 4 baitide massiivi, saadan need soketi kaudu ĂŒkshaaval ning pĂ€rast nende vastuvĂ”tmist ja kliendi poole kokku panemist edastan kĂ”ik failist loetud baitid sissevoolu kaudu.
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 sarnaneb kliendimoodulile, ainus erinevus on, et see loeb andmeid soketist, mitte klaviatuurilt. Kood ĂŒhenduses on sama nagu selector.
Antud juhul on algatamine eraldi koodiblokis, kuna antud teostuses vabastatakse ressursid edastamise lĂ”pus ja vĂ”etakse uuesti kasutusele â taas, et vĂ€ltida mĂ€lu lekkeid.
private void initialization()
{
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 variatsiooni lihtsast andmete edastamise protokollist ja mĂ”istsime, kuidas see peaks töötama. PĂ”himĂ”tteliselt ei avastanud ma siin Ameerikat ja midagi vĂ€ga uut ei kirjutatud, kuid sarnaseid artikleid Habras ei olnud ning cmd tööriistade kirjutamise tsĂŒklis oleks olnud vale selle ĂŒle mitte arutada.
Lingid:
Allikas: habr.com
