基于libssh2实现连接服务器 进行shell命令实时交互

255 阅读5分钟

基于libssh2_pollrequest_pty_exc shell等函数实现与服务器的实时交互

libssh2_poll函数一定要理解 用途很重要

#include "mainwindow.h"
#include<iostream>
#include <QApplication>
#include "libssh2.h"
#include<winsock2.h>
#include<QThread>
#include<QDebug>
using namespace std;

string username="****";
string password="*****";
string ip="*******";

SOCKET  Sock=INVALID_SOCKET;
LIBSSH2_SESSION *Session=nullptr; //һ���Ự
LIBSSH2_CHANNEL *ExecChannel;
LIBSSH2_CHANNEL *ListeningChannel[3]={nullptr,nullptr,nullptr}; //3������ͨ��
bool running=true;


bool connectToServer()
{
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        cout << "WSAStartup failed" << endl;
        return false;
    }

    Sock = socket(AF_INET, SOCK_STREAM, 0);
    if (Sock == INVALID_SOCKET) {
        cout << "Failed to create socket" << endl;
        WSACleanup();
        return false;
    }
    //���÷�������ַ
    sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(22);
    server_addr.sin_addr.s_addr = inet_addr(ip.c_str()); //ipv4��ַ תΪ�����ֽ���

    // ȷ��ʹ����ȷ������ת�� connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) != 0
    if (::connect(Sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) != 0) {
        cout << "Connection to server failed" << endl;
        closesocket(Sock);
        WSACleanup();
        return false;
    }
    qDebug()<<"Server connect successfully"<<endl;
    return true;
}
bool sessionCreate()
{

    if (libssh2_init(0) != 0) {//libssh2��ʼ��
        cout << "libssh2 initialization failed" << endl;
        closesocket(Sock);
        WSACleanup();
        return false;
    }

    Session = libssh2_session_init(); //ssh�Ự��������
    if (!Session) {
        cout << "Failed to create SSH session" << endl;
        closesocket(Sock);
        WSACleanup();
        return false;
    }

    // ���� SSH ����
    if (libssh2_session_handshake(Session, Sock) != 0) {
        fprintf(stderr, "Failed to establish SSH session\n");
        libssh2_session_free(Session);
        closesocket(Sock);
        WSACleanup();
        return false;
    }

    // �����û���֤
    if (libssh2_userauth_password(Session, username.c_str(), password.c_str()) != 0) {
        fprintf(stderr, "Authentication failed\n");
        libssh2_session_disconnect(Session, "Normal Shutdown");
        libssh2_session_free(Session);
        closesocket(Sock);
        WSACleanup();
        return false;
    }

    cout<<" Session sycAuthenticed successfully!"<<endl;

    return true;
}
bool disconnectFromServer()
{
    if(libssh2_session_disconnect(Session, "Normal Shutdown")!=0){
        return false;
    }
    libssh2_session_free(Session);
    if(closesocket(Sock)!=0){
        return false;
    }
    if(WSACleanup()!=0){
        return false;
    }
    cout<<"Disconnected from server."<<endl;
    return true;
}
void cleanAllChannels(){
    libssh2_channel_free(ExecChannel);

}

bool createExecChannel() {
    // Open a session for ExecChannel and request a shell
    ExecChannel = libssh2_channel_open_session(Session);
    if (!ExecChannel) {
        fprintf(stderr, "Unable to open session channel.\n");
        libssh2_session_free(Session);
        close(Sock);
        return false;
    }

    // 请求伪终端
    const char *term = "vanilla";
    int term_w = 80;
    int term_h = 50;
    int rc;
    qDebug() << "init 设置当前终端高=" << term_w << " 宽=" << term_h;

    rc = libssh2_channel_request_pty_ex(ExecChannel, term, (unsigned int)strlen(term), NULL, 0, term_w, term_h, 0, 0);
    if (rc!=0) {
        fprintf(stderr, "Pty request failed with code %d\n", rc);
        libssh2_channel_free(ExecChannel);
        libssh2_session_free(Session);
        close(Sock);
        return false;
    }
    // 启动 shell
    rc = libssh2_channel_shell(ExecChannel);
    if (rc!=0) {
        fprintf(stderr, "Shell request failed with code %d\n", rc);
        libssh2_channel_free(ExecChannel);
        libssh2_session_free(Session);
        close(Sock);
        return false;
    }
    qDebug()<<"伪终端 shell请求成功";

    qDebug()<<"ending...";

    return true;
}

void poll_channels() {
    //vector<LIBSSH2_POLLFD> active_fds;

    // Initialize active channels
    LIBSSH2_POLLFD fds;

    if (ExecChannel) {
        fds.type = LIBSSH2_POLLFD_CHANNEL;
        fds.fd.channel = ExecChannel;
        fds.events = LIBSSH2_POLLFD_POLLIN | LIBSSH2_POLLFD_POLLOUT;
        //active_fds.push_back(fds);
    }

    int count=0;
    // Event loop
    while (running){
        count++;


        int rc = libssh2_poll(&fds, 1, 5000); // 5s timeout

        qDebug() << "rc is " << rc;

        // rc>0 有事件发生
        if (rc > 0) {

            //有数据发生 对每一个通道进行通道事件监测
            if (fds.revents & LIBSSH2_POLLFD_POLLIN) {

                qDebug()<<"reading events happeded!=============================================================";

                // Read data from the channel

                char buffer[9996];

                int bytesRead;
                bytesRead= libssh2_channel_read(fds.fd.channel, buffer, sizeof(buffer)-1);

                buffer[bytesRead] = '\0'; //添加字符串结束符

                if (bytesRead < 0){
                    // Handle error
                    libssh2_channel_close(fds.fd.channel);
                    libssh2_channel_free(fds.fd.channel);
                }
                else if (bytesRead == 0){
                    qDebug()<<"there is no useful data in  channel";
                    // Channel closed
                    libssh2_channel_close(fds.fd.channel);
                    libssh2_channel_free(fds.fd.channel);
                }
                else if(bytesRead>0){

                    printf("execChannel received data: %s ", buffer);

                }
            }

            else if (fds.revents & LIBSSH2_POLLFD_POLLOUT) {

                qDebug()<<"you can send your command  here";
                string command;
                getline(cin,command);
                cout<<"the command you want to excute is "<< command <<endl;
                libssh2_channel_write(ExecChannel, command.c_str(),command.size());
                libssh2_channel_write(ExecChannel, "\n",2);

            }

            else if (fds.revents & LIBSSH2_POLLFD_CHANNEL_CLOSED) {
                qDebug()<<" channel closed events................................................";
                libssh2_channel_free(fds.fd.channel);

            }
            else {
                qDebug()<<"unknown events";
            }

        }


        else if (rc == 0) {
            qDebug() << "rc == 0, no events happened";
        }

        else {
            qDebug() << "Poll error";
            break;
        }
        qDebug()<<"";
        qDebug()<<"sleeping...";
        // Sleep(6000);

    }
    qDebug() << "Poll ending";
}

//状态枚举
enum state
{
    INCOMPLETED,
    COMPLETED,
    TIMEOUT
};

//在有限时间之内 对通道进行事件的轮询 读取数据
int handle_read(LIBSSH2_CHANNEL *channel, char *buffer, int 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;

    int read_size = 0; //读取字节数

    while (timeout > 0)
    {
        int rc = (libssh2_poll(&fds, 1, 10));
        //无事件发生
        if (rc < 1)
        {
            timeout -= 10;
            Sleep(1);
            continue;
        }
//通道数据可读
        if (fds.revents & LIBSSH2_POLLFD_POLLIN)
        {
//            ssize_t libssh2_channel_read(LIBSSH2_CHANNEL *channel, char *buf, size_t buflen);
//            char *buf:
//            类型:指向字符数组的指针。
//            描述:用于存储读取的数据的缓冲区。在调用此函数之前,您需要确保该缓冲区的大小足够大,以容纳即将读取的数据。
//            size_t buflen:
//            类型:无符号整型,表示缓冲区的大小。
//            描述:指定 buf 缓冲区的最大字节数。函数将最多读取 buflen 字节的数据。

            int n = libssh2_channel_read(channel, &buffer[read_size], buf_size - read_size);

//          &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; //更新读取字节数

                //libssh2_channel_eof 是 libssh2 库中用于检查 SSH 通道状态的一个函数。
                //它的主要作用是判断通道是否已达到文件结束(EOF),即远程端是否已经关闭了该通道。
                //0 通道未关闭     非0 关闭
                if (libssh2_channel_eof(channel)!=0)//如果通道关闭了
                {
                    *state = COMPLETED;
                    return read_size;
                }


                char end = buffer[read_size - 2];
                if (end == '$' || end == '#') //参考Linux命令行 root@iZ2vc3fc08d4hrgzl7gcvnZ:/#  ’#‘后面还有个空格
                {
                    *state = COMPLETED;
                    cout<<"##########################################################"<<endl;
                    return read_size;
                }

            }

            if (read_size == buf_size)
            {
                *state = INCOMPLETED;
                return read_size;
            }
        }

        Sleep(1);
        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)
            {

//                当用户输入的命令被发送到通道后,缓冲区可能包含来自通道的新数据。
//                &buffer[len + 1] 表示从 buffer 中的 len + 1 位置开始输出,意味着将跳过用户输入的命令及其后的空字符。
//                处理分段输出:
//                由于 SSH 通道的输出可能是分段的,使用 &buffer[len + 1] 可以确保在输出时不会重复打印用户输入的命令,而是从命令之后的内容开始
//                cout<<"len>0-----------------------"<<endl;
                 printf("%s", &buffer[len+1]);
//                cout<<"111111111111111111111111111111111111"<<endl;
                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);
}


int main()
{
    qDebug()<<"启动";

    connectToServer();
    sessionCreate();
    createExecChannel();
    handle_loop(ExecChannel);

    return 0;
}