当前位置: 首页 > news >正文

Day34 UDP套接字编程 可靠文件传输与实时双向聊天系统

day34 UDP套接字编程 可靠文件传输与实时双向聊天系统

UDP文件传输

实现客户端向服务器传输文件(如图片)的功能,确保传输后文件内容完全一致且可正常打开。传输过程采用简单的确认机制防止数据包丢失,传输完成后双方程序自动退出。核心知识点包括:UDP套接字创建、文件读写、数据包分块传输、传输结束标志处理。

客户端代码 (cli.c)

#include <arpa/inet.h>   // 提供IP地址转换函数
#include <fcntl.h>       // 提供文件控制操作
#include <netinet/in.h>  // 定义IPv4地址结构
#include <netinet/ip.h>  // 定义IP协议相关结构
#include <stdio.h>       // 标准输入输出函数
#include <stdlib.h>      // 标准库函数
#include <string.h>      // 字符串操作函数
#include <sys/socket.h>  // 套接字API
#include <sys/types.h>   // 数据类型定义
#include <time.h>        // 时间相关函数
#include <unistd.h>      // POSIX系统调用typedef struct sockaddr *(SA);  // 定义sockaddr指针别名,简化类型转换int main(int argc, char **argv)
{// 创建UDP套接字(AF_INET: IPv4, SOCK_DGRAM: 数据报套接字)int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sockfd){perror("socket");  // 套接字创建失败时打印错误return 1;}// 配置服务器地址结构struct sockaddr_in ser;ser.sin_family = AF_INET;         // 地址族:IPv4ser.sin_port = htons(50000);      // 端口号转网络字节序(50000)ser.sin_addr.s_addr = inet_addr("192.168.1.48");  // 服务器IP地址// 打开本地文件(只读模式)int fd = open("/home/linux/1.png", O_RDONLY);if (-1 == fd){perror("open error\n");return 1;}int num = 0;        // 累计已发送字节数char buf[1024] = {0};  // 数据缓冲区(1024字节块)while (1){bzero(buf, sizeof(buf));  // 清空缓冲区int ret = read(fd, buf, sizeof(buf));  // 从文件读取数据到缓冲区num += ret;  // 累计传输字节数printf("num:%d\n", num);  // 实时打印已发送字节数// 读取结束或出错时退出循环if (ret <= 0){break;}// 向服务器发送数据包sendto(sockfd, buf, ret, 0, (SA)&ser, sizeof(ser));// 接收服务器确认包(小阻塞:确保服务器处理完当前包)bzero(buf, sizeof(buf));recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);}// 发送传输结束标志strcpy(buf, "^_^");  // 结束标志字符串sendto(sockfd, buf, strlen(buf), 0, (SA)&ser, sizeof(ser));// 关闭资源close(sockfd);close(fd);return 0;
}

服务器代码 (ser.c)

#include <arpa/inet.h>   // 提供IP地址转换函数
#include <netinet/in.h>  // 定义IPv4地址结构
#include <netinet/ip.h>  // 定义IP协议相关结构
#include <stdio.h>       // 标准输入输出函数
#include <stdlib.h>      // 标准库函数
#include <string.h>      // 字符串操作函数
#include <sys/socket.h>  // 套接字API
#include <sys/types.h>   // 数据类型定义
#include <unistd.h>      // POSIX系统调用
#include <time.h>        // 时间相关函数
#include <fcntl.h>       // 提供文件控制操作typedef struct sockaddr * (SA);  // 定义sockaddr指针别名int main(int argc, char **argv)
{// 创建UDP套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sockfd){perror("socket");return 1;}// 配置服务器地址结构(用于绑定)struct sockaddr_in ser, cli;ser.sin_family = AF_INET;         // 地址族:IPv4ser.sin_port = htons(50000);      // 端口号转网络字节序ser.sin_addr.s_addr = inet_addr("192.168.1.48");  // 本机IP地址// 绑定套接字到指定地址和端口int ret = bind(sockfd, (SA) &ser, sizeof(ser));if (-1 == ret){perror("bind");return 1;}time_t tm;  // 时间变量(未实际使用,保留原代码)socklen_t len = sizeof(cli);  // 客户端地址结构长度// 创建新文件(写入/创建/截断模式,权限0666)int fd = open("2.png", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (-1 == fd){perror("open error\n");return 1;}int num = 0;  // 累计接收字节数while (1){char buf[1024] = {0};  // 数据缓冲区// 接收客户端数据包int ret = recvfrom(sockfd, buf, sizeof(buf), 0, (SA)&cli, &len);num += ret;  // 累计接收字节数printf("num:%d\n", num);  // 实时打印已接收字节数// 检测到结束标志时退出循环if (0 == strcmp(buf, "^_^")){break;}// 将数据写入文件write(fd, buf, ret);// 发送确认包(解除客户端阻塞)bzero(buf, sizeof(buf));strcpy(buf, "go on");  // 确认消息sendto(sockfd, buf, strlen(buf), 0, (SA)&cli, len);}// 关闭资源close(sockfd);close(fd);return 0;
}

理想运行结果

  1. 文件传输过程
    • 客户端输出:num:1024num:2048 → … → num:[文件总大小](字节数递增)
    • 服务器输出:num:1024num:2048 → … → num:[文件总大小](与客户端一致)
  2. 传输验证
    • 传输完成后,2.png 与原始 1.png 大小完全相同(ls -l 对比)
    • 图片文件可正常打开(如 eog 2.png 无损坏)
  3. 退出机制
    • 客户端发送 ^_^ 后自动退出
    • 服务器收到 ^_^ 后自动退出
  4. 关键特性
    • 每个数据包传输后通过 recvfrom/sendto 实现简单确认,避免发送过快导致丢包
    • 结束标志 ^_^ 确保双方同步退出

UDP聊天

实现双向实时聊天功能,支持连续消息收发。输入 #quit 时,客户端和服务器同时退出。核心知识点包括:多进程并发处理(fork)、消息循环控制、退出信号同步。

服务器代码 (ser.c)

#include <arpa/inet.h>   // IP地址转换
#include <fcntl.h>       // 文件控制
#include <netinet/in.h>  // IPv4地址结构
#include <netinet/ip.h>  // IP协议结构
#include <signal.h>      // 信号处理(用于进程终止)
#include <stdio.h>       // 标准I/O
#include <stdlib.h>      // 标准库
#include <string.h>      // 字符串操作
#include <sys/socket.h>  // 套接字API
#include <sys/types.h>   // 数据类型
#include <time.h>        // 时间函数
#include <unistd.h>      // POSIX系统调用typedef struct sockaddr *(SA);  // sockaddr指针别名int main(int argc, char **argv)
{// 创建UDP套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sockfd){perror("socket");return 1;}// 配置服务器地址结构struct sockaddr_in ser, cli;ser.sin_family = AF_INET;ser.sin_port = htons(50000);      // 监听端口50000ser.sin_addr.s_addr = inet_addr("192.168.1.48");  // 本机IP// 绑定套接字int ret = bind(sockfd, (SA)&ser, sizeof(ser));if (-1 == ret){perror("bind");return 1;}socklen_t len = sizeof(cli);  // 客户端地址长度char buf[512] = {0};          // 消息缓冲区// 等待客户端初始连接("start"消息)recvfrom(sockfd, buf, sizeof(buf), 0, (SA)&cli, &len);// 创建子进程处理并发pid_t pid = fork();if (pid > 0)  // 父进程:接收客户端消息{while (1){bzero(buf, sizeof(buf));  // 清空缓冲区// 接收客户端消息recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);// 检测退出命令if (0 == strcmp(buf, "#quit\n")){kill(pid, 9);  // 终止子进程exit(1);       // 父进程退出}printf("cli:%s", buf);  // 显示客户端消息}}else if (0 == pid)  // 子进程:发送服务器消息{while (1){printf("to cli:");  // 提示输入char buf[512] = {0};fgets(buf, sizeof(buf), stdin);  // 读取用户输入// 发送消息到客户端sendto(sockfd, buf, strlen(buf), 0, (SA)&cli, len);// 检测退出命令if (0 == strcmp(buf, "#quit\n")){kill(getppid(), 9);  // 终止父进程exit(1);             // 子进程退出}}}else  // fork失败{perror("fork");return 1;}close(sockfd);  // 关闭套接字(实际不会执行到此处)return 0;
}

客户端代码 (cli.c)

#include <arpa/inet.h>   // IP地址转换
#include <netinet/in.h>  // IPv4地址结构
#include <netinet/ip.h>  // IP协议结构
#include <stdio.h>       // 标准I/O
#include <stdlib.h>      // 标准库
#include <string.h>      // 字符串操作
#include <sys/socket.h>  // 套接字API
#include <sys/types.h>   // 数据类型
#include <unistd.h>      // POSIX系统调用
#include <time.h>        // 时间函数
#include <fcntl.h>       // 文件控制
#include <signal.h>      // 信号处理typedef struct sockaddr * (SA);  // sockaddr指针别名int main(int argc, char **argv)
{// 创建UDP套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sockfd){perror("socket");return 1;}// 配置服务器地址结构struct sockaddr_in ser;ser.sin_family = AF_INET;ser.sin_port = htons(50000);      // 目标端口50000ser.sin_addr.s_addr = inet_addr("192.168.1.48");  // 服务器IPsocklen_t len = sizeof(ser);  // 地址长度char buf[512] = {0};strcpy(buf, "start");  // 初始连接消息sendto(sockfd, buf, strlen(buf), 0, (SA)&ser, len);  // 通知服务器// 创建子进程处理并发pid_t pid = fork();if (pid > 0)  // 父进程:接收服务器消息{while (1){bzero(buf, sizeof(buf));// 接收服务器消息recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);// 检测退出命令if (0 == strcmp(buf, "#quit\n")){kill(pid, 9);  // 终止子进程exit(1);       // 父进程退出}printf("ser:%s", buf);  // 显示服务器消息}}else if (0 == pid)  // 子进程:发送客户端消息{while (1){printf("to ser:");  // 提示输入char buf[512] = {0};fgets(buf, sizeof(buf), stdin);  // 读取用户输入// 发送消息到服务器sendto(sockfd, buf, strlen(buf), 0, (SA)&ser, len);// 检测退出命令if (0 == strcmp(buf, "#quit\n")){kill(getppid(), 9);  // 终止父进程exit(1);             // 子进程退出}}}else  // fork失败{perror("fork");return 1;}close(sockfd);  // 关闭套接字(实际不会执行到此处)return 0;
}

理想运行结果

  1. 初始化连接

    • 客户端发送 start 消息,服务器开始监听
    • 双方进入消息循环
  2. 聊天过程

    • 服务器终端:
      to cli:Hello  // 服务器输入
      cli:How are you?  // 显示客户端消息
      to cli:#quit  // 输入退出命令
      
    • 客户端终端:
      ser:Hello  // 显示服务器消息
      to ser:How are you?  // 客户端输入
      ser:#quit  // 收到退出命令
      
  3. 退出机制

    • 任意一方输入 #quit 后:
      • 发送方立即终止对方进程(kill
      • 本地进程退出(exit(1)
    • 双方终端同时关闭,无残留进程
  4. 关键特性

    • 通过 fork 实现双工通信:父进程处理接收,子进程处理发送
    • 退出命令 #quit 触发双向终止(避免单方退出导致僵局)
    • 消息实时显示(printf 前无缓冲,即时刷新)
http://www.dtcms.com/a/366032.html

相关文章:

  • 信号调制与解调 matlab仿真
  • 异常处理机制与debug
  • 复写零(双指针)
  • 单片机day2
  • 配置时钟分频与倍频
  • 解构复杂财务逆向业务:如何优雅地生成与管理负数单?
  • Python基础(⑥属性装饰器)
  • 你只需输入一句话,MoneyPrinterTurbo直接给你输出一个视频
  • 普通人如何用 AI 提效?5 个低门槛工具 + 3 类场景案例,让 AI 成为日常助手
  • phpstorm 操作git 另外的操作在 我的收藏
  • c#编写的应用程序调用不在同一文件夹下的DLL
  • Java继承
  • c++ zint二维码、条形码开发库
  • c++多线程(1)------创建和管理线程td::thread
  • Python数据分析与处理(二):将数据写回.mat文件的不同方法【超详细】
  • AI+法律:用ERNIE-Bot解析合同条款,识别风险点
  • 进程管理和IPC
  • 常用假设检验方法及 Python 实现
  • 多层环境室内定位系统综述总结
  • 【JavaEE】(22) Spring 事务
  • 苏哒智能会议一体机:让会议管理进入智能时代
  • Java与Vue构建的企业设备资产管理系统,集成后台管理与移动端操作,覆盖设备全生命周期,支持采购、维护、报废等流程,提供完整源码,助力企业高效管控资产
  • 刻意练习实践说明使用手册
  • nginx配置讲解
  • GitLens:VS Code下高效解决代码追溯的Git管理实用插件
  • Spring Boot + Nacos 配置中心示例工程
  • ego(2)---初始轨迹生成后的关键点采样
  • 一文读懂线性回归的灵魂:成本函数 J(w,b) 全解析
  • K8s基于节点软亲和的高 CPU Pod 扩容与优先调度方案
  • 华为OD最新机试真题-二进制差异数-OD统一考试(C卷)