当前位置:首页 > 系统教程 > 正文

Linux网络编程实战:手把手实现远程命令执行 (基于TCP Socket的完整教程)

Linux网络编程实战:手把手实现远程命令执行 (基于TCP Socket的完整教程)

欢迎来到Linux网络编程实战教程!本文将带你从零开始,使用TCP Socket实现一个简单的远程命令执行系统。通过本教程,你将掌握Socket编程实战的核心技能,包括socket创建、绑定、监听、连接、数据传输以及命令执行与结果返回。无论你是初学者还是有一定经验的开发者,都能从中获得实用的知识和代码。

Linux网络编程实战:手把手实现远程命令执行 (基于TCP Socket的完整教程) Linux网络编程 TCP Socket 远程命令执行 Socket编程实战 第1张

1. 环境准备与基础知识

确保你有一台Linux机器(如Ubuntu/CentOS),并安装gcc编译器:sudo apt install gccsudo yum install gcc。你需要了解基本的TCP/IP协议栈,以及socket API函数:socket(), bind(), listen(), accept(), connect(), send(), recv()

2. 服务端实现步骤

服务端负责监听端口,接受客户端连接,然后循环接收命令,通过popen()执行并将输出返回给客户端。我们采用“长度+数据”的格式解决TCP粘包问题:先发送4字节(网络字节序)的数据长度,再发送实际数据。

服务端完整代码 (server.c)

#include #include #include #include #include #include #include #include #define PORT 8888#define BUFFER_SIZE 4096// 发送全部数据(处理短写)int send_all(int sockfd, const char *buf, int len) {    int total = 0;    while (total < len) {        int n = send(sockfd, buf + total, len - total, 0);        if (n == -1) break;        total += n;    }    return total;}// 接收指定长度数据int recv_all(int sockfd, char *buf, int len) {    int total = 0;    while (total < len) {        int n = recv(sockfd, buf + total, len - total, 0);        if (n <= 0) break;        total += n;    }    return total;}int main() {    int server_fd, client_fd;    struct sockaddr_in server_addr, client_addr;    socklen_t client_len = sizeof(client_addr);    char cmd[BUFFER_SIZE];    char result[BUFFER_SIZE];    FILE *fp;    // 创建socket    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {        perror("socket");        exit(1);    }    // 设置端口复用    int opt = 1;    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {        perror("setsockopt");        exit(1);    }    // 绑定地址和端口    memset(&server_addr, 0, sizeof(server_addr));    server_addr.sin_family = AF_INET;    server_addr.sin_addr.s_addr = INADDR_ANY;    server_addr.sin_port = htons(PORT);    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {        perror("bind");        exit(1);    }    // 监听    if (listen(server_fd, 5) == -1) {        perror("listen");        exit(1);    }    printf("[服务端] 启动成功,监听端口 %d...\n", PORT);    while (1) {        // 接受客户端连接        client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);        if (client_fd == -1) {            perror("accept");            continue;        }        printf("[客户端] %s:%d 已连接\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));        // 循环处理命令        while (1) {            // 先接收命令长度(4字节,网络字节序)            int cmd_len;            if (recv_all(client_fd, (char*)&cmd_len, 4) != 4) {                printf("[客户端] 断开连接\n");                break;            }            cmd_len = ntohl(cmd_len);  // 转换为主机字节序            // 接收命令内容            if (cmd_len >= BUFFER_SIZE) {                printf("[警告] 命令过长,丢弃\n");                // 跳过数据                char tmp[1024];                while (cmd_len > 0) {                    int n = recv(client_fd, tmp, sizeof(tmp), 0);                    if (n <= 0) break;                    cmd_len -= n;                }                continue;            }            if (recv_all(client_fd, cmd, cmd_len) != cmd_len) {                perror("recv cmd");                break;            }            cmd[cmd_len] = "\0";            printf("[执行] 命令: %s\n", cmd);            // 执行命令            fp = popen(cmd, "r");            if (fp == NULL) {                strcpy(result, "popen 失败");            } else {                int total = 0;                while (fgets(result + total, BUFFER_SIZE - total, fp) != NULL) {                    total = strlen(result);                    if (total >= BUFFER_SIZE - 1) break;                }                pclose(fp);            }            // 准备发送结果,格式:长度(4字节)+ 数据            int result_len = strlen(result);            int net_len = htonl(result_len);            send_all(client_fd, (char*)&net_len, 4);            send_all(client_fd, result, result_len);            memset(result, 0, BUFFER_SIZE);  // 清空缓冲区        }        close(client_fd);    }    close(server_fd);    return 0;}

3. 客户端实现步骤

客户端连接服务端,从标准输入读取命令,发送给服务端,并接收执行结果打印。

客户端完整代码 (client.c)

#include #include #include #include #include #include #include #define SERVER_IP "127.0.0.1"#define PORT 8888#define BUFFER_SIZE 4096int send_all(int sockfd, const char *buf, int len) {    int total = 0;    while (total < len) {        int n = send(sockfd, buf + total, len - total, 0);        if (n == -1) break;        total += n;    }    return total;}int recv_all(int sockfd, char *buf, int len) {    int total = 0;    while (total < len) {        int n = recv(sockfd, buf + total, len - total, 0);        if (n <= 0) break;        total += n;    }    return total;}int main() {    int sockfd;    struct sockaddr_in server_addr;    char cmd[BUFFER_SIZE];    char result[BUFFER_SIZE];    // 创建socket    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {        perror("socket");        exit(1);    }    // 设置服务器地址    memset(&server_addr, 0, sizeof(server_addr));    server_addr.sin_family = AF_INET;    server_addr.sin_port = htons(PORT);    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {        perror("inet_pton");        exit(1);    }    // 连接服务器    if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {        perror("connect");        exit(1);    }    printf("[客户端] 已连接到服务器 %s:%d\n", SERVER_IP, PORT);    while (1) {        printf("> ");        fflush(stdout);        if (fgets(cmd, BUFFER_SIZE, stdin) == NULL) break;        cmd[strcspn(cmd, "\n")] = "\0";  // 去掉换行符        if (strlen(cmd) == 0) continue;        if (strcmp(cmd, "exit") == 0) break;        // 发送命令长度(4字节,网络字节序)        int cmd_len = strlen(cmd);        int net_len = htonl(cmd_len);        send_all(sockfd, (char*)&net_len, 4);        send_all(sockfd, cmd, cmd_len);        // 接收结果长度        int result_len;        if (recv_all(sockfd, (char*)&result_len, 4) != 4) {            printf("[错误] 服务器断开\n");            break;        }        result_len = ntohl(result_len);        // 接收结果数据        if (result_len >= BUFFER_SIZE) {            printf("[警告] 结果过长,截断显示\n");            recv_all(sockfd, result, BUFFER_SIZE-1);            result[BUFFER_SIZE-1] = "\0";            // 跳过剩余数据            char tmp[1024];            int remain = result_len - (BUFFER_SIZE-1);            while (remain > 0) {                int n = recv(sockfd, tmp, sizeof(tmp), 0);                if (n <= 0) break;                remain -= n;            }        } else {            recv_all(sockfd, result, result_len);            result[result_len] = "\0";        }        printf("%s\n", result);    }    close(sockfd);    return 0;}

4. 编译与运行

打开两个终端,分别编译服务端和客户端:

gcc server.c -o servergcc client.c -o client

先运行服务端:./server,再运行客户端:./client。在客户端输入命令(如lspwd),即可看到远程执行结果。输入exit退出客户端。

5. 实操要点与优化建议

  • 粘包处理:本例使用“4字节长度头”解决,实际项目中可设计更复杂的协议(如HTTP格式)。
  • 命令执行安全popen直接执行用户输入存在注入风险,建议对命令进行白名单过滤或使用参数化方式(如execve系列)。
  • 并发处理:当前服务端只能串行处理客户端,可以使用fork()或多线程为每个客户端创建独立服务进程/线程。
  • 错误处理:代码中已包含基本错误处理,实际应用需更完善(如信号处理、资源回收)。
  • 缓冲区溢出:代码中使用固定缓冲区,命令输出过长会截断,可改用动态分配或分块发送。

6. 总结

通过本教程,你亲手实现了一个基于TCP Socket远程命令执行系统,深入理解了Linux网络编程的核心API和Socket编程实战技巧。你可以在此基础上扩展功能,如添加用户认证、加密传输、并发处理等,打造更强大的远程管理工具。希望本文能成为你网络编程之路的坚实起点!

本文关键词:Linux网络编程,TCP Socket,远程命令执行,Socket编程实战