不知道大家有沒有用過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服務器端)
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <errno.h> 4 #include <string.h> 5 #include <netdb.h> 6 #include <sys/types.h> 7 #include <netinet/in.h> 8 #include <sys/socket.h> 9 #include <arpa/inet.h> //inet_ntoa()函數的頭文件 10 #include <string> 11 #include <unistd.h> 12 #include <iostream> 13 using namespace std; 14 15 16 17 bool Judge(char *buf,size_t size) 18 { 19 for(int i =0;i<size;++i) 20 { 21 if(buf[i]!=0) 22 { 23 return false; 24 } 25 } 26 return true; 27 } 28 29 30 #define portnumber 8001//定義端口號:(0-1024為保留端口號,最好不要用) 31 32 int main(int argc, char *argv[]) 33 { 34 int sockfd, new_fd; 35 struct sockaddr_in server_addr; //描述服務器地址 36 struct sockaddr_in client_addr; //描述客戶端地址 37 socklen_t sin_size; 38 char hello[] = "Hello! Are You Fine?\n"; 39 40 41 /* 服務器端開始建立sockfd描述符 */ 42 if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) // AF_INET:IPV4;SOCK_STREAM:TCP 43 { 44 fprintf(stderr, "Socket error:%s\n\a", strerror(errno)); 45 exit(1); 46 } 47 48 /* 服務器端填充 sockaddr結構 */ 49 bzero(&server_addr, sizeof(struct sockaddr_in)); // 初始化,置0 50 server_addr.sin_family = AF_INET; // Internet 51 //server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // (將本機器上的long數據轉化為網絡上的long數據)和任何主機通信 //INADDR_ANY 表示可以接收任意IP地址的數據,即綁定到所有的IP 52 server_addr.sin_addr.s_addr=inet_addr("192.168.84.128"); //用於綁定到一個固定IP,inet_addr用於把數字加格式的ip轉化為整形ip 53 server_addr.sin_port = htons(portnumber); // (將本機器上的short數據轉化為網絡上的short數據)端口號 54 55 56 if (bind(sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) == -1) 57 { 58 fprintf(stderr, "Bind error:%s\n\a", strerror(errno)); 59 exit(1); 60 } /* 捆綁sockfd描述符到IP地址 */ 61 62 /* 設置允許連接的最大客戶端數 */ 63 if (listen(sockfd, 5) == -1) 64 { 65 fprintf(stderr, "Listen error:%s\n\a", strerror(errno)); 66 exit(1); 67 } 68 69 70 /* 服務器阻塞,直到客戶程序建立連接 */ 71 sin_size = sizeof(struct sockaddr_in); 72 new_fd = accept(sockfd, (struct sockaddr *)(&client_addr), &sin_size); 73 cout<<"連接成功等待輸入並接收命令字符串"<<endl; 74 if (new_fd == -1) 75 { 76 fprintf(stderr, "Accept error:%s\n\a", strerror(errno)); 77 exit(1); 78 } 79 char recvbuf[512] = { 0 }; 80 char sendbuf[512] = { 0 }; 81 while (1) 82 { 83 //等待接受字符串,也就是等待命令的輸入 84 cout<<"等待接收命令字符串"<<endl; 85 int len = recv(new_fd, recvbuf, 512, 0); 86 string tmp(recvbuf); 87 cout<<"接受字符串,等待書寫結果集:"<<recvbuf<<endl; 88 89 //執行傳輸過來的字符串並且解析成命令行命令並執行制作成結果集文件等待傳輸 90 //核心就是popen函數 91 FILE *stream; 92 FILE *wstream; 93 char buf[1024]; 94 95 memset( buf, '\0', sizeof(buf) );//初始化buf,以免後面寫如亂碼到文件中 96 stream = popen( tmp.c_str(), "r" ); //將“ls -l”命令的輸出 通過管道讀取(“r”參數)到FILE* stream 97 wstream = fopen( "result.txt", "w+"); //新建一個可寫的文件 98 99 size_t lengt=fread( buf, sizeof(char), sizeof(buf), stream); //將剛剛FILE* stream的數據流讀取到buf中 100 fwrite( buf, 1, lengt, wstream );//不要寫1024的buf的大小,在linux下會多出一部分\0,讀取到多少結果就往文件中寫多少 101 102 pclose( stream ); 103 fclose( wstream ); 104 cout<<"結果集書寫完畢"<<endl; 105 system("iconv result.txt -f UTF-8 -t GBK -o resultfile.txt "); 106 107 108 109 FILE *fp; 110 fp=fopen("resultfile.txt","r"); 111 int count=0; 112 //傳輸結果集文件 113 while (1) 114 { 115 //fseek(fp,0,SEEK_SET); 116 //memset(sendbuf, 0, 512);//這句話是否有必要還有待考究 117 int len = fread(sendbuf, 1, 512,fp ); 118 send(new_fd, sendbuf, len, 0); 119 //cout<<"傳輸中"<<count<<endl; 120 cout<<sendbuf<<endl; 121 count++; 122 cout<<"count:"<<count<<endl; 123 // printf("sendBuf:%c\n",* sendBuf); 124 // printf("len:%d\n", len); 125 if (Judge(sendbuf,512)) 126 { 127 cout<<"退出"<<endl; 128 //send(new_fd,sendbuf,len,0);//發送全空字符串?! 129 break; 130 } 131 memset(sendbuf, 0, 512); 132 } 133 134 135 cout<<"結果集傳輸完畢"<<endl; 136 memset(recvbuf, 0, 512); 137 } 138 cout<<"傳輸完畢"<<endl; 139 /* 結束通訊 */ 140 close(new_fd); 141 close(sockfd); 142 exit(0); 143 }
WindowsClient(Windows客戶端)
1 //客戶端 2 3 #define _CRT_SECURE_NO_WARNINGS 4 #include<stdio.h> 5 #include<stdlib.h> 6 #include<winsock2.h> 7 #include<string.h> 8 #include<stdlib.h> 9 #include<iostream> 10 #include<string> 11 #include<windows.h> 12 #include<locale.h> 13 using namespace std; 14 15 bool Judge(char *buf, size_t size) 16 { 17 for (int i = 0; i < size; ++i) 18 { 19 if (buf[i] != 0) 20 { 21 return false; 22 } 23 } 24 return true; 25 } 26 27 #pragma comment(lib, "ws2_32.lib") 28 int main() 29 { 30 WORD wVersionRequested; //typedef unsigned short WORD 31 WSADATA wsaData; //用阿裡存儲系統傳回的關於WinSocket的資料 32 int err; //用來判斷啟動函數是否正常 33 34 wVersionRequested = MAKEWORD(1, 1); 35 36 err = WSAStartup(wVersionRequested, &wsaData); 37 38 if (err != 0) 39 { 40 return -1; 41 } 42 43 if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) 44 { 45 WSACleanup(); 46 return -1; 47 } 48 49 SOCKET socketClient = socket(AF_INET, SOCK_STREAM, 0); //AF_INET tcpip的協議 50 //初始化連接 51 SOCKADDR_IN addrSrv; //服務器的地址 52 addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.84.128"); 53 addrSrv.sin_family = AF_INET; //使用的是TCP/IP 54 addrSrv.sin_port = htons(8001); //轉為網絡序 設置端口號 55 56 //連接到服務器 使用SOCKET對象連接服務器,送入服務器的地址信息 強轉 57 if (connect(socketClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)) < 0) //協議參數 套接字參數 58 { 59 printf("connction faild!"); 60 closesocket(socketClient); 61 return 0; 62 } 63 64 //如果連接到了 那就先傳出命令字符串再去接收命令執行結果集 65 char recvBuf[512] = { 0 }; 66 char sendBuf[512] = { 0 }; 67 68 // FILE* fp = fopen("resultfile.txt", "w");//真的有必要再寫入一個文件裡面麼? 69 // if (!fp) 70 // return 0; 71 72 73 //寫入文件 74 while (1) 75 { 76 //輸入並傳輸命令 77 cin >> sendBuf; 78 send(socketClient, sendBuf, 512, 0); 79 //cout << "已經傳輸了命令,等待接受結果集" << endl; 80 memset(sendBuf, 0, 512); 81 82 while (1) 83 { 84 //接收結果集 85 int len = recv(socketClient, recvBuf, 512, 0); 86 // cout << "接受結果集" << endl; 87 //fwrite(recvBuf, 1, len, fp); 88 cout << recvBuf << endl; 89 // cout << "len:" << len << endl; 90 // cout << "打印結果集" << endl; 91 //printf("%s\n", recvBuf); 92 93 if (Judge(recvBuf,512)) 94 { 95 // cout << "我要退出接受結果集的循環了" << endl; 96 break; 97 } 98 memset(recvBuf, 0, 512); 99 // cout << "清空緩沖區" << endl; 100 } 101 102 } 103 104 105 106 107 108 109 //char recvBuf[50] = {0}; 110 111 //int size = 10; 112 //int i = 0; 113 //while (i<100) 114 //{ 115 // //recv(socketClient, recvBuf, 50, 0); //socket對象已經接收到數據,現在開始次on個緩存區中取數據 116 // //printf("%s\n", recvBuf); 117 // send(socketClient, "123456789" , 50, 0); 118 // //Sleep(100); 119 //} 120 121 closesocket(socketClient); //關閉socket連接 122 123 WSACleanup(); 124 125 printf("Client exit!"); 126 system("pause"); 127 return 0; 128 129 }