模拟XShell的小项目

该项目旨在模拟XShell的功能,通过TCP Socket在Windows客户端与Linux服务器端之间传输命令并展示执行结果。使用popen函数而非system调用,以避免子shell带来的问题。执行流程包括客户端发送命令,服务器执行命令并保存结果,再将结果回传至客户端展示。代码简洁,直接展示了LinuxServer和WindowsClient的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

不知道大家有没有用过XShell这款工具,这款工具通过windows可以远程操作处于开机状态的linux操作系统,也就是说把你的电脑和一台服务器连入网络,你通过输入服务器所在的IP地址建立一个会话就可以远端操作linux的服务器了,十分方便。

这次这个模拟XShell的小项目就是类似的功能

 

执行流程:

windows客户端输入命令,通过网络传输到linux服务器端上,linux服务器端执行命令,将执行命令产生的结果保存进文件,然后再将文件传输回windows客户端进行展示。

 

问题思考:真的有必要将结果保存在文件当中么?可以通过管道直接把结果文件流书写到socket上,然后客户端直接读取socket上的数据,省去书写和读取文件的时间

 

说一下大概的思路,很简单的一个大体思路。

完成通信的方式是通过socket编程实现的,也就是说,启用在linux的服务器端和启用在windows的客户端之间交换命令和结果集都是通过socket传输的。

执行命令的方式不采用system执行系统调用的方式,因为这么做可能产生如下后果:对于shell执行命令来说,大多数时候都是产生一个子shell来执行命令,他本身不会亲自涉险,因为一旦用户编写的程序含有什么严重BUG会导致终端出现错误而停止运行,这时人和linux之间的交互方式就被切断了,尤其是在没有图形界面的纯命令操作环境下,连正常关机都很难办到,所以为了避免这种情况的发生shell会产生一个子shell进程来执行。这就意味着有可能你在切换工作目录的时候(比如说你用cd这个命令),就只会在子进程shell中切换工作目录,然后你打算在新目录下做点什么的时候会发现,你欸切换目录其实失败了,因为在父亲shell下你根本没有切换过目录。

那么我采用了popen这个函数,本身这个函数是通过管道来实现的,这个函数通过传参的方式读入命令,然后可以通过管道的另一端将返回的结果集书写进文件里面。然后我把文件传输回windows显示出来。

执行流程很简单不是什么难题。因为是tcp是流形式的,在windows一段采用二进制读写就可以了。

因为代码不是很多,就直接贴出来了

LinuxServer(Linux服务器端)

#include <stdlib.h> 
#include <stdio.h> 
#include <errno.h> 
#include <string.h> 
#include <netdb.h> 
#include <sys/types.h> 
#include <netinet/in.h> 
#include <sys/socket.h> 
#include <arpa/inet.h> //inet_ntoa()函数的头文件
#include <string>
#include <unistd.h>
#include <iostream>
using namespace std;



bool Judge(char *buf,size_t size)
{
    for(int i =0;i<size;++i)
    {
        if(buf[i]!=0)
        {
            return false;
        }
    }
    return true;
}


#define portnumber 8001//定义端口号:(0-1024为保留端口号,最好不要用)

int main(int argc, char *argv[])
{
    int sockfd, new_fd;
    struct sockaddr_in server_addr; //描述服务器地址
    struct sockaddr_in client_addr; //描述客户端地址
    socklen_t sin_size;
    char hello[] = "Hello! Are You Fine?\n";


    /* 服务器端开始建立sockfd描述符 */
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) // AF_INET:IPV4;SOCK_STREAM:TCP
    {
        fprintf(stderr, "Socket error:%s\n\a", strerror(errno));
        exit(1);
    }

    /* 服务器端填充 sockaddr结构 */
    bzero(&server_addr, sizeof(struct sockaddr_in)); // 初始化,置0
    server_addr.sin_family = AF_INET; // Internet
    //server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // (将本机器上的long数据转化为网络上的long数据)和任何主机通信 //INADDR_ANY 表示可以接收任意IP地址的数据,即绑定到所有的IP
    server_addr.sin_addr.s_addr=inet_addr("192.168.84.128"); //用于绑定到一个固定IP,inet_addr用于把数字加格式的ip转化为整形ip
    server_addr.sin_port = htons(portnumber); // (将本机器上的short数据转化为网络上的short数据)端口号

                                            
    if (bind(sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) == -1)
    {
        fprintf(stderr, "Bind error:%s\n\a", strerror(errno));
        exit(1);
    }  /* 捆绑sockfd描述符到IP地址 */

    /* 设置允许连接的最大客户端数 */
    if (listen(sockfd, 5) == -1)
    {
        fprintf(stderr, "Listen error:%s\n\a", strerror(errno));
        exit(1);
    }


    /* 服务器阻塞,直到客户程序建立连接 */
    sin_size = sizeof(struct sockaddr_in);
    new_fd = accept(sockfd, (struct sockaddr *)(&client_addr), &sin_size);
    cout<<"连接成功等待输入并接收命令字符串"<<endl;
    if (new_fd == -1)
    {
        fprintf(stderr, "Accept error:%s\n\a", strerror(errno));
        exit(1);
    }
    char recvbuf[512] = { 0 };
    char sendbuf[512] = { 0 };
    while (1)
    {
        //等待接受字符串,也就是等待命令的输入
        cout<<"等待接收命令字符串"<<endl;
        int len = recv(new_fd, recvbuf,  512, 0);
        string tmp(recvbuf);
        cout<<"接受字符串,等待书写结果集:"<<recvbuf<<endl;

        //执行传输过来的字符串并且解析成命令行命令并执行制作成结果集文件等待传输
        //核心就是popen函数
        FILE   *stream;  
           FILE    *wstream;
           char   buf[1024]; 
     
            memset( buf, '\0', sizeof(buf) );//初始化buf,以免后面写如乱码到文件中
            stream = popen( tmp.c_str(), "r" ); //将“ls -l”命令的输出 通过管道读取(“r”参数)到FILE* stream
            wstream = fopen( "result.txt", "w+"); //新建一个可写的文件

            size_t lengt=fread( buf, sizeof(char), sizeof(buf),  stream);  //将刚刚FILE* stream的数据流读取到buf中
            fwrite( buf, 1, lengt, wstream );//不要写1024的buf的大小,在linux下会多出一部分\0,读取到多少结果就往文件中写多少
    
            pclose( stream );  
            fclose( wstream );
        cout<<"结果集书写完毕"<<endl;
        system("iconv result.txt -f UTF-8 -t GBK -o resultfile.txt ");



        FILE *fp;
        fp=fopen("resultfile.txt","r");
        int count=0;
        //传输结果集文件
        while (1)
        {
            //fseek(fp,0,SEEK_SET);
            //memset(sendbuf, 0, 512);//这句话是否有必要还有待考究
            int len = fread(sendbuf, 1, 512,fp );
            send(new_fd, sendbuf, len, 0);
            //cout<<"传输中"<<count<<endl;
            cout<<sendbuf<<endl;
            count++;
            cout<<"count:"<<count<<endl;
            //    printf("sendBuf:%c\n",* sendBuf);
            //    printf("len:%d\n", len);
            if (Judge(sendbuf,512))
            {
                cout<<"退出"<<endl;
                //send(new_fd,sendbuf,len,0);//发送全空字符串?!
                break;
            }
            memset(sendbuf, 0, 512);
        }


        cout<<"结果集传输完毕"<<endl;
        memset(recvbuf, 0, 512);
    }
    cout<<"传输完毕"<<endl;
    /* 结束通讯 */
    close(new_fd);
    close(sockfd);
    exit(0);
}


WindowsClient(Windows客户端)

//客户端

#define    _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<winsock2.h>
#include<string.h>
#include<stdlib.h>
#include<iostream>
#include<string>
#include<windows.h>
#include<locale.h>
using namespace std;

bool Judge(char *buf, size_t size)
{
    for (int i = 0; i < size; ++i)
    {
        if (buf[i] != 0)
        {
            return false;
        }
    }
    return true;
}

#pragma comment(lib, "ws2_32.lib")
int main()
{
    WORD wVersionRequested;  //typedef unsigned short WORD
    WSADATA wsaData;   //用阿里存储系统传回的关于WinSocket的资料
    int err;  //用来判断启动函数是否正常

    wVersionRequested = MAKEWORD(1, 1);

    err = WSAStartup(wVersionRequested, &wsaData);

    if (err != 0)
    {
        return -1;
    }

    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
        WSACleanup();
        return -1;
    }

    SOCKET socketClient = socket(AF_INET, SOCK_STREAM, 0);   //AF_INET tcpip的协议
                                                             //初始化连接
    SOCKADDR_IN addrSrv;  //服务器的地址
    addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.84.128");
    addrSrv.sin_family = AF_INET;  //使用的是TCP/IP 
    addrSrv.sin_port = htons(8001);  //转为网络序  设置端口号

                                   //连接到服务器 使用SOCKET对象连接服务器,送入服务器的地址信息  强转
    if (connect(socketClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)) < 0)  //协议参数  套接字参数 
    {
        printf("connction faild!");
        closesocket(socketClient);
        return 0;
    }

    //如果连接到了  那就先传出命令字符串再去接收命令执行结果集
    char recvBuf[512] = { 0 };
    char sendBuf[512] = { 0 };
    
//    FILE* fp = fopen("resultfile.txt", "w");//真的有必要再写入一个文件里面么?
//    if (!fp)
//        return 0;


    //写入文件
    while (1)
    {
        //输入并传输命令
        cin >> sendBuf;
        send(socketClient, sendBuf, 512, 0);
        //cout << "已经传输了命令,等待接受结果集" << endl;
        memset(sendBuf, 0, 512);

        while (1)
        {
            //接收结果集
            int len = recv(socketClient, recvBuf, 512, 0);
        //    cout << "接受结果集" << endl;
            //fwrite(recvBuf, 1, len, fp);
            cout << recvBuf << endl;
        //    cout << "len:" << len << endl;
        //    cout << "打印结果集" << endl;
            //printf("%s\n", recvBuf);
            
            if (Judge(recvBuf,512))
            {
        //        cout << "我要退出接受结果集的循环了" << endl;
                break;
            }
            memset(recvBuf, 0, 512);
    //        cout << "清空缓冲区" << endl;
        }

    }






    //char recvBuf[50] = {0};

    //int size = 10;
    //int i = 0;
    //while (i<100)
    //{
    //    //recv(socketClient, recvBuf, 50, 0);  //socket对象已经接收到数据,现在开始次on个缓存区中取数据
    //    //printf("%s\n", recvBuf);
    //    send(socketClient, "123456789" , 50, 0);
    //    //Sleep(100);
    //}

    closesocket(socketClient);  //关闭socket连接

    WSACleanup();

    printf("Client exit!");
    system("pause");
    return 0;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值