Приветствую.
Ma az olyan kliens-szerver alkalmazások írási folyamatát szeretném megvizsgálni, amelyek a szabványos Windows segédprogramok funkcióit látják el, mint például Telnet, TFTP, stb., stb. tiszta Java nyelven. Egyértelmű, hogy nem hozok semmi újat - ezek a segédprogramok több mint egy éve sikeresen működnek, de úgy gondolom, hogy nem mindenki tudja, mi folyik a motorháztető alatt.
Pontosan erről lesz szó a vágás alatt.
Ebben a cikkben, hogy ne húzódjon el, az általános információkon kívül csak a Telnet szerverről írok, de jelenleg más segédprogramokról is van anyag - a sorozat további részeiben lesz.
Először is ki kell találnia, hogy mi az a Telnet, mire van szükség és mire használják. A forrásokat szó szerint nem idézem (ha szükséges, a cikk végén linket csatolok a témával foglalkozó anyagokhoz), csak annyit mondok, hogy a Telnet távoli hozzáférést biztosít az eszköz parancssorához. Nagyjából itt ér véget a funkcionalitása (szándékosan hallgattam a szerverport eléréséről; erről később). Ez azt jelenti, hogy a megvalósításhoz el kell fogadnunk egy sort a kliensen, át kell adni a szervernek, megpróbálni átadni a parancssornak, elolvasni a parancssori választ, ha van, vissza kell adni a kliensnek és jelenítse meg a képernyőn, vagy hiba esetén értesítse a felhasználót, hogy valami nincs rendben.
A fentiek megvalósításához ennek megfelelően szükségünk van 2 munkaosztályra és néhány tesztosztályra, ahonnan elindítjuk a szervert, és amelyen keresztül a kliens fog működni.
Ennek megfelelően a pályázati struktúra jelenleg a következőket tartalmazza:
- TelnetClient
- TelnetClientTester
- TelnetServer
- TelnetServerTester
Nézzük végig mindegyiket:
TelnetClient
Ennek az osztálynak csak a fogadott parancsok elküldésére és a kapott válaszok megjelenítésére kell tudnia. Ezenkívül képesnek kell lennie egy távoli eszköz tetszőleges (a fent említett) portjához csatlakozni, és le kell kapcsolódnia róla.
Ennek érdekében a következő funkciókat valósították meg:
Olyan függvény, amely egy socket címet vesz argumentumként, megnyit egy kapcsolatot, és elindítja a bemeneti és kimeneti adatfolyamokat (a stream változók fent vannak deklarálva, a teljes források a cikk végén találhatók).
public void run(String ip, int port)
{
try {
Socket socket = new Socket(ip, port);
InputStream sin = socket.getInputStream();
OutputStream sout = socket.getOutputStream();
Scanner keyboard = new Scanner(System.in);
reader = new Thread(()->read(keyboard, sout));
writer = new Thread(()->write(sin));
reader.start();
writer.start();
}
catch (Exception e) {
System.out.println(e.getMessage());
}
}
Ugyanazon funkció túlterhelése, csatlakozás az alapértelmezett porthoz - telnet esetén ez a 23
public void run(String ip)
{
run(ip, 23);
}
A funkció beolvassa a karaktereket a billentyűzetről, és elküldi a kimeneti aljzatba - ami jellemző, vonal módban, nem karakter módban:
private void read(Scanner keyboard, OutputStream sout)
{
try {
String input = new String();
while (true) {
input = keyboard.nextLine();
for (char i : (input + " n").toCharArray())
sout.write(i);
}
}
catch (Exception e) {
System.out.println(e.getMessage());
}
}
A funkció adatokat fogad az aljzatból és megjeleníti a képernyőn
private void write(InputStream sin)
{
try {
int tmp;
while (true){
tmp = sin.read();
System.out.print((char)tmp);
}
}
catch (Exception e) {
System.out.println(e.getMessage());
}
}
A funkció leállítja az adatok fogadását és továbbítását
public void stop()
{
reader.stop();
writer.stop();
}
}
TelnetServer
Ennek az osztálynak rendelkeznie kell azzal a funkcióval, hogy parancsot fogadjon egy socketből, elküldje végrehajtásra, és a parancsból választ küldjön vissza a socketbe. A program szándékosan nem ellenőrzi a bemeneti adatokat, mert egyrészt még „dobozos telnetben” is lehet formázni a szerverlemezt, másrészt a biztonság kérdése ebben a cikkben elvileg kimaradt, és ezért nincs néhány szót a titkosításról vagy az SSL-ről.
Csak 2 funkció van (az egyik túlterhelt), és általában ez nem túl jó gyakorlat, de ennek a feladatnak a szempontjából helyénvalónak tűnt, hogy mindent úgy hagyjak, ahogy van.
boolean isRunning = true;
public void run(int port) {
(new Thread(()->{ try {
ServerSocket ss = new ServerSocket(port); // создаем сокет сервера и привязываем его к вышеуказанному порту
System.out.println("Port "+port+" is waiting for connections");
Socket socket = ss.accept();
System.out.println("Connected");
System.out.println();
// Берем входной и выходной потоки сокета, теперь можем получать и отсылать данные клиенту.
InputStream sin = socket.getInputStream();
OutputStream sout = socket.getOutputStream();
Map<String, String> env = System.getenv();
String wayToTemp = env.get("TEMP") + "tmp.txt";
for (int i :("Connectednnr".toCharArray()))
sout.write(i);
sout.flush();
String buffer = new String();
while (isRunning) {
int intReader = 0;
while ((char) intReader != 'n') {
intReader = sin.read();
buffer += (char) intReader;
}
final String inputToSubThread = "cmd /c " + buffer.substring(0, buffer.length()-2) + " 2>&1";
new Thread(()-> {
try {
Process p = Runtime.getRuntime().exec(inputToSubThread);
InputStream out = p.getInputStream();
Scanner fromProcess = new Scanner(out);
try {
while (fromProcess.hasNextLine()) {
String temp = fromProcess.nextLine();
System.out.println(temp);
for (char i : temp.toCharArray())
sout.write(i);
sout.write('n');
sout.write('r');
}
}
catch (Exception e) {
String output = "Something gets wrong... Err code: "+ e.getStackTrace();
System.out.println(output);
for (char i : output.toCharArray())
sout.write(i);
sout.write('n');
sout.write('r');
}
p.getErrorStream().close();
p.getOutputStream().close();
p.getInputStream().close();
sout.flush();
}
catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}).start();
System.out.println(buffer);
buffer = "";
}
}
catch(Exception x) {
System.out.println(x.getMessage());
}})).start();
}
A program megnyitja a szerverportot, addig olvassa be az adatokat, amíg nem találkozik egy parancsvég karakterrel, átadja a parancsot egy új folyamatnak, és átirányítja a kimenetet a folyamatból a socketbe. Minden olyan egyszerű, mint egy Kalasnyikov géppuska.
Ennek megfelelően a funkció túlterhelése van az alapértelmezett porttal:
public void run()
{
run(23);
}
Nos, ennek megfelelően a szervert leállító funkció is triviális, megszakítja az örök hurkot, megsértve annak feltételét.
public void stop()
{
System.out.println("Server was stopped");
this.isRunning = false;
}
Itt nem adok tesztosztályokat, ezek lent találhatók - csak ellenőrzik a nyilvános metódusok működését. Minden a git-en van.
Összefoglalva, néhány este alatt megértheti a fő konzol segédprogramok működési elvét. Most, amikor telenetezünk egy távoli számítógépre, megértjük, mi történik - a varázslat eltűnt)
Szóval a linkek:
Forrás: will.com