如何修改TCP套接字程序实现服务器发命令并获取客户端执行结果
TCP Socket 命令执行与结果回传修改方案
需求说明
当前TCP套接字代码仅实现客户端发送内容、服务器回显的功能,现需修改为:服务端下发Shell命令(如
ls、ls -l、ls -l | wc、ls > result.txt等),客户端执行该命令并将执行结果返回给服务端。
修改后的服务端代码(server.c)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { int portno; int sockfd, ret; struct sockaddr_in serverAddr; int newSocket; struct sockaddr_in newAddr; socklen_t addr_size; char buffer[4096]; // 扩大缓冲区以容纳命令执行结果 pid_t childpid; if (argc != 2) { printf("Usage: %s <port>\n", argv[0]); exit(1); } sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { printf("[-]Error in creating socket.\n"); exit(1); } printf("[+]Server Socket is created.\n"); portno = atoi(argv[1]); memset(&serverAddr, '\0', sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(portno); serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有可用网卡,而非仅127.0.0.1 ret = bind(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); if (ret < 0) { printf("[-]Error in binding.\n"); exit(1); } printf("[+]Bind to port %d\n", portno); if (listen(sockfd, 10) == 0) { printf("[+]Listening....\n"); } else { printf("[-]Error in listening.\n"); exit(1); } while (1) { newSocket = accept(sockfd, (struct sockaddr*)&newAddr, &addr_size); if (newSocket < 0) { printf("[-]Error in accepting connection.\n"); continue; } printf("Connection accepted from %s:%d\n", inet_ntoa(newAddr.sin_addr), ntohs(newAddr.sin_port)); if ((childpid = fork()) == 0) { close(sockfd); // 子进程不需要监听套接字 while (1) { // 服务端输入要下发的命令 printf("Enter command to send (or 'QUIT' to exit): "); fgets(buffer, sizeof(buffer), stdin); buffer[strcspn(buffer, "\n")] = '\0'; // 去掉换行符 if (strcmp(buffer, "QUIT") == 0) { send(newSocket, buffer, strlen(buffer), 0); printf("[-]Disconnected from %s:%d\n", inet_ntoa(newAddr.sin_addr), ntohs(newAddr.sin_port)); break; } // 发送命令给客户端 send(newSocket, buffer, strlen(buffer), 0); // 接收客户端返回的命令执行结果 bzero(buffer, sizeof(buffer)); ssize_t bytes_received = recv(newSocket, buffer, sizeof(buffer)-1, 0); if (bytes_received < 0) { printf("[-]Error in receiving result.\n"); break; } else if (bytes_received == 0) { printf("[-]Client disconnected.\n"); break; } buffer[bytes_received] = '\0'; // 确保字符串终止 printf("\nClient execution result:\n%s\n", buffer); } close(newSocket); exit(0); } } close(sockfd); return 0; }
修改后的客户端代码(client.c)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/wait.h> #define BUFFER_SIZE 4096 // 执行Shell命令并返回输出结果 char* execute_command(const char* cmd) { FILE* pipe = popen(cmd, "r"); if (!pipe) { return strdup("Error: Failed to execute command.\n"); } char* result = malloc(BUFFER_SIZE); bzero(result, BUFFER_SIZE); char buffer[1024]; size_t total_len = 0; while (fgets(buffer, sizeof(buffer), pipe) != NULL) { size_t len = strlen(buffer); if (total_len + len < BUFFER_SIZE) { strcat(result, buffer); total_len += len; } else { // 缓冲区不足时截断并提示 strncat(result, "[Output truncated due to buffer limit]\n", BUFFER_SIZE - total_len - 1); break; } } pclose(pipe); return result; } int main(int argc, char *argv[]) { int portno; int clientSocket, ret; struct sockaddr_in serverAddr; char buffer[BUFFER_SIZE]; if (argc != 3) { printf("Usage: %s <server_ip> <port>\n", argv[0]); exit(1); } clientSocket = socket(AF_INET, SOCK_STREAM, 0); if (clientSocket < 0) { printf("[-]Error in creating socket.\n"); exit(1); } printf("[+]Client Socket is created.\n"); portno = atoi(argv[2]); memset(&serverAddr, '\0', sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(portno); // 使用传入的服务器IP而非固定127.0.0.1 if (inet_pton(AF_INET, argv[1], &serverAddr.sin_addr) <= 0) { printf("[-]Invalid server IP address.\n"); exit(1); } ret = connect(clientSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); if (ret < 0) { printf("[-]Error in connecting to server.\n"); exit(1); } printf("[+]Connected to Server.\n"); while (1) { // 接收服务端下发的命令 bzero(buffer, sizeof(buffer)); ssize_t bytes_received = recv(clientSocket, buffer, sizeof(buffer)-1, 0); if (bytes_received < 0) { printf("[-]Error in receiving command.\n"); break; } else if (bytes_received == 0) { printf("[-]Server disconnected.\n"); break; } buffer[bytes_received] = '\0'; // 处理退出命令 if (strcmp(buffer, "QUIT") == 0) { printf("[-]Server requested disconnect. Exiting.\n"); break; } printf("[+]Received command: %s\n", buffer); // 执行命令并获取结果 char* result = execute_command(buffer); // 将结果发送回服务端 send(clientSocket, result, strlen(result), 0); free(result); // 释放内存 } close(clientSocket); return 0; }
关键修改解析
服务端修改点
- 命令输入逻辑:新增服务端控制台输入命令的功能,用
fgets读取命令并自动去除换行符,支持QUIT命令主动断开连接。 - 缓冲区扩容:把缓冲区从1024字节调整为4096字节,避免长命令输出被截断。
- 监听地址优化:将固定
127.0.0.1改为INADDR_ANY,允许服务端接收来自任意网卡的客户端连接(按需可改回固定IP)。 - 参数校验与错误提示:增加命令行参数检查,优化连接、接收过程中的错误提示信息,方便调试。
客户端修改点
- 命令执行封装:新增
execute_command函数,通过popen调用Shell执行命令并捕获标准输出,还处理了缓冲区溢出的截断提示。 - 动态服务器IP:支持通过命令行参数传入服务端IP,不再固定为127.0.0.1,增强跨机器部署的灵活性。
- 命令处理流程:接收服务端命令后先判断是否为退出指令,否则执行命令并将结果回传给服务端。
- 内存管理:手动管理命令执行结果的内存分配与释放,避免内存泄漏问题。
编译与运行说明
- 编译服务端:
gcc server.c -o server - 编译客户端:
gcc client.c -o client - 启动服务端:
./server 8080(替换为自定义端口) - 启动客户端:
./client 127.0.0.1 8080(替换为服务端实际IP和端口) - 在服务端控制台输入命令(如
ls -l),即可接收客户端返回的执行结果。
内容的提问来源于stack exchange,提问作者John Taramas
相关产品推荐
相关产品推荐

