网络编程项目篇

news2024/9/22 15:43:13

一、tftp客户端下载

1)tftp协议概述

简单文件传输协议,适用于在网络上进行文件传输的一套标准协议,使用UDP传输

特点:

是应用层协议

基于UDP协议实现

数据传输模式

octet:二进制模式(常用)

mail:已经不再支持

2)tftp下载模型

TFTP通信过程总结

  1. 服务器在69号端口等待客户端的请求
  2. 服务器若批准此请求,则使用 临时端口 与客户端进行通信。
  3. 每个数据包的编号都有变化(从1开始)
  4. 每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
  5. 数据长度以512Byte传输的,小于512Byte的数据意味着数据传输结束。

3)tftp协议分析

差错码:

0 未定义,差错错误信息

1 File not found.

2 Access violation.

3 Disk full or allocation exceeded.

4 illegal TFTP operation.

5 Unknown transfer ID.

6 File already exists.

7 No such user.

8 Unsupported option(s) requested.

效果展示

源码

#include<myhead.h>
#define SER_PORT 69 			//服务器端口号
#define SER_IP "192.168.10.101" 	//服务器IP地址


int main(int argc, const char *argv[])
{
	//1、创建用于通信的客户端	套接字文件描述符
	int cfd = socket(AF_INET,SOCK_DGRAM,0);
	if(cfd == -1)
	{
		perror("socket error");
		return -1;
	}
	printf("socket success\n");

	//2、向服务器发送下载请求
	//2.1 填充服务器地址
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SER_PORT);
	inet_pton(AF_INET,SER_IP,&sin.sin_addr);

	//终端输入操作的文件名
	char destname[50] = "";
	printf("请输入您需要操作的文件名:");
	fgets(destname,sizeof(destname),stdin);
	//将回车替换成0
	destname[strlen(destname)-1] = '\0';

	//2.2 创建请求数据包
    char buf[516] = "";
    short *op = (short *)buf;
    *op = htons(1);
    char *filename = buf + 2;
	
    strcpy(filename, destname);
    char *zero1 = filename + strlen(filename);
    *zero1 = 0;
    char *mode = zero1 + 1;
    strcpy(mode, "octet");
    char *zero2 = mode + strlen(mode);
    *zero2 = 0;
    size_t size = 2 + strlen(filename) + strlen(mode) + 2;
	printf("%s\n",buf);
	//2.3发送请求数据包
	sendto(cfd,buf,size,0,(struct sockaddr*)&sin,sizeof(sin));

	//出错判断
	uint16_t err_op = htons(5);
	
	
	//打开下载位置的文件描述符
	int newfd = open("5.png",O_APPEND|O_CREAT|O_WRONLY|O_TRUNC,0664);

	while(1)
	{
		//2.4 接收数据
		socklen_t addrlen = sizeof(sin);
   		bzero(buf, sizeof(buf));
		int res = 0;
		//2.5 解析数据
		res = recvfrom(cfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,&addrlen);

		//判断出错
        if (res < 4)
        {
            printf("接收到的数据包太小,无法解析");
            break;
        }

		//接收操作码
		uint16_t opcode = ntohs(*(uint16_t *)buf);
		if(opcode == 5)
		{
			//错误码
			char err_code[2] = "";
			strncpy(err_code, buf + 2, 2);
			printf("ERROR[%d:%s]\n", ntohs(*(uint16_t*)err_code), buf + 4);
			break;
		}
		else if(opcode == 3)
		{
			if(res < 516)
			{	
				write(newfd,buf+4,res-4);
				printf("下载完成\n");
				break;
			}

			//将接收到的数据写入当前目录下的5.png文件中
			write(newfd,buf+4,res-4);
			//向服务器回复确认数据包ACK
			*op = htons(4);
			sendto(cfd,buf,4,0,(struct sockaddr*)&sin,sizeof(sin));
		}
		else
		{
			printf("未知操作码: %d", opcode);
			break;
		}

	}

	//4、关闭文件描述符
	close(newfd);
	close(cfd);

	return 0;
}

二、基于UDP的网络聊天室

项目需求:

  1. 如果有用户登录,其他用户可以收到这个人的登录信息
  2. 如果有人发送信息,其他用户可以收到这个人的群聊信息
  3. 如果有人下线,其他用户可以收到这个人的下线信息
  4. 服务器可以发送系统信息

效果展示

源码

ChatRoom.h

#ifndef CHATROOM_H
#define CHATROOM_H

#include <myhead.h>

#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024

//客户端发送消息的三种模式
#define LOGIN 	1
#define CHAT 	2
#define QUIT 	3

//服务器端存储用户信息结构体
typedef struct node
{
    struct sockaddr_in cin;   //用户地址结构体
    struct node* next;       //指针域
} Client;

//客户端用户发送信息结构体
typedef struct
{
    int type;
    char usrName[50];
    char msgText[1024];
}msgTyp;

//信号处理
typedef void (*sighandler_t)(int);

//函数声明
int ser_recv(int sfd, Client* head);
int ser_login(int sfd, Client* head, msgTyp rcv_msg, struct sockaddr_in cin);
int ser_quit(int sfd, Client* head, msgTyp rcv_msg, struct sockaddr_in cin);
int ser_chat(int sfd, Client* head, msgTyp rcv_msg, struct sockaddr_in cin);
int ser_system(int sfd, struct sockaddr_in sin);


#endif

ChatSer.c

#include"ChatRoom.h"

int main(int argc, const char *argv[])
{
    //终端输入服务器IP和端口号
    //判断终端输入
    if(argc != 3)
    {
        printf("输入错误\n");
        printf("usage:./a.out IP PORT");
        return -1;
    }
	//1、创建用于通信的服务器套接字文件描述符
	int sfd = socket(AF_INET,SOCK_DGRAM,0);
	if(sfd == -1)
	{
		perror("socket error");
		return -1;
	}
	printf("sockeyt success\n");

	//端口号快速重用
	int reuse  =-1;
	if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)) == -1)
	{
		perror("setsockopt error");
		return -1;
	}
	printf("setsockopt success\n");

	//2、为套接字绑定IP地址和端口号
	//2.1、填充地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET; 					//通信域
	sin.sin_port = htons(atoi(argv[2])); 			//端口号
	sin.sin_addr.s_addr = inet_addr(argv[1]); 	//IP地址
	
	//2.2、绑定
	if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin)) == -1)
	{
		perror("bind error");
		return -1;
	}
	printf("bind success\n");

	//创建进程
	int pid = 0;
	pid = fork();
	if(pid > 0)
	{
		//父进程, 接收消息
		Client*  head = (Client*)malloc(sizeof(Client));
		head->next = NULL;
		ser_recv(sfd , head);
 
	}
	else if(pid==0)
	{
		//子进程,发送系统消息
		ser_system(sfd, sin);
	}

	//4、关闭文件描述符
	close(sfd);

	return 0;
}

//系统提示消息
int ser_system(int sfd, struct sockaddr_in sin)
{
    msgTyp sys_msg = {htonl(CHAT), "**system**"};
 
    while(1)
    {
        bzero(sys_msg.msgText, sizeof(sys_msg.msgText));
        fgets(sys_msg.msgText, sizeof(sys_msg.msgText), stdin);
        sys_msg.msgText[strlen(sys_msg.msgText)-1] = 0;
 
        //将当前进程当做客户端,父进程当做服务器,发送信息;
        if(sendto(sfd, &sys_msg, sizeof(sys_msg), 0, (void*)&sin, sizeof(sin)) < 0)
        {
            perror("sendto");
            return -1;
        }
    }
    printf("系统消息发送成功");
    return 0;
}
 
//登录上线函数
int ser_login(int sfd, Client* head, msgTyp rcv_msg, struct sockaddr_in cin)
{
    //打印登录成功
    printf("%s [%s:%d]登录成功\n", rcv_msg.usrName, (char*)inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
 
    sprintf(rcv_msg.msgText, "-----%s登录成功-----", rcv_msg.usrName);
 
    while(head->next != NULL)
    {
        head = head->next;
        if(sendto(sfd, &rcv_msg, sizeof(rcv_msg), 0, (void*)&(head->cin), sizeof(head->cin)) < 0)
        {
            perror("sendto");
            return -1;
        }
    }
 
    //将登录成功的客户端信息添加到链表中;
    Client *temp = (Client*)malloc(sizeof(Client));
    temp->cin = cin;
    temp->next = NULL;
    
    head->next = temp;
    return 0;
}
 
//接收函数
int ser_recv(int sfd, Client* head)
{
    msgTyp rcv_msg;
    int recv_len = 0;
    struct sockaddr_in cin;
    socklen_t clen = sizeof(cin);
 
    while(1)
    {
        //接收消息
        recv_len = recvfrom(sfd, &rcv_msg, sizeof(rcv_msg), 0, (void*)&cin, &clen);
        if(recv_len < 0)
        {
            perror("recvfrom");
            return -1;
        }
        //提取出协议
        //将网络字节序转换为主机字节序
        int type = ntohl(rcv_msg.type);
 
        switch(type)
        {
        case LOGIN:
            ser_login(sfd, head, rcv_msg, cin);
            break;
        case CHAT:
            ser_chat(sfd, head, rcv_msg, cin);
            break;
        case QUIT:
            ser_quit(sfd, head, rcv_msg, cin);
            break;
        }
    }
}
 
//聊天发送函数
int ser_chat(int sfd, Client* head, msgTyp rcv_msg, struct sockaddr_in cin)
{
    printf("%s [%s:%d]chat成功\n", rcv_msg.usrName, (char*)inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
    //重新拼接群聊消息: 名字+消息
    char buf[258] = "";
    sprintf(buf, "%s:%s", rcv_msg.usrName, rcv_msg.msgText);
    strcpy(rcv_msg.msgText, buf);
 
    //循环发送,除了自己以外的ip地址
    while(head->next != NULL)
    {
        head = head->next;
        if(memcmp(&cin, &head->cin, sizeof(cin)) != 0)
        {    
            if(sendto(sfd, &rcv_msg, sizeof(rcv_msg), 0, (void*)&(head->cin), sizeof(head->cin)) < 0)
            {
                perror("sendto");
                return -1;
            }
        }
    }
    return 0;    
}
 
//退出函数
int ser_quit(int sfd, Client* head, msgTyp rcv_msg, struct sockaddr_in cin)
{
    printf("%s [%s:%d]已下线\n", rcv_msg.usrName, (char*)inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
    sprintf(rcv_msg.msgText, "-----%s 已下线-----\n", rcv_msg.usrName);
    //循环遍历链表,发送"xxx一下线"的信息
    //发送给当前客户端外的其余客户端
    while(head->next != NULL)
    {
        if(memcmp(&cin, &head->next->cin, sizeof(cin)) == 0)
        {
            //从链表中删除该客户端信息
            //free
            Client* temp = head->next;
            head->next = temp->next;
            free(temp);
        }
        else
        {
            head = head->next;
            if(sendto(sfd, &rcv_msg, sizeof(rcv_msg), 0, (void*)&head->cin, sizeof(head->cin)) < 0)
            {
                perror("sendto");
                return -1;
            }
        }
    }
    return 0;
}




ChatCli.c

#include"ChatRoom.h"
//信号处理函数
void handler(int sig)
{
	//回收子进程资源并退出
	while(waitpid(-1,NULL, WNOHANG)>0);
	exit(0);
}

int main(int argc, const char *argv[])
{
    //终端输入服务器IP和端口号
    //判断终端输入
    if(argc != 3)
    {
        printf("输入错误\n");
        printf("usage:./a.out IP PORT");
        return -1;
    }
    
	//注册信号处理函数,让子进程退出后,父进程回收子进程资源并退出
	sighandler_t h = signal(SIGCHLD, handler);
	if(h == SIG_ERR)
	{
		perror("signal error");
		return -1;
	}

	//1、创建用于通信的客户端	套接字文件描述符
	int cfd = socket(AF_INET,SOCK_DGRAM,0);
	if(cfd == -1)
	{
		perror("socket error");
		return -1;
	}
	printf("sockeyt success\n");

	//端口号快速重用
	int reuse  =-1;
	if(setsockopt(cfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)) == -1)
	{
		perror("setsockopt error");
		return -1;
	}
	printf("setsockopt success\n");

	//2、为套接字绑定IP地址和端口号
	//2.1、填充地址信息结构体

	//3、数据收发
	char buf[128] = "";
	//填充服务器地址信息
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(atoi(argv[2]));
	sin.sin_addr.s_addr = inet_addr(argv[1]);
   
   //用户登录
   msgTyp msg;
   msg.type = htonl(LOGIN);

   printf("请输入姓名:");
   fgets(msg.usrName,sizeof(msg.usrName),stdin);
   msg.usrName[strlen(msg.usrName) - 1] = '\0';

   //将登录信息发送给服务器
   if(sendto(cfd, &msg, sizeof(msg), 0, (struct sockaddr*)&sin, sizeof(sin)) == -1)
   {
       perror("发送登录信息失败");
       return -1;
   }
   printf("登录信息发送成功\n");

	//父进程用于接收服务器消息,子进程用于向服务器发送消息
	pid_t pid = fork();
	if (pid < 0) 
    {
		perror("创建子进程失败");
		return -1;
	} 

    else if (pid == 0) 
    {
		// 子进程:向服务器发送消息
		while (1) 
        {
            //终端输入聊天信息
			bzero(msg.msgText, sizeof(msg.msgText));
			fgets(msg.msgText, sizeof(msg.msgText), stdin);
			msg.msgText[strlen(msg.msgText) - 1] = '\0';
            if (strcmp(msg.msgText, "quit") == 0)
            {    
			    msg.type = htonl(QUIT);
			}
            else
            {
                msg.type = htonl(CHAT);
            }
			//发送给服务器
			if (sendto(cfd, &msg, sizeof(msg), 0, (struct sockaddr*)&sin, sizeof(sin)) == -1)
            {
				perror("发送消息失败");
				exit(-1);
			}

            if(msg.type == htonl(QUIT))
		    {
			    exit(0); 		//退出子进程;
		    }
		}
	} 

    else
    {
		// 父进程:接收服务器消息
        msgTyp recvMsg;
		while (1) 
        {
			if (recvfrom(cfd, &recvMsg, sizeof(recvMsg), 0, NULL,NULL) == -1) 
            {
				perror("接收消息失败");
				break;
			}
   
			printf("%s\n",recvMsg.msgText);
		}
	}


	//4、关闭套接字
	close(cfd);

	return 0;
}

附录

myhead.h

#include<stdio.h>
#include<errno.h>       //错误码的头文件
#include <sys/types.h>
#include <sys/stat.h>
#include<dirent.h> 		//文件夹操作头文件
#include <fcntl.h>      //open的头文件
#include<unistd.h>     //close\sleep的头文件
#include<string.h>     //str系列函数头文件
#include<math.h>          //数学头文件
#include<stdlib.h>     //标准库文件
#include<time.h>       //时间头文件
#include <dirent.h>   //关于目录操作的头文件
#include <sys/wait.h>   //关于资源回收的头文件
#include <pthread.h>    //线程相关函数头文件
#include <semaphore.h>   //无名信号量的头文件
#include <signal.h>      //信号绑定函数对应的头文件
#include <sys/ipc.h>       //进程间通信的头文件
#include <sys/msg.h>        //消息队列的头文件
#include <sys/shm.h>        //共享内存的头文件
#include <sys/user.h>        //获取用户信息的头文件
#include <sys/sem.h>        //信号灯集的头文件

#include <netinet/in.h> 	//点分十进制地址转换成网络字节序
#include <sys/socket.h>      //套接字头文件
#include <arpa/inet.h>    //字节序转换函数所在头文件
#include <linux/input.h>  //读取键盘输入
#include <sys/un.h>  //域套接字
#include <sys/select.h>  //IO多路复用select函数
#include <poll.h>  //IO多路复用poll函数
#include<sqlite3.h> //sqlite数据库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2051026.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【SpringBoot】SpringBoot中分页插件(PageHelper)的使用

目录 1.分页概念 2.原生写法 3.PageHelper插件分页查询 3.1 介绍 3.2 使用 3.3 Page对象和PageInf对象 1.分页概念 用户查询的数据不可能一次性全部展示给用户&#xff08;如果用户有一万条数据呢&#xff09;&#xff0c;而是分页展示给用户&#xff0c;这就是分页查询。…

Hospital Information System (HIS)

Hospital Information System &#xff08;HIS&#xff09; 医院门诊就诊流程

快速体验Ollama安装部署并支持AMD GPU推理加速

序言 Ollama 是一个专注于本地运行大型语言模型&#xff08;LLM&#xff09;的框架&#xff0c;它使得用户能够在自己的计算机上轻松地部署和使用大型语言模型&#xff0c;而无需依赖昂贵的GPU资源。Ollama 提供了一系列的工具和服务&#xff0c;旨在简化大型语言模型的安装、…

深入理解JVM运行时数据区(内存布局 )5大部分 | 异常讨论

前言&#xff1a; JVM运行时数据区&#xff08;内存布局&#xff09;是Java程序执行时用于存储各种数据的内存区域。这些区域在JVM启动时被创建&#xff0c;并在JVM关闭时销毁。它们的布局和管理方式对Java程序的性能和稳定性有着重要影响。 目录 一、由以下5大部分组成 1.…

【html+css 绚丽Loading】 - 000004 玄天旋轮

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽Loading&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495…

STM32 编码器模式详解

编码器模式 stm32的定时器带的也有编码器模式。 所用的编码器是有ABZ三相&#xff0c;其中ab相是用来计数&#xff0c;z相输出零点信号。 AB相根据旋转的方向不同&#xff0c;输出的波形如下图所示&#xff1a; 从图上可以看出来&#xff0c;cw方向A相会超前B相90度左右&#…

egret 拖尾的实现 MotionStreak

背景&#xff1a;egret项目中需要用到拖尾效果&#xff0c;引擎原生没有提供&#xff0c;参考cocos2dx 的 MotionStreak实现拖尾效果。 原理 拖尾的原理很简单&#xff0c;定时记录节点的位置&#xff0c;根据运行的轨迹和指定的拖尾宽度生成拖尾网格&#xff0c;然后将纹理绘…

VS2019开发跨平台(Linux)程序时,怎么配置第三方库的路径

一、问题描述&#xff1a; 使用跨平台编译时&#xff0c;VS2019总是提示链接openssl库有问题&#xff1b; 二、错误时的配置&#xff1a; 1、前提 openssl在Linux系统默认下是1.0.0版本&#xff0c;而自己准备好的是1.1.1版本&#xff0c;并且路径完全不在一个地方&#xf…

【Linux-进程】系统初识:冯诺依曼体系结构

系列文章&#xff1a;《Linux入门》 目录 冯诺依曼体系结构 1&#xff09;硬件上 &#x1f337;1.什么是冯诺依曼体系结构&#xff1f; &#x1f337;2.冯诺依曼结构的五个主要组成部分 1.运算器 2.控制器 3.存储器 4.输入输出 设备 ⁉️3.为什么还需要内存呢&#xf…

c++数据结构算法复习基础-- 4 -- 线性表-单向循环链表-常用操作接口-复杂度分析

1、单向循环链表一 1&#xff09;特点 每一个节点除了数据域&#xff0c;还有一个next指针域指向下一个节点(存储了下一个节点的地址) 末尾节点的指针域指向了头节点 析构函数思路图 2&#xff09;代码实现 //定义结点 //单向循环链表 class CircleLink { public://构造函数…

使用python基于fastapi发布接口(一)

FastAPI官网地址 FastAPI基于Python 3.6+和Starlette框架,天生就带着高性能和异步的基因。 FastAPI的文档生成功能简直是开发者的福音! 你不再需要手动编写API文档,FastAPI能自动帮你搞定。 FastAPI还超级灵活,支持各种数据库和认证方式,无论是SQLite、PostgreSQL还是M…

【xilinx】TPM可信平台模块与 Zynq UltraScale+ PS SPI 接口

本博客&#xff08;Venu Inaganti&#xff09;介绍了可信平台模块 (TPM) 与 Zynq UltraScale PS SPI 控制器的连接。 目前唯一具有 TPM 的评估板是 KR260/KV260 SOM&#xff0c;因此为了帮助正在试验 Zynq UltraScale 设备的用户&#xff0c;本文介绍了如何通过 PMOD 连接器与…

【MongoDB】Java连接MongoDB

连接URI 连接 URI提供驱动程序用于连接到 MongoDB 部署的指令集。该指令集指示驱动程序应如何连接到 MongoDB&#xff0c;以及在连接时应如何运行。下图解释了示例连接 URI 的各个部分&#xff1a; 连接的URI 主要分为 以下四个部分 第一部分 连接协议 示例中使用的 连接到具有…

计算机视觉中的上采样与下采样:深入浅出实例代码解析

文章目录 一、引言二、下采样&#xff08;Downsampling&#xff09;三、上采样&#xff08;Upsampling&#xff09;1. 最近邻插值2.双线性插值3.转置卷积&#xff08;Deconvolution&#xff09;4.代码部分 四、总结 在计算机视觉领域&#xff0c;尤其是在深度学习和卷积神经网络…

宝塔面板部署webman项目+nginx反向代理

新建站点 新建一个站点&#xff0c;php版本选择纯净态即可&#xff0c;反正都是用不上的&#xff0c;域名填写你申请得到的域名 拉取代码 新建一个目录&#xff0c;然后将代码部署到本地 启动项目 推荐使用宝塔面板的进程守护管理器启动项目&#xff0c;其实就是用superviso…

ATT格式与Intel格式x86汇编指令的区别

AT&T公司 这个公司的创始人就是发明电话的贝尔&#xff0c;而Unix和C语言都是出自贝尔实验室的产物。 Intel公司 世界上第一片CPU是1971年发明的&#xff0c;型号是Intel生产的4004微处理器。 两种格式的区别 AT&T格式Intel格式目的操作数d、源操作数s op s, d 注…

vue2中使用i18n配置elementUi切换语言

1、下载插件 npm i vue-i18n8.22.2 2、新建文件夹i18n 3、编写index.js文件 import Vue from "vue"; import VueI18n from "vue-i18n"; import locale from element-ui/lib/locale; // 引入 elementui 的多语言 import enLocale from element-ui/lib/l…

【MySQL】C/C++连接MySQL客户端,MySQL函数接口认知,图形化界面进行连接

【MySQL】C/C引入MySQL客户端 安装mysqlclient库mysql接口介绍初始化mysql_init链接数据库mysql_real_connect下发mysql命令mysql_query获取出错信息mysql_error获取执行结果mysql_store_result获取结果行数mysql_num_rows获取结果列数mysql_num_fields判断结果列数mysql_field…

域自适应,你适应了嘛?

“最难的深度学习是谁&#xff1f;” “嗯&#xff0c;是迁徙学习吧&#xff1f;” “要分情况&#xff0c;不过&#xff0c;应该是迁徙学习吧&#xff1f; ” “不是迁徙学习嘛&#xff1f;” 目录 域自适应是啥&#xff1f; 域自适应的方法&#xff1f; 基于差异的方法…

Kafka系列之:Kafka Connect深入探讨 - 错误处理和死信队列

Kafka系列之&#xff1a;Kafka Connect深入探讨 - 错误处理和死信队列 一、快速失败二、YOLO&#xff1a;默默忽略坏消息三、如果一条消息掉在树林里&#xff0c;会发出声音吗&#xff1f;四、将消息路由到死信队列五、记录消息失败原因&#xff1a;消息头六、记录消息失败原因…