编写具有 Windows 客户端-服务器实用程序功能的软件,第 01 部分

欢迎光临!

今天,我想了解一下编写客户端-服务器应用程序的过程,这些应用程序执行标准 Windows 实用程序的功能,例如用纯 Java 编写的 Telnet、TFTP 等。 很明显,我不会带来任何新内容 - 所有这些实用程序都已成功运行一年多,但我相信并不是每个人都知道幕后发生了什么。

这正是将在削减中讨论的内容。

在本文中,为了不拖沓,除了一般信息之外,我将只写有关 Telnet 服务器的内容,但目前还有有关其他实用程序的材料 - 它将在本系列的后续部分中。

首先你要弄清楚Telnet是什么,需要它做什么,有什么用。 我不会逐字引用来源(如有必要,我会在文章末尾附上有关该主题的材料的链接),我只会说 Telnet 提供了对设备命令行的远程访问。 总的来说,这就是它的功能结束的地方(我故意对访问服务器端口保持沉默;稍后会详细介绍)。 这意味着要实现它,我们需要在客户端接受一行,将其传递给服务器,尝试将其传递给命令行,读取命令行响应,如果有,将其传递回客户端并显示在屏幕上显示,或者,如果出现错误,让用户知道出现了问题。

因此,为了实现上述内容,我们需要 2 个工作类和一些测试类,我们将通过它们启动服务器并通过它们来运行客户端。
因此,目前应用程序结构包括:

  • 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());
        }
    }

该函数从socket接收数据并显示在屏幕上


    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”中也可以格式化服务器磁盘,其次,本文原则上忽略了安全问题,这就是为什么没有关于加密或 SSL 的一些话。

只有 2 个函数(其中一个已重载),一般来说这不是一个很好的做法,但出于此任务的目的,我认为保留所有内容原样似乎是合适的。

 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

添加评论