ようこそ。
今日は、Telnet、TFTP などの標準 Windows ユーティリティの機能を Pure Java で実行するクライアント/サーバー アプリケーションを作成するプロセスを見ていきたいと思います。 私が何も新しいものを導入しないことは明らかです。これらのユーティリティはすべて XNUMX 年以上正常に機能していますが、内部で何が起こっているのかを誰もが知っているわけではないと思います。
これはまさにこのカットの下で議論されることです。
この記事では、話が長くならないように、一般的な情報に加えて、Telnet サーバーについてのみ書きますが、現時点では他のユーティリティに関する資料もあります。これについては、シリーズの今後の部分で説明する予定です。
まず、Telnet とは何なのか、何に必要なのか、何に使用されるのかを理解する必要があります。 ソースをそのまま引用することはしません (必要に応じて、このトピックに関する資料へのリンクを記事の最後に添付します)。Telnet がデバイスのコマンド ラインへのリモート アクセスを提供することだけを述べます。 概して、これでその機能は終了します (サーバー ポートへのアクセスについては意図的に沈黙していました。これについては後で詳しく説明します)。 つまり、これを実装するには、クライアントで行を受け入れ、それをサーバーに渡し、コマンドラインに渡してみて、コマンドラインの応答を読み取り、存在する場合はそれをクライアントに返し、それを画面に表示するか、エラーが発生した場合は、何かが間違っていることをユーザーに知らせます。
したがって、上記を実装するには、2 つの作業クラスと、サーバーを起動してクライアントが動作するテスト クラスが必要になります。
したがって、現時点ではアプリケーション構造には次のものが含まれます。
- Telnet クライアント
- Telnetクライアントテスター
- Telnetサーバー
- Telnetサーバーテスター
それぞれを見てみましょう。
Telnet クライアント
このクラスができることは、受信したコマンドを送信し、受信した応答を表示することだけです。 さらに、リモート デバイスの任意の (前述の) ポートに接続したり、そこから切断したりできる必要があります。
これを実現するために、次の機能が実装されました。
ソケット アドレスを引数として受け取り、接続を開き、入出力ストリームを開始する関数 (ストリーム変数は上で宣言されており、完全なソースは記事の最後にあります)。
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());
}
}
同じ関数をオーバーロードし、デフォルトのポートに接続します - Telnet の場合、これは 23 です
public void run(String ip)
{
run(ip, 23);
}
この関数はキーボードから文字を読み取り、出力ソケットに送信します。これは通常、文字モードではなくライン モードで行われます。
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());
}
}
この関数はソケットからデータを受信し、画面に表示します。
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());
}
}
データの送受信を停止する機能です
public void stop()
{
reader.stop();
writer.stop();
}
}
Telnetサーバー
このクラスには、ソケットからコマンドを受信し、それを実行のために送信し、コマンドからの応答をソケットに送り返す機能が必要です。 プログラムは意図的に入力データをチェックしません。これは、第一に、「ボックス Telnet」でもサーバー ディスクをフォーマットすることが可能であり、第二に、この記事ではセキュリティの問題が原則として省略されているためです。暗号化または SSL について一言。
関数は 2 つしかなく (そのうちの XNUMX つはオーバーロードされています)、一般にこれはあまり良い習慣ではありませんが、このタスクの目的では、すべてをそのままにしておくのが適切であると考えました。
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();
}
プログラムはサーバー ポートを開き、コマンド終了文字に到達するまでそこからデータを読み取り、コマンドを新しいプロセスに渡し、出力をプロセスからソケットにリダイレクトします。 すべてはカラシニコフ突撃銃と同じくらい簡単です。
したがって、デフォルトのポートを使用したこの関数にはオーバーロードがあります。
public void run()
{
run(23);
}
したがって、サーバーを停止する関数も簡単で、永遠のループを中断し、その条件に違反します。
public void stop()
{
System.out.println("Server was stopped");
this.isRunning = false;
}
テスト クラスはここでは提供しません。テスト クラスは以下にあります。テスト クラスが行うのは、パブリック メソッドの機能をチェックすることだけです。 すべては git 上にあります。
要約すると、数晩あれば、メイン コンソール ユーティリティの操作原理を理解できるようになります。 さて、リモート コンピュータにテレネットすると、何が起こっているかがわかります - 魔法は消えました)
したがって、リンクは次のとおりです。
出所: habr.com