使用libSSH2实现与Linux Shell的命令行交互

使用Linux已经有不少年头,也使用过不少Linux的SSH工具,比如SecureCRT,XShell,Putty,SmartTTY,但都未发现有一个工具可以像Windows资源管理器一样操作Linux下的文件的工具,SecureCRT的同门软件SecureFX有那么点感觉,浏览目录、文件结构还是挺不错的,但是在打开、编辑文件上还是不太流畅。

于是就想着如果自己能搞一个出来应该会很不错。那首先得使用SSH协议连接上服务器,网上查了一下,发现libSSH2还不错,文档,示例这些都有,但是没有完整的命令行交互示例。在example中有一个ssh2.c的示例给出了一个框架:

    /* Open a SHELL on that pty */
    if (libssh2_channel_shell(channel))
    {
        fprintf(stderr, "Unable to request shell on allocated pty\n");
        goto shutdown;
    }

    /* At this point the shell can be interacted with using
     * libssh2_channel_read()
     * libssh2_channel_read_stderr()
     * libssh2_channel_write()
     * libssh2_channel_write_stderr()
     *
     * Blocking mode may be (en|dis)abled with: libssh2_channel_set_blocking()
     * If the server send EOF, libssh2_channel_eof() will return non-0
     * To send EOF to the server use: libssh2_channel_send_eof()
     * A channel can be closed with: libssh2_channel_close()
     * A channel can be freed with: libssh2_channel_free()
     */

skip_shell:
    if (channel)
    {
        libssh2_channel_free(channel);
        channel = NULL;
    }

    /* Other channel types are supported via:
     * libssh2_scp_send()
     * libssh2_scp_recv2()
     * libssh2_channel_direct_tcpip()
     */

这个框架只是以注释的形式给出了一些API,说在那个位置可以使用这些API来实现与Shell的交互。
但是如果只是单独使用这些API可能只能进行一个回合的交互,达不到一个完整的像见的ssh工具那样输入一个命令后,显示结果,再等待输入下一个命令的效果。

笔者结合另一个示例ssh2_echo.c,添加使用libssh2_poll来监测channel的读写,实现了简单的命令行交互(不支持像top这样可以刷新的命令):

enum state
{
    INCOMPLETED,
    COMPLETED,
    TIMEOUT
};

ssize_t handle_read(LIBSSH2_CHANNEL *channel, char *buffer, size_t buf_size, enum state *state, int timeout)
{
    LIBSSH2_POLLFD fds;
    fds.type = LIBSSH2_POLLFD_CHANNEL;
    fds.fd.channel = channel;
    fds.events = LIBSSH2_POLLFD_POLLIN | LIBSSH2_POLLFD_POLLOUT;

    ssize_t read_size = 0;
    while (timeout > 0)
    {
        int rc = (libssh2_poll(&fds, 1, 10));
        if (rc < 1)
        {
            timeout -= 10;
            usleep(10000);
            continue;
        }

        if (fds.revents & LIBSSH2_POLLFD_POLLIN)
        {
            int n = libssh2_channel_read(channel, &buffer[read_size], buf_size - read_size);
            if (n == LIBSSH2_ERROR_EAGAIN)
            {
                continue;
            }
            else if (n < 0)
            {
                *state = COMPLETED;
                return read_size;
            }
            else
            {
                read_size += n;
                if (libssh2_channel_eof(channel))
                {
                    *state = COMPLETED;
                    return read_size;
                }
                char end = buffer[read_size - 2];
                if (end == '$' || end == '#')
                {
                    *state = COMPLETED;
                    return read_size;
                }
            }
            if (read_size == buf_size)
            {
                *state = INCOMPLETED;
                return read_size;
            }
        }
        usleep(10000);
        timeout -= 10;
    }

    *state = TIMEOUT;
    return 0;
}

void handle_loop(LIBSSH2_CHANNEL *channel)
{
    char buffer[8192];
    char cmd[64];
    int len = 0;
    ssize_t n;
    while (1)
    {
        enum state state = INCOMPLETED;
        do
        {
            n = handle_read(channel, buffer, sizeof(buffer) - 1, &state, 3000);
            if (state == TIMEOUT)
            {
                if (len > 0)
                {
                    cmd[len - 1] = 0;
                }
                printf("exec cmd:`%s` timeout\n", cmd);
                break;
            }
            buffer[n] = 0;
            if (len > 0)
            {
                printf("%s", &buffer[len + 1]);
                len = 0;
            }
            else
            {
                printf("%s", buffer);
            }
        } while (state == INCOMPLETED);

        fgets(cmd, sizeof(cmd), stdin);
        len = strlen(cmd);
        libssh2_channel_write(channel, cmd, len);
    }
    libssh2_channel_close(channel);
}

只需要skip_shell前面调用handle_loop即可。这里还实现了检测读取超时和读取Buffer是否写满,如果写满了则输出了再读取。

这里需要注意libssh2_poll接口,在第一次调用时,一般不会有LIBSSH2_POLLFD_POLLIN事件,需要再次调用才会有,跟了一下代码,第一次调用时在接收数据,准备好数据后,但并没有设置LIBSSH2_POLLFD_POLLIN事件,所以需要循环读取,libssh2_poll的超时参数有的情况不起作用,参见代码:

if(active_fds) {
            /* Don't block on the sockets if we have channels/listeners which
               are ready */
            timeout_remaining = 0;
        }

注意需要在支持颜色的Shell终端中执行,否则会有各种颜色标识输出,会干扰正常信息。 可以在VSCode终端、MinGW终端中执行。

以上代码在MinGW64下测试通过。

你可能感兴趣的:(Linux,linux,ssh,libSSH2,shell,交互)