Duke vazhduar ciklin e artikujve të dedikuar për implementimet e personalizuara të mjeteve të komandës në Windows, nuk mund të mos përmendim TFTP (Protokolli i Transferit të Skedarëve të Thjeshtë) — një protokoll i thjeshtë për transferimin e skedarëve.
Ashtu si herën e kaluar, do të bëjmë një përmbledhje të shkurtër të teorisë, do të shohim kodin që implementon funksionalitetin e ngjashëm me atë që ne kërkojmë dhe do ta analizojmë atë. Më shumë — nën vijë.
Nuk do të kopjoj informacionin ndihmës, lidhjet për të cilat zakonisht mund t'i gjeni në fund të artikullit, do të them vetëm se në thelb TFTP është një variant i thjeshtuar i protokollit FTP, ku është hequr konfigurimi i kontrollit të aksesit dhe në thelb nuk ka asgjë tjetër përveç komandave për marrjen dhe dërgimin e skedarit. Megjithatë, për ta bërë implementimin tonë pak më elegant dhe të adaptuar ndaj principeve të sotme të kodimit, sintaksa është ndryshuar pak — kjo nuk ndryshon principet e funksionimit, por ndihmon që interfeusi, IMHO, të jetë pak më logjik dhe të kombinonte avantazhet e FTP dhe TFTP.
Në veçanti, kur ulni klientin, ai kërkon adresën IP të serverit dhe portin ku është hapur TFTP i personalizuar (për shkak të moskompatibilitetit me protokollin standard, e konsiderova të arsyeshme të lë mundësinë për të zgjedhur portin për përdoruesin), pas së cilës ndodh lidhja, rezultat i së cilës është se klienti mund të dërgojë një nga urdhrat — get ose put, për të marrë ose dërguar një skedar në server. Të gjitha skedarët dërgohen në mënyrë binare — për të thjeshtuar logjikën.
Për realizimin e protokollit, unë kam përdorur tradicionalisht 4 klasa:
- TFTPClient
- TFTPServer
- TFTPClientTester
- TFTPServerTester
Për shkak se klasat testuese ekzistojnë vetëm për debugging të klasave kryesore, unë nuk do t’i shqyrtoj ato, por kodi do të jetë në repositor, me një lidhje për të mund të konsultoheni në fund të artikullit. Tani do të shqyrtoj klasat kryesore.
TFTPClient
Qëllimi i kësaj klase është të lidhet me serverin e largët përmes adresës së tij IP dhe numrit të portit, të lexojë nga rrjedha hyrëse (në këtë rast — tastiera) urdhrin, ta analizojë, ta dërgojë te serveri, dhe, në varësi të nevojës për transferim ose marrje skedari, ta dërgojë ose të marrë atë.
Kodi i lançimit të klientit për t'u lidhur me serverin dhe për të pritur një urdhër nga rrjedha e hyrjes duket kështu. Një sërë variablash globalë, që përdoren këtu, jepen jashtë artikullit, në tekstin e plotë të programit. Për shkak të trivialisë së tyre, nuk i sjell, për të mos e ngarkuar artikullin.
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());
}
}Të kalojmë në metodat që thirren në këtë bllok kodi:
Këtu ndodh dërgimi i skedarit — me ndihmën e skanuesit ne e paraqesim përmbajtjen e skedarit si një masiv bajtesh, të cilat i shkruajmë radhazi në soket, pastaj e mbyllim dhe e hapim përsëri (një zgjidhje e cila nuk është e dukshme, por garanton lirimin e burimeve), pas së cilës shfaqim në ekran një mesazh për dërgimin e suksesshëm.
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());
}
}Ky fragment kodi përshkruan marrjen e të dhënave nga serveri. Gjithçka është prapë triviale, e rëndësishme është vetëm blloku i parë i kodit. Për të kuptuar saktësisht sa byte duhet të lexojmë nga socket-i, duhet të dimë se sa peshojnë skedari që po dërgohet. Madhësia e skedarit në сервер paraqitet si një numër i gjatë, prandaj këtu merren 4 byte, të cilat më pas konvertohen në një numër. Ky nuk është një qasje shumë Javane, është më tepër e ngjashme me C, por zgjidh problemin e vet.
Më pas gjithçka është triviale — ne marrim një numër të njohur byte-esh nga socket-i dhe i shkruajmë ato në skedarin, dhe pastaj japim një mesazh mbi suksesin.
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());
}
}Në rast se në dritaren e klientit është futur një komandë e ndryshme nga get ose put, do të thirret funksioni showErrorMessage, i cili tregon se inputi nuk është i saktë. Për shkak të trivialitetit — nuk po e përmend. Më interesant është funksioni i marrjes dhe ndarjes së vargut të hyrjes. Në të ne kalojmë skanerin, nga i cili presim që të marrim një varg të ndarë me dy hapësira dhe që përmban komandën, adresën burim dhe adresën destinacion.
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("Input i keq");
}
}Dërgimi i komandës — kalimi i komandës së futur nga skaneri në soket dhe dërgimi i saj me forcë
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());
}
}Selektoři — një funksion që përcakton veprimet e programit në varësi të vargut të futur. Këtu gjithçka nuk është shumë e bukur dhe përdoret një teknikë jo shumë e mirë me daljen e detyruar jashtë bllokut të kodit, por arsyeja kryesore për këtë është mungesa e disa gjërave në Java, si delegatët në C#, treguesit e funksionit nga C++ ose edhe një goto e tmerrshme, që lejojnë implementimin e kësaj me elegancë. Nëse e dini si ta bëni kodin më të bukur — pres kritikën tuaj në komente. Më duket se një fjalor String-delegate do të ishte i nevojshëm, por nuk ka delegatë…
private void selector()
{
do{
if (typeOfCommand.equals("get")){
get(sourcePath, destPath);
break;
}
if (typeOfCommand.equals("put")){
put(sourcePath, destPath);
break;
}
showErrorMessage();
}
while (false);
}
}TFTPServer
Funksionaliteti i serverit ndryshon nga ai i klientit kryesisht në mënyrën se si komandat vijnë; ato nuk vijnë nga tastiera, por nga një socket. Disa metoda përputhen krejtësisht, kështu që nuk do t'i përmend atje, do të trajtoj vetëm dallimet.
Për të filluar, përdoret metoda run, e cila merr portin si input dhe përpunon të dhënat hyrëse nga socket-i në një cikël të pafund.
public void run(int port) {
this.port = port;
incialization();
while (true) {
getAndParseInput();
selector();
}
}Metoda put, që është një mbështjellës i metodës writeToFileFromSocket, hap një rrjedhë shkrimi në skedar dhe shkruan të gjitha bajtat e hyrjes nga socket-i; pas përfundimit të shkrimit, nxjerr një mesazh për përfundimin e suksesshëm të transferimit.
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());
}
}Metoda get siguron marrjen e skedarit të serverit. Siç është përmendur në pjesën për anën e klientit të programit, për të siguruar një transmetim të suksesshëm të skedarit, duhet të dihet madhësia e tij, e cila ruhet në një tërë të gjatë, kështu që unë e ndaj atë në një array prej 4 bajtësh, duke i dërguar një nga një në soket, dhe pastaj, duke i pranuar dhe rikonstruktuar ato në klient në një numër, dërgoj të gjitha bajtët që përbëjnë skedarin, të lexuara nga rrjedha e hyrjes së skedarit.
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());
}
}; Metoda getAndParseInput është e njëjtë me atë në anën e klientit, me përjashtim të asaj se ajo lexon të dhënat nga soketi, jo nga tastiera. Kodi në depo, ashtu si dhe selector.
Në këtë rast, inicializimi është nxjerrë në një bllok të veçantë kode, sepse në kuadër të kësaj zbatimi, pas përfundimit të transmetimit, burimet lirohen dhe rikthehen sërish — përsëri me qëllimin për të siguruar mbrojtjen nga rrjedhjet e memories.
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());
}
}Në përfundim:
Sapo shkruam variacionin tonë mbi protokollin e thjeshtë të transmetimit të të dhënave dhe kuptuam se si duhet të funksionojë. Në thelb, nuk kam zbuluar Amerikën këtu dhe nuk kam shkruar diçka shumë të re, por — nuk kishte artikuj të ngjashëm në Habrë, dhe në kuadër të shkruarjes së një cikli artikujsh për utilitat e cmd, nuk mund ta kalonim pa e përmendur.
Linket:
Burimi: habr.com
