I den fortsatta serien av artiklar om anpassade implementationer av Windows-konsolverktyg kan TFTP (Trivial File Transfer Protocol) â en enkel filöverföringsprotokoll â inte undvikas.
Som förra gÄngen kommer vi kort gÄ igenom teorin, se koden som implementerar funktionaliteten liknande den som krÀvs, och analysera den. Mer information finns nedan.
Jag kommer inte att kopiera och klistra in referensinformation, lĂ€nkar till vilken som traditionellt kan hittas i slutet av artikeln, jag sĂ€ger bara att TFTP i grunden Ă€r en förenklad variant av FTP-protokollet, dĂ€r Ă„tkomstkontrollinstĂ€llningar tagits bort, och det finns i huvudsak inget annat Ă€n kommandon för att hĂ€mta och skicka filer. För att göra vĂ„r implementation lite mer elegant och anpassad till dagens kodningsprinciper har syntaxen Ă€ndrats nĂ„got â detta pĂ„verkar inte arbetsprinciperna men grĂ€nssnittet, enligt min Ă„sikt, blir nĂ„got mer logiskt och kombinerar de positiva sidorna av bĂ„de FTP och TFTP.
Vid uppstart begĂ€r klienten IP-adressen till servern och porten dĂ€r den anpassade TFTP Ă€r öppen (pĂ„ grund av inkompatibilitet med det standardprotokollet ansĂ„g jag det lĂ€mpligt att ge anvĂ€ndaren möjlighet att vĂ€lja port), varefter en anslutning sker, vilket gör det möjligt för klienten att skicka ett av kommandona â get eller put, för att hĂ€mta eller skicka filen till servern. Alla filer skickas i binĂ€rt lĂ€ge â för att förenkla logiken.
För att implementera protokollet har jag traditionellt anvÀnt 4 klasser:
- TFTPClient
- TFTPServer
- TFTPClientTester
- TFTPServerTester
Eftersom testklasserna endast finns för att debugga de grundlÀggande klasserna kommer jag inte att gÄ igenom dem, men koden kommer att finnas i repositoryt, och en lÀnk till det kan hittas i slutet av artikeln. Nu kommer jag att gÄ igenom de grundlÀggande klasserna.
TFTPClient
Uppgiften för denna klass Àr att ansluta till den fjÀrrservern via dess IP och portnummer, lÀsa kommandot frÄn inmatningsströmmen (i detta fall tangentbordet), parsa det, skicka det till servern, och beroende pÄ om överföring eller mottagning av fil Àr nödvÀndig, antingen skicka eller hÀmta den.
Koden för att starta klienten för att ansluta till servern och vÀnta pÄ kommandon frÄn inmatningsströmmen ser ut sÄ hÀr. En mÀngd globala variabler som anvÀnds hÀr beskrivs utanför artikeln, i den fullstÀndiga koden. PÄ grund av deras trivialitet tar jag inte med dem för att inte överbelasta artikeln.
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());
}
}LÄt oss gÄ igenom metoderna som anropas i denna kodblock:
HĂ€r sker överföringen av filen â med hjĂ€lp av scanner representerar vi innehĂ„llet av filen som en bytearray, som vi sedan successivt skriver till socketen, varefter vi stĂ€nger den och öppnar den pĂ„ nytt (inte den mest uppenbara lösningen, men den garanterar resursfrigöring), och dĂ€refter skriver vi ut ett meddelande om att överföringen lyckades.
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());
}
}Denna kodsnutt beskriver hur man hÀmtar data frÄn servern. à terigen Àr det trivialt, intressant Àr bara den första kodblocken. För att förstÄ hur mÄnga byte som behöver lÀsas frÄn socketen, mÄste man veta hur mycket den överförda filen vÀger. Filstorleken pÄ servern presenteras som ett lÄngt heltal, sÄ hÀr tas 4 byte emot som sedan konverteras till ett tal. Detta Àr inte riktigt ett Java-sÀtt, det liknar mer C, men det löser sin uppgift.
Sedan Ă€r allt trivialt â vi tar emot ett kĂ€nt antal byte frĂ„n socketen och skriver dem till en fil, varefter vi visar ett meddelande om framgĂ„ng.
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());
}
}Om ett kommando som skiljer sig frĂ„n get eller put anges i klientfönstret, kommer funktionen showErrorMessage att anropas för att visa ogiltig input. PĂ„ grund av dess trivialitet â jag tar inte med det. Lite intressantare Ă€r funktionen för att ta emot och dela upp ingĂ„ngsstrĂ€ngen. Vi skickar en scanner som vi förvĂ€ntar oss ger oss en strĂ€ng, uppdelad med tvĂ„ mellanslag, som innehĂ„ller ett kommando, kĂ€lladi, och destinationsadress.
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("Ogiltig input");
}
}Att skicka kommandot innebÀr att överföra det angivna kommandot frÄn scannern till socketen och tvingat skicka det.
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());
}
}VĂ€ljaren Ă€r en funktion som definierar programmets Ă„tgĂ€rder beroende pĂ„ den angivna strĂ€ngen. HĂ€r Ă€r det inte sĂ„ vackert och en inte sĂ„ bra metod anvĂ€nds, dĂ€r blocket tvingas att gĂ„ utanför sin kodblock, men huvudanledningen Ă€r att Java saknar vissa funktioner, sĂ„som delegater i C#, funktionspekare frĂ„n C++ eller Ă„tminstone det skrĂ€mmande och förfĂ€rliga goto, vilket gör det möjligt att genomföra detta pĂ„ ett snyggt sĂ€tt. Om du vet hur man gör koden lite mer elegant â jag ser fram emot kritik i kommentarerna. Det verkar som att en String-delegat-ordbok behövs, men det finns ingen 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
Serverns funktionalitet skiljer sig i stort sett bara frÄn klientens funktionalitet genom att kommandona inte kommer frÄn tangentbordet, utan frÄn socketen. En del metoder sammanfaller helt och hÄllet, sÄ jag kommer inte att ta med dem, utan fokuserar pÄ skillnaderna.
För att starta anvÀnds metoden run, som fÄr en port som inparameter och hanterar indata frÄn socketen i en oÀndlig loop.
public void run(int port) {
this.port = port;
incialization();
while (true) {
getAndParseInput();
selector();
}
}Metoden put, som Àr en omslagsmetod för writeToFileFromSocket-metoden, öppnar en skrivström till en fil och skriver alla indata frÄn socketen. NÀr skrivningen avslutas, visas ett meddelande om att överföringen slutförts framgÄngsrikt.
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());
}
}Metoden get tillhandahÄller hÀmtning av serverfilen. Som nÀmnts i avsnittet om programmets klientsida, för att en filöverföring ska lyckas, mÄste filens storlek, som lagras som ett lÄngt heltal, vara kÀnd. DÀrför delas storleken upp i en array av 4 byte, som skickas byte för byte till socketen, och sedan, efter att ha mottagit och samlat dem till ett tal pÄ klienten, skickas alla byte som utgör filen som lÀsts frÄn indataflödet.
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());
}
}; Metoden getAndParseInput överensstÀmmer med den motsvarande i klienten, med den enda skillnaden att den lÀser data frÄn socketen istÀllet för frÄn tangentbordet. Koden i repositoryt Àr som selector.
I det hĂ€r fallet har initialiseringen flyttats till ett separat kodblock, eftersom resurserna frigörs efter att överföringen Ă€r slutförd, och sedan Ă„teranvĂ€nds â Ă„terigen för att skydda mot minneslĂ€ckor.
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());
}
}Sammanfattningsvis:
Nyss skrev vi vÄr egen variation av ett enkelt protokoll för dataöverföring och gick igenom hur det ska fungera. I princip har jag inte kommit med nÄgot revolutionerande, men det fanns inga liknande artiklar pÄ Habra, och i arbetet med att skriva en artikelserie om cmd-verktyg kunde jag inte lÄta bli att ta upp det.
LĂ€nkar:
KĂ€lla: habr.com
