当前位置:首页 > 技术 > 正文内容

基于TCP的机械臂角度控制及UDP的TFTP文件传输实现

访客 技术 2026年6月17日 1

TCP客户端控制机械臂角度

以下代码实现一个TCP客户端程序,通过键盘按键控制红色和蓝色机械臂的角度。按键规则为:w/W增大红色臂角度(上限90°),s/S减小红色臂角度(下限-90°),d/D增大蓝色臂角度(上限180°),a/A减小蓝色臂角度(下限0°)。程序首先建立与服务器的连接,然后发送初始角度值,循环读取键盘输入并调整角度后发送给服务器。

#include <myhead.h>
#define SERVER_IP "192.168.126.12"
#define SERVER_PORT 8888

#define CLIENT_IP "192.168.244.140"
#define CLIENT_PORT 9999

int main(int argc, const char *argv[]) {
    // 创建套接字
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd == -1) {
        perror("socket创建失败");
        return -1;
    }
    printf("套接字fd = %d\n", sock_fd);

    // 绑定客户端地址
    struct sockaddr_in client_addr;
    client_addr.sin_family = AF_INET;
    client_addr.sin_port = htons(CLIENT_PORT);
    client_addr.sin_addr.s_addr = inet_addr(CLIENT_IP);
    if (bind(sock_fd, (struct sockaddr*)&client_addr, sizeof(client_addr)) == -1) {
        perror("绑定失败");
        return -1;
    }
    printf("绑定成功\n");

    // 连接服务器
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
    if (connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("连接失败");
        return -1;
    }
    printf("连接成功\n");

    // 初始化角度数据包: [0xff, 0x02, 0x00, angle, 0xff]
    char red_arm[5] = {0xff, 0x02, 0x00, 0x00, 0xff};
    unsigned char blue_arm[5] = {0xff, 0x02, 0x01, 0x00, 0xff};

    // 发送初始角度(0°)
    send(sock_fd, red_arm, sizeof(red_arm), 0);
    sleep(1); // 防止粘包
    send(sock_fd, blue_arm, sizeof(blue_arm), 0);

    char key_input = 0;
    while (1) {
        system("stty -icanon");
        key_input = getchar();

        switch (key_input) {
            case 'w':
            case 'W':
                red_arm[3] += 1;
                if (red_arm[3] > 90) red_arm[3] = 90;
                send(sock_fd, red_arm, sizeof(red_arm), 0);
                break;
            case 's':
            case 'S':
                red_arm[3] -= 1;
                if (red_arm[3] < -90) red_arm[3] = -90;
                send(sock_fd, red_arm, sizeof(red_arm), 0);
                break;
            case 'd':
            case 'D':
                blue_arm[3] += 1;
                if (blue_arm[3] > 180) blue_arm[3] = 180;
                send(sock_fd, blue_arm, sizeof(blue_arm), 0);
                break;
            case 'a':
            case 'A':
                blue_arm[3] -= 1;
                if (blue_arm[3] < 0) blue_arm[3] = 0;
                send(sock_fd, blue_arm, sizeof(blue_arm), 0);
                break;
        }
    }

    close(sock_fd);
    return 0;
}

基于UDP的TFTP文件传输

此程序实现了经典的TFTP文件传输协议(基于UDP),提供文件下载和上传功能。用户通过简单菜单选择操作,输入文件名后程序自动与指定TFTP服务器(端口69)通信。代码包含错误处理、数据包编号校验以及ACK应答机制,确保传输可靠性。

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h>

#define LOG_ERROR(msg) do {\
    perror(msg);\
    printf("出错位置: %d行 %s %s\n", __LINE__, __func__, __FILE__);\
} while(0)

#define TFTP_PORT 69
#define PKT_SIZE 516

int handle_download(int udp_fd, struct sockaddr_in srv_addr);
int handle_upload(int udp_fd, struct sockaddr_in srv_addr);

int main(int argc, const char *argv[]) {
    if (argc < 2) {
        printf("用法: %s <服务器IP>\n", argv[0]);
        return -1;
    }

    int udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (udp_fd < 0) {
        perror("socket创建失败");
        return -1;
    }

    struct sockaddr_in server_info;
    server_info.sin_family = AF_INET;
    server_info.sin_port = htons(TFTP_PORT);
    server_info.sin_addr.s_addr = inet_addr(argv[1]);

    char option;
    while (1) {
        system("clear");
        printf("===================\n");
        printf("    1. 下载文件\n");
        printf("    2. 上传文件\n");
        printf("    3. 退出\n");
        printf("===================\n");

        scanf("%c", &option);
        while(getchar() != '\n');

        switch (option) {
            case '1':
                handle_download(udp_fd, server_info);
                break;
            case '2':
                handle_upload(udp_fd, server_info);
                break;
            case '3':
                goto exit_program;
            default:
                printf("无效选项\n");
        }
        printf("按回车继续...");
        while(getchar() != '\n');
    }

exit_program:
    close(udp_fd);
    return 0;
}

int handle_upload(int udp_fd, struct sockaddr_in srv_addr) {
    char fname[20] = {0};
    printf("输入上传文件名: ");
    fgets(fname, 20, stdin);
    fname[strlen(fname)-1] = '\0';

    int file_fd = open(fname, O_RDONLY);
    if (file_fd < 0) {
        if (errno == ENOENT) {
            printf("文件不存在\n");
            return -2;
        }
        LOG_ERROR("打开文件失败");
        return -1;
    }

    char packet[PKT_SIZE] = {0};
    int pkt_len = sprintf(packet, "%c%c%s%c%s%c", 0, 2, fname, 0, "octet", 0);
    if (sendto(udp_fd, packet, pkt_len, 0, (struct sockaddr*)&srv_addr, sizeof(srv_addr)) < 0) {
        LOG_ERROR("发送WRQ失败");
        return -1;
    }

    unsigned short block_num = 0;
    int recv_len;
    socklen_t addr_len = sizeof(srv_addr);
    while (1) {
        bzero(packet, PKT_SIZE);
        recv_len = recvfrom(udp_fd, packet, PKT_SIZE, 0, (struct sockaddr*)&srv_addr, &addr_len);
        if (recv_len < 0) {
            LOG_ERROR("接收失败");
            return -1;
        }

        if (packet[1] == 4) { // ACK包
            if (block_num == ntohs(*(unsigned short*)(packet+2))) {
                packet[1] = 3; // 改为DATA包
                block_num++;
                *(unsigned short*)(packet+2) = htons(block_num);

                int data_size = read(file_fd, packet+4, PKT_SIZE-4);
                if (data_size < 0) {
                    LOG_ERROR("读文件失败");
                    return -1;
                } else if (data_size == 0) {
                    printf("上传完成\n");
                    break;
                }

                if (sendto(udp_fd, packet, data_size+4, 0, (struct sockaddr*)&srv_addr, sizeof(srv_addr)) < 0) {
                    LOG_ERROR("发送DATA失败");
                    return -1;
                }
            } else {
                printf("网络异常,上传失败\n");
                break;
            }
        } else if (packet[1] == 5) {
            printf("错误: %s\n", packet+4);
            break;
        }
    }
    close(file_fd);
    return 0;
}

int handle_download(int udp_fd, struct sockaddr_in srv_addr) {
    char fname[20] = {0};
    printf("输入下载文件名: ");
    fgets(fname, 20, stdin);
    fname[strlen(fname)-1] = '\0';

    char packet[PKT_SIZE] = {0};
    int pkt_len = sprintf(packet, "%c%c%s%c%s%c", 0, 1, fname, 0, "octet", 0);
    if (sendto(udp_fd, packet, pkt_len, 0, (struct sockaddr*)&srv_addr, sizeof(srv_addr)) < 0) {
        LOG_ERROR("发送RRQ失败");
        return -1;
    }

    int file_created = 0;
    int file_fd;
    unsigned short expected_block = 1;
    socklen_t addr_len = sizeof(srv_addr);

    while (1) {
        bzero(packet, PKT_SIZE);
        int recv_len = recvfrom(udp_fd, packet, PKT_SIZE, 0, (struct sockaddr*)&srv_addr, &addr_len);
        if (recv_len < 0) {
            LOG_ERROR("接收失败");
            return -1;
        }

        if (packet[1] == 3) { // DATA包
            if (!file_created) {
                file_fd = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0664);
                if (file_fd < 0) {
                    LOG_ERROR("创建文件失败");
                    return -1;
                }
                file_created = 1;
            }

            if (htons(expected_block) == *(unsigned short*)(packet+2)) {
                if (write(file_fd, packet+4, recv_len-4) < 0) {
                    LOG_ERROR("写文件失败");
                    break;
                }

                packet[1] = 4; // 改为ACK包
                sendto(udp_fd, packet, 4, 0, (struct sockaddr*)&srv_addr, sizeof(srv_addr));

                if (recv_len < PKT_SIZE) {
                    printf("下载完成\n");
                    break;
                }
                expected_block++;
            }
        } else if (packet[1] == 5) {
            printf("错误: %s\n", packet+4);
            break;
        }
    }
    if (file_created) close(file_fd);
    return 0;
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。