828华为云征文|采用Flexus云服务器X实例部署RTSP直播服务器
一、前言
这篇文章讲解: 采用华为云最新推出的Flexus云服务器X实例搭建RTSP服务器,完成视频直播需求。
随着实时视频流传输需求的增长,RTSP(实时流协议)服务器成为了许多视频监控、直播和多媒体应用的核心组件。在当今高度互联的世界中,能够快速部署且稳定运行的RTSP服务器对于确保高质量的视频流体验至关重要。本文将指导如何在华为云Flexus X实例上部署一款轻量级的RTSP服务器——(采用simple-rtsp-server)。
通过本教程,将详细介绍如何在华为云Flexus X实例上安装和配置simple-rtsp-server。simple-rtsp-server以其简单易用的特点而闻名,非常适合那些希望快速搭建起基本RTSP服务的开发者。将涵盖从环境准备到软件安装,再到基本配置的所有步骤,帮助顺利搭建一个可靠的RTSP服务器,为视频流应用提供坚实的基础。
华为云Flexus云服务器X实例是由国家科技进步奖获得者、华为公司Fellow及华为云首席架构师顾炯炯牵头研发的一款创新性云服务器。该实例基于华为的擎天QingTian架构、瑶光云脑和盘古大模型等核心技术,是业界首款应用驱动的柔性算力云服务器,适用于高科技、零售、金融、游戏等多个行业的通用工作负载场景,如网络应用、数据库、虚拟桌面、分析索引、微服务及持续集成/持续部署(CI/CD)等。
传统的云服务器通常只提供固定的CPU和内存规格,无法精准匹配用户的实际资源需求,导致资源利用效率低下。相比之下,华为云Flexus X实例提供了更为灵活的算力配置,支持超过100种不同的CPU与内存配比,最高可达到3:1的比例,从而更好地适应各种业务应用的需求。
Flexus X实例不仅在性能方面表现出色,还内置了智能应用调优算法,结合华为技术专家多年积累的经验,在基础模式下,其GeekBench单核及多核跑分可达业界同规格独享型实例的1.6倍。在性能模式下,Flexus X实例的性能超过了同类C系/G系/R系及S系旗舰型云主机的标准。
此外,Flexus X实例还配备了X-Turbo加速技术和大模型底层智能调度技术,为关键业务应用提供加速功能。例如,在Flexus X实例上部署的MySQL、Redis和Nginx等应用,其性能最高可达业界同规格独享型实例的6倍(MySQL性能),长期运行时也能保持2倍的性能优势。
Flexus X实例在定价策略上定位于经济型级别,但其性能表现却超越了旗舰级云主机。通过动态业务画像规格优化等技术,用户在将业务从本地服务器或其他云服务提供商迁移到Flexus X实例时,可以节省高达30%的算力成本,从而实现业务的全面提速和效能提升,享受到云基础设施的显著改进体验。
二、服务器选购
2.1 登录官网
链接:https://www.huaweicloud.com/
在官网首页的轮播图里可以看到,有Flexus
云服务器的宣传。这是华为云匠心打造的下一代跃级产品,面向中低负载场景,性能倍增、体验跃级的服务器。
2.2 选购服务器
在产品页面,也可以看到Flexus
云服务的选项,点击进去选购服务器。
链接:https://www.huaweicloud.com/product/flexus.html
在选购页面可以看到服务器推广器件,1年36
块钱。 每个月的流量是100G
,对于一些访问量不高的服务器或者测试用是非常合适的。
当前我要选择的服务器是:Flexus云服务器X实例
,点击Flexus
系列产品,选择X实例。Flexus云服务器X实例符合:柔性算力,六倍性能,旗舰体验,覆盖高科技、零售、金融、游戏等行业大多数通用工作负载场景。
2.3 选择服务器区域
针对时延敏感型业务请选择靠近您业务的区域,以降低网络时延,提高访问速度;针对和存量云产品有内网互通需求的业务,请选择和存量产品相同的区域。
2.4 选择服务器规格
2.5 选择系统镜像
我这选择ubuntu系统,用来搭建服务器。这个根据自己的情况选择,自己适合那一种就选择哪一种。
2.6 选择存储盘
我选择150G大小。
2.7 配置密码
设置好服务器的名字(如果你有多个服务器,为了自己好区别)和系统的登录密码。
2.8 配置云备份
云备份这个不买。有需要自己可以购买。
2.9 确认配置
2.10 立即购买
购买成功。
创建成功之后,邮箱会收到提示的。
2.10 后台控制台
链接:https://console.huaweicloud.com/ecm
在控制台可以看到服务器的详情。
三、服务器登录
3.1 查看服务器的详情
点击服务器的名称,可以进去到详情页面。
3.2 远程登录
填入设置好的密码。
登录成功。
3.9 采用FinalShell登录
自带的在浏览器里运行,每次需要打开浏览器,文件也不方便上传下载。
所以,这里开发阶段,我采用的 FinalShell登录到服务器。
新建SSH连接,输入连接信息。
登录成功。
接下来就可以进行开发了。
四、搭建RTSP流媒体服务器
4.1 simple-rtsp-server库
simple-rtsp-server依赖ffmpeg,版本要求>=4.x。
GitHub仓库地址:https://github.com/BreakingY/simple-rtsp-server
4.2 安装基础依赖
在命令行运行。
sudo apt-get -y install autoconf automake build-essential libass-dev libfreetype6-dev libsdl2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texinfo zlib1g-dev
安装过程中:
安装完成。
4.3 安装依赖库
在系统命令行分别运行以下命令,安装依赖库。
汇编库:
sudo apt-get install yasm
sudo apt-get install nasm视频库:
sudo apt-get install libx264-dev
sudo apt-get install libx265-dev音频库:
sudo apt-get install libfdk-aac-dev
sudo apt-get install libmp3lame-dev
sudo apt-get install libopus-dev
4.4 安装ffmpeg
下载ffmpeg的库源码。
wget https://ffmpeg.org//releases/ffmpeg-4.0.5.tar.bz2tar xjvf ffmpeg-4.0.5.tar.bz2cd ffmpeg-4.0.5
编译ffmpeg库源码。
./configure --prefix=/usr/local --enable-libx264 --disable-x86asm --enable-nonfree --enable-libfdk-aac --enable-shared --enable-gpl --enable-libmp3lame --enable-libopus --extra-cflags=-I/usr/local/include --extra-ldflags=-L/usr/local/libmakemake install
4.5 simple-rtsp-server下载编译
git clone https://github.com/BreakingY/simple-rtsp-server.gitcd simple-rtsp-servermkdir buildcd buildcmake ..make -j4
4.6 运行服务器
cp -r ../mp4path .
./rtsp_server loop 0
运行效果:
4.7 开放规则
要记得把服务器的 端口开放出来, 不然无法访问。
4.8 播放测试(RTSP协议播放云端视频文件)
自带了4个测试文件。 可以直接播放。
这是自带的4个测试文件的路径:
root@flexusx-1a58:~/work/simple-rtsp-server# ls mp4path/
test_1280x720_h264_aac.mp4 test_h264_aac.mp4 test_h264_pcma.mkv test_h265_aac.mp4
下面是播放格式:
rtsp://116.205.107.156:8554/test_1280x720_h264_aac.mp4
其中:
rtsp://<自己的公网IP>:8554/<mp4path目录下的视频文件名称>
下面是potplayer播放器播放的效果:
播放过程中,在服务器上会打印出每个过程的详情。
4.9 rtsp_server.c代码
#include "common.h"
#include "session.h"
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define BUF_MAX_SIZE (1024 * 1024)
char *mp4Dir = "mp4path/\0"; // MP4文件存放位置
int auth = 1;
#define USER "admin"
#define PASSWORD "123456"
/*doClientThd线程参数*/
struct thd_arg_st
{int client_sock_fd;char client_ip[30];int client_port;
};int create_rtp_sockets(int *fd1, int *fd2, int *port1, int *port2)
{struct sockaddr_in addr;int port = 0;*fd1 = socket(AF_INET, SOCK_DGRAM, 0);if (*fd1 < 0){perror("socket");return -1;}*fd2 = socket(AF_INET, SOCK_DGRAM, 0);if (*fd2 < 0){perror("socket");close(*fd1);return -1;}memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_ANY);for (port = 1024; port <= 65535; port += 2){addr.sin_port = htons(port);if (bind(*fd1, (struct sockaddr *)&addr, sizeof(addr)) == 0){addr.sin_port = htons(port + 1);if (bind(*fd2, (struct sockaddr *)&addr, sizeof(addr)) == 0){*port1 = port;*port2 = port + 1;return 0;}close(*fd1);}}close(*fd1);close(*fd2);return -1;
}
static void generate_session_id(char *session_id, size_t size) {if (size < 9) {return;}time_t timestamp = time(NULL);srand((unsigned int)timestamp);int random_part = rand() % 1000000;snprintf(session_id, size, "%02ld%06d", timestamp % 100, random_part);return;
}
/*处理客户端rtsp请求*/
void *doClientThd(void *arg)
{signal(SIGINT, sig_handler);signal(SIGQUIT, sig_handler);signal(SIGKILL, sig_handler);struct thd_arg_st *arg_thd = (struct thd_arg_st *)arg;int client_sock_fd = arg_thd->client_sock_fd;char *client_ip = arg_thd->client_ip;int client_port = arg_thd->client_port;char method[40];char url[100];char url_tmp[100];char filename[100];char url_setup[100];char track[1024];char url_play[1024];char local_ip[40];char version[40];int cseq;char *buf_ptr;char *buf_tmp;char *recv_buf = malloc(BUF_MAX_SIZE);char *send_buf = malloc(BUF_MAX_SIZE);char line[400];// rtp_over_tcpint sig_0 = -1;int sig_1 = -1;int sig_2 = -1;int sig_3 = -1;int ture_of_rtp_tcp = 0;// rtp_over_udpint client_rtp_port = -1;int client_rtcp_port = -1;int client_rtp_port_1 = -1;int client_rtcp_port_1 = -1;int server_rtp_port = -1;int server_rtcp_port = -1;int server_rtp_port_1 = -1;int server_rtcp_port_1 = -1;int server_udp_socket_rtp_fd = -1;int server_udp_socket_rtcp_fd = -1;int server_udp_socket_rtp_1_fd = -1;int server_udp_socket_rtcp_1_fd = -1;char ch = '/';int findflag = 0;char path[100];memcpy(path, mp4Dir, strlen(mp4Dir));path[strlen(mp4Dir)] = '\0';char path_tmp[100];memcpy(path_tmp, mp4Dir, strlen(mp4Dir));path_tmp[strlen(mp4Dir)] = '\0';int fd;char *realm = "simple-rtsp-server";char nonce[33] = {0};generate_nonce(nonce, sizeof(nonce));char session_id[512];generate_session_id(session_id, sizeof(session_id));while (1){int recv_len;recv_len = recv(client_sock_fd, recv_buf, BUF_MAX_SIZE, 0);if (recv_len <= 0)goto out;recv_buf[recv_len] = '\0';printf("---------------C->S--------------\n");printf("%s", recv_buf);buf_ptr = getLineFromBuf(recv_buf, line);buf_tmp = buf_ptr;if (sscanf(line, "%s %s %s\r\n", method, url, version) != 3){printf("parse err\n");goto out;}/*解析序列号*/while (1){buf_ptr = getLineFromBuf(buf_ptr, line);if (!strncmp(line, "CSeq:", strlen("CSeq:"))){if (sscanf(line, "CSeq: %d\r\n", &cseq) != 1){printf("parse err\n");goto out;}break;}}if(auth == 1){// authorizationif(!strcmp(method, "SETUP") || !strcmp(method, "DESCRIBE") || !strcmp(method, "PLAY")){AuthorizationInfo *auth_info = find_authorization(recv_buf);if(auth_info == NULL){handleCmd_Unauthorized(send_buf, cseq, realm, nonce);printf("---------------S->C--------------\n");printf("%s", send_buf);send(client_sock_fd, send_buf, strlen(send_buf), 0);continue;}else{// printf("nonce:%s\n", auth_info->nonce);// printf("realm:%s\n", auth_info->realm);// printf("response:%s\n", auth_info->response);// printf("uri:%s\n", auth_info->uri);// printf("username:%s\n", auth_info->username);// 鉴权校验int ret = authorization_verify(USER, PASSWORD, realm, nonce, auth_info->uri, method, auth_info->response);free_authorization_info(auth_info);if(ret < 0){ // 鉴权失败goto out;}}}}/* 如果是SETUP,需要解析是RTP_OVER_TCP还是RTP_OVER_UDP模式 */if (!strcmp(method, "SETUP")){memset(url_setup, 0, sizeof(url_setup));memset(track, 0, sizeof(track));strcpy(url_setup, url);char *p = strrchr(url_setup, ch);memcpy(track, p + 1, strlen(p)); // video-track0 audio -track1while (1){buf_tmp = getLineFromBuf(buf_tmp, line);if (!buf_tmp){break;}if (!strncmp(line, "Transport: RTP/AVP/TCP", strlen("Transport: RTP/AVP/TCP"))){if (memcmp(track, "track0", 6) == 0){sscanf(line, "Transport: RTP/AVP/TCP;unicast;interleaved=%d-%d\r\n", &sig_0, &sig_1);}else{sscanf(line, "Transport: RTP/AVP/TCP;unicast;interleaved=%d-%d\r\n", &sig_2, &sig_3);}ture_of_rtp_tcp = 1;break;}if (!strncmp(line, "Transport: RTP/AVP/UDP", strlen("Transport: RTP/AVP/UDP"))){if (memcmp(track, "track0", 6) == 0){sscanf(line, "Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n", &client_rtp_port, &client_rtcp_port);}else{sscanf(line, "Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n", &client_rtp_port_1, &client_rtcp_port_1);}break;}if (!strncmp(line, "Transport: RTP/AVP", strlen("Transport: RTP/AVP"))){if (memcmp(track, "track0", 6) == 0){sscanf(line, "Transport: RTP/AVP;unicast;client_port=%d-%d\r\n", &client_rtp_port, &client_rtcp_port);}else{sscanf(line, "Transport: RTP/AVP;unicast;client_port=%d-%d\r\n", &client_rtp_port_1, &client_rtcp_port_1);}break;}}}if (!strcmp(method, "OPTIONS")){char *p = strrchr(url, ch);memcpy(filename, p + 1, strlen(p));char *tmp = strcat(path_tmp, filename);findflag = 1;fd = open(tmp, O_RDONLY);if (fd < 0) // 请求的资源不存在返回404并关闭客户端文件描述符{perror("failed");handleCmd_404(send_buf, cseq);send(client_sock_fd, send_buf, strlen(send_buf), 0);goto out;}else{close(fd);if (handleCmd_OPTIONS(send_buf, cseq)){printf("failed to handle options\n");goto out;}}}else if (!strcmp(method, "DESCRIBE")){if (findflag == 0){char *p = strrchr(url, ch);memcpy(filename, p + 1, strlen(p));char *tmp = strcat(path_tmp, filename);fd = open(tmp, O_RDONLY);if (fd < 0) // 请求的资源不存在返回404并关闭客户端文件描述符{perror("failed");handleCmd_404(send_buf, cseq);send(client_sock_fd, send_buf, strlen(send_buf), 0);goto out;}close(fd);findflag = 1;}char sdp[1024];char localIp[100];sscanf(url, "rtsp://%[^:]:", localIp);int ret = generateSDP(path_tmp, localIp, sdp, sizeof(sdp));if (ret < 0){ // mp4文件有问题,或者视频不是H264/H265,音频不是AAC/PCMAhandleCmd_500(send_buf, cseq);send(client_sock_fd, send_buf, strlen(send_buf), 0);goto out;}if (handleCmd_DESCRIBE(send_buf, cseq, url, sdp)){printf("failed to handle describe\n");goto out;}}else if (!strcmp(method, "SETUP") && ture_of_rtp_tcp == 0) // RTP_OVER_UDP{sscanf(url, "rtsp://%[^:]:", local_ip);if (memcmp(track, "track0", 6) == 0){create_rtp_sockets(&server_udp_socket_rtp_fd, &server_udp_socket_rtcp_fd, &server_rtp_port, &server_rtp_port);handleCmd_SETUP_UDP(send_buf, cseq, client_rtp_port, server_rtp_port, session_id);}else{create_rtp_sockets(&server_udp_socket_rtp_1_fd, &server_udp_socket_rtcp_1_fd, &server_rtp_port_1, &server_rtp_port_1);handleCmd_SETUP_UDP(send_buf, cseq, client_rtp_port_1, server_rtp_port_1, session_id);}}else if (!strcmp(method, "SETUP") && ture_of_rtp_tcp == 1) // RTP_OVER_TCP{sscanf(url, "rtsp://%[^:]:", local_ip);if (memcmp(track, "track0", 6) == 0){handleCmd_SETUP_TCP(send_buf, cseq, local_ip, client_ip, sig_0, session_id);}else{handleCmd_SETUP_TCP(send_buf, cseq, local_ip, client_ip, sig_2, session_id);}}else if (!strcmp(method, "PLAY")){memset(url_play, 0, sizeof(url_play));memset(track, 0, sizeof(track));strcpy(url_play, url);if (handleCmd_PLAY(send_buf, cseq, url_play, session_id)){printf("failed to handle play\n");goto out;}}else{goto out;}printf("---------------S->C--------------\n");printf("%s", send_buf);send(client_sock_fd, send_buf, strlen(send_buf), 0);if (!strcmp(method, "PLAY")){struct timeval time_pre, time_now;gettimeofday(&time_pre, NULL);char *tmp = strcat(path, filename);int ret = addClient(tmp, client_sock_fd, sig_0, sig_2, ture_of_rtp_tcp, client_ip, client_rtp_port, client_rtp_port_1,server_udp_socket_rtp_fd, server_udp_socket_rtcp_fd, server_udp_socket_rtp_1_fd, server_udp_socket_rtcp_1_fd);if (ret < 0)goto out;int sum = getClientNum();gettimeofday(&time_now, NULL);int time_handle = 1000 * (time_now.tv_sec - time_pre.tv_sec) + (time_now.tv_usec - time_pre.tv_usec) / 1000;printf("timeuse:%dms sum_client:%d\n\n", time_handle, sum);goto over;}}
out:close(client_sock_fd);free(recv_buf);free(send_buf);free(arg);return NULL;
over:free(recv_buf);free(send_buf);free(arg);return NULL;
}int main(int argc, char *argv[])
{if(argc < 3){printf("./rtsp_server auth(0-not authentication; 1-authentication) loop(0-not loop 1-loop)\n");return -1;}auth = atoi(argv[1]);reloop_flag = atoi(argv[2]);int server_sock_fd;int ret;signal(SIGINT, sig_handler);signal(SIGQUIT, sig_handler);signal(SIGKILL, sig_handler);signal(SIGPIPE, SIG_IGN);signal(SIGFPE, SIG_IGN);server_sock_fd = createTcpSocket();if (server_sock_fd < 0){printf("failed to create tcp socket\n");return -1;}ret = bindSocketAddr(server_sock_fd, SERVER_IP, SERVER_PORT);if (ret < 0){printf("failed to bind addr\n");return -1;}ret = listen(server_sock_fd, 100);if (ret < 0){printf("failed to listen\n");return -1;}moduleInit();printf("rtsp://%s:%d/filename\n", SERVER_IP, SERVER_PORT);while (1){int client_sock_fd;char client_ip[40];int client_port;pthread_t tid;client_sock_fd = acceptClient(server_sock_fd, client_ip, &client_port);if (client_sock_fd < 0){printf("failed to accept client\n");return -1;}printf("###########accept client --> client_sock_fd:%d client ip:%s,client port:%d###########\n", client_sock_fd, client_ip, client_port);struct thd_arg_st *arg;arg = malloc(sizeof(struct thd_arg_st));memcpy(arg->client_ip, client_ip, strlen(client_ip));arg->client_port = client_port;arg->client_sock_fd = client_sock_fd;ret = pthread_create(&tid, NULL, doClientThd, (void *)arg);if (ret < 0){perror("doClientThd pthread_create()");}pthread_detach(tid);}moduleDel();close(server_sock_fd);return 0;
}
4.10 common.c 代码
#include "common.h"
#include "md5.h"
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/log.h>
#include <libavutil/time.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int createTcpSocket()
{int sockfd;int on = 1;sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0)return -1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));return sockfd;
}
int createUdpSocket()
{int sockfd;int on = 1;sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0)return -1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));return sockfd;
}int bindSocketAddr(int sockfd, const char *ip, int port)
{struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip);if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0)return -1;return 0;
}
int acceptClient(int sockfd, char *ip, int *port)
{int clientfd;socklen_t len = 0;struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));len = sizeof(addr);clientfd = accept(sockfd, (struct sockaddr *)&addr, &len);if (clientfd < 0){printf("accept err:%s\n", strerror(errno));return -1;}strcpy(ip, inet_ntoa(addr.sin_addr));*port = ntohs(addr.sin_port);return clientfd;
}
void rtpHeaderInit(struct RtpPacket *rtpPacket, uint8_t csrcLen, uint8_t extension,uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker,uint16_t seq, uint32_t timestamp, uint32_t ssrc)
{rtpPacket->rtpHeader.csrcLen = csrcLen;rtpPacket->rtpHeader.extension = extension;rtpPacket->rtpHeader.padding = padding;rtpPacket->rtpHeader.version = version;rtpPacket->rtpHeader.payloadType = payloadType;rtpPacket->rtpHeader.marker = marker;rtpPacket->rtpHeader.seq = seq;rtpPacket->rtpHeader.timestamp = timestamp;rtpPacket->rtpHeader.ssrc = ssrc;return;
}char *getLineFromBuf(char *buf, char *line)
{while (*buf != '\n'){*line = *buf;line++;buf++;}*line = '\n';++line;*line = '\0';++buf;return buf;
}
static char *extract_value(const char *source, const char *key) {const char *start = strstr(source, key);if (!start) {return NULL;}start += strlen(key) + 2; // 跳过 key="const char *end = strchr(start, '"');if (!end) {return NULL;}size_t len = end - start;char *value = (char *)malloc(len + 1);if (!value) {return NULL;}strncpy(value, start, len);value[len] = '\0';return value;
}AuthorizationInfo *find_authorization(const char *request) {const char *auth_start = strstr(request, "Authorization: ");if (!auth_start) {return NULL; // Authorization字段未找到}auth_start += strlen("Authorization: ");const char *auth_end = strchr(auth_start, '\r');if (!auth_end) {auth_end = strchr(auth_start, '\n');}if (!auth_end) {return NULL; // 无法找到行尾}char *auth_value = (char *)malloc(auth_end - auth_start + 1);if (!auth_value) {return NULL; // 内存分配失败}strncpy(auth_value, auth_start, auth_end - auth_start);auth_value[auth_end - auth_start] = '\0';AuthorizationInfo *auth_info = (AuthorizationInfo *)malloc(sizeof(AuthorizationInfo));if (!auth_info) {free(auth_value);return NULL; // 内存分配失败}auth_info->username = extract_value(auth_value, "username");auth_info->realm = extract_value(auth_value, "realm");auth_info->nonce = extract_value(auth_value, "nonce");auth_info->uri = extract_value(auth_value, "uri");auth_info->response = extract_value(auth_value, "response");free(auth_value);return auth_info;
}void free_authorization_info(AuthorizationInfo *auth_info) {if (auth_info) {free(auth_info->username);free(auth_info->realm);free(auth_info->nonce);free(auth_info->uri);free(auth_info->response);free(auth_info);}return;
}
// 生成随机字符串
static void generate_random_string(char *buf, int length) {static const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";for (size_t i = 0; i < length; i++) {buf[i] = charset[rand() % (sizeof(charset) - 1)];}return;
}// 生成nonce
void generate_nonce(char *nonce, int length) {if (length < 1) {nonce[0] = '\0';return;}memset(nonce, 0, length);srand((unsigned int)time(NULL));char random_string[128] = {0};generate_random_string(random_string, sizeof(random_string));char timestamp[32];snprintf(timestamp, sizeof(timestamp), "%ld", (long)time(NULL));char combined[256] = {0};snprintf(combined, sizeof(combined), "%s%s", random_string, timestamp);MD5_CTX md5;unsigned char decrypt[16];MD5Init(&md5);MD5Update(&md5, combined, strlen(combined));MD5Final(&md5, decrypt);for(int i = 0; i < 16; i++) {snprintf(&(nonce[i * 2]), 3, "%02x", decrypt[i]);}return;
}int authorization_verify(char *username, char *password, char *realm, char *nonce, char *uri, char * method, char *response){// md5(username:realm:password)unsigned char res1[16];char res1_hex[33] = {0};char buffer1[256] = {0};sprintf(buffer1,"%s:%s:%s", username, realm, password);MD5_CTX md5_1;MD5Init(&md5_1);MD5Update(&md5_1, buffer1, strlen(buffer1));MD5Final(&md5_1, res1);for(int i = 0; i < 16; i++) {snprintf(&(res1_hex[i * 2]), 3, "%02x", res1[i]);}// md5(public_method:url)unsigned char res2[16];char res2_hex[33] = {0};char buffer2[256] = {0};sprintf(buffer2,"%s:%s", method, uri);MD5_CTX md5_2;MD5Init(&md5_2);MD5Update(&md5_2, buffer2, strlen(buffer2));MD5Final(&md5_2, res2);for(int i = 0; i < 16; i++) {snprintf(&(res2_hex[i * 2]), 3, "%02x", res2[i]);}// md5( md5(username:realm:password):nonce:md5(public_method:url) )unsigned char res[16];char res_hex[33] = {0};char buffer[512] = {0};sprintf(buffer,"%s:%s:%s", res1_hex, nonce, res2_hex);MD5_CTX md5;MD5Init(&md5);MD5Update(&md5, buffer, strlen(buffer));MD5Final(&md5, res);for(int i = 0; i < 16; i++) {snprintf(&(res_hex[i * 2]), 3, "%02x", res[i]);}// printf("res:%s response:%s\n", res_hex, response);if(strcmp(res_hex, response) == 0){return 0;}return -1;
}int handleCmd_Unauthorized(char *result, int cseq, char *realm, char *nonce){sprintf(result, "RTSP/1.0 401 Unauthorized\r\n""CSeq: %d\r\n""WWW-Authenticate: Digest realm=\"%s\", nonce=\"%s\"\r\n""\r\n",cseq,realm,nonce);return 0;
}
int handleCmd_OPTIONS(char *result, int cseq)
{sprintf(result, "RTSP/1.0 200 OK\r\n""CSeq: %d\r\n""Public: OPTIONS, DESCRIBE, SETUP, PLAY\r\n""\r\n",cseq);return 0;
}
int handleCmd_DESCRIBE(char *result, int cseq, char *url, char *sdp)
{sprintf(result, "RTSP/1.0 200 OK\r\nCSeq: %d\r\n""Content-Base: %s\r\n""Content-type: application/sdp\r\n""Content-length: %d\r\n\r\n""%s",cseq,url,strlen(sdp),sdp);return 0;
}
int handleCmd_SETUP_TCP(char *result, int cseq, char *localIp, char *clientIp, int sig_0, char *session)
{sprintf(result, "RTSP/1.0 200 OK\r\n""CSeq: %d\r\n""Transport: RTP/AVP/TCP;unicast;destination=%s;source=%s;interleaved=%d-%d\r\n""Session: %s\r\n""\r\n",cseq,clientIp,localIp,sig_0,sig_0 + 1,session);return 0;
}
int handleCmd_SETUP_UDP(char *result, int cseq, int clientRtpPort, int serverRtpPort, char *session)
{sprintf(result, "RTSP/1.0 200 OK\r\n""CSeq: %d\r\n""Transport: RTP/AVP;unicast;client_port=%d-%d;server_port=%d-%d\r\n""Session: %s\r\n""\r\n",cseq,clientRtpPort,clientRtpPort + 1,serverRtpPort,serverRtpPort + 1,session);return 0;
}
int handleCmd_PLAY(char *result, int cseq, char *url_setup, char *session)
{sprintf(result, "RTSP/1.0 200 OK\r\n""CSeq: %d\r\n""Range: npt=0.000-\r\n""Session: %s; timeout=60\r\n\r\n",cseq,session);return 0;
}
int handleCmd_404(char *result, int cseq)
{sprintf(result, "RTSP/1.0 404 NOT FOUND\r\n""CSeq: %d\r\n""\r\n",cseq);return 0;
}
int handleCmd_500(char *result, int cseq)
{sprintf(result, "RTSP/1.0 500 SERVER ERROR\r\n""CSeq: %d\r\n""\r\n",cseq);return 0;
}
int check_media_info(const char *filename, MediaInfo *info)
{AVFormatContext *format_ctx = NULL;int ret;if ((ret = avformat_open_input(&format_ctx, filename, NULL, NULL)) < 0){fprintf(stderr, "Could not open source file %s\n", filename);return ret;}if ((ret = avformat_find_stream_info(format_ctx, NULL)) < 0){fprintf(stderr, "Could not find stream information\n");avformat_close_input(&format_ctx);return ret;}info->has_audio = 0;info->has_video = 0;info->is_video_h26x = 0;info->is_audio_aac_pcma = 0;info->audio_sample_rate = 0;info->audio_channels = 0;// info->vps = NULL;// info->sps = NULL;// info->pps = NULL;// info->vps_size = 0;// info->sps_size = 0;// info->pps_size = 0;for (unsigned int i = 0; i < format_ctx->nb_streams; i++){AVStream *stream = format_ctx->streams[i];AVCodecParameters *codecpar = stream->codecpar;if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO){info->has_video = 1;if (codecpar->codec_id == AV_CODEC_ID_H264 || codecpar->codec_id == AV_CODEC_ID_H265 || codecpar->codec_id == AV_CODEC_ID_HEVC){info->is_video_h26x = 1;if (codecpar->codec_id == AV_CODEC_ID_H264)info->video_type = H264;elseinfo->video_type = H265;}}else if (codecpar->codec_type == AVMEDIA_TYPE_AUDIO){info->has_audio = 1;if (codecpar->codec_id == AV_CODEC_ID_AAC || codecpar->codec_id == AV_CODEC_ID_PCM_ALAW){info->is_audio_aac_pcma = 1;info->audio_sample_rate = codecpar->sample_rate;info->audio_channels = codecpar->channels;info->profile = codecpar->profile;if(codecpar->codec_id == AV_CODEC_ID_AAC)info->audio_type = AAC;elseinfo->audio_type = PCMA;}}}avformat_close_input(&format_ctx);return 0;
}
void free_media_info(MediaInfo *info)
{// if (info->vps)// {// free(info->vps);// }// if (info->sps)// {// free(info->sps);// }// if (info->pps)// {// free(info->pps);// }return;
}
/*
#define FF_PROFILE_AAC_MAIN 0
#define FF_PROFILE_AAC_LOW 1
#define FF_PROFILE_AAC_SSR 2
#define FF_PROFILE_AAC_LTP 3
#define FF_PROFILE_AAC_HE 4
#define FF_PROFILE_AAC_HE_V2 28
#define FF_PROFILE_AAC_LD 22
#define FF_PROFILE_AAC_ELD 38
#define FF_PROFILE_MPEG2_AAC_LOW 128
#define FF_PROFILE_MPEG2_AAC_HE 131
*/
static int get_audio_obj_type(int aactype){//AAC HE V2 = AAC LC + SBR + PS//AAV HE = AAC LC + SBR//所以无论是 AAC_HEv2 还是 AAC_HE 都是 AAC_LCswitch(aactype){case 0:case 2:case 3:return aactype + 1;case 1:case 4:case 28:return 2;default:return 2;}return 2;
}static int get_sample_rate_index(int freq, int aactype){int i = 0;int freq_arr[13] = {96000, 88200, 64000, 48000, 44100, 32000,24000, 22050, 16000, 12000, 11025, 8000, 7350};//如果是 AAC HEv2 或 AAC HE, 则频率减半if(aactype == 28 || aactype == 4){freq /= 2;}for(i = 0; i < 13; i++){if(freq == freq_arr[i]){return i;}}return 4;//默认是44100
}static int get_channel_config(int channels, int aactype){//如果是 AAC HEv2 通道数减半if(aactype == 28){return (channels / 2);}return channels;
}
int generateSDP(char *file, char *localIp, char *buffer, int buffer_len)
{memset(buffer, 0, buffer_len);MediaInfo info;if (check_media_info(file, &info) != 0){printf("server error\n");free_media_info(&info);return -1;}if (info.has_video && !info.is_video_h26x){printf("only support h264 h265\n");free_media_info(&info);return -1;}if (info.has_audio && !info.is_audio_aac_pcma){printf("only support aac pcma\n");free_media_info(&info);return -1;}sprintf(buffer, "v=0\r\n""o=- 9%ld 1 IN IP4 %s\r\n""c=IN IP4 %s\r\n""t=0 0\r\n""a=control:*\r\n",time(NULL), localIp, localIp);if (info.has_video){sprintf(buffer + strlen(buffer), "m=video 0 RTP/AVP %d\r\n""a=rtpmap:%d %s/90000\r\n"//"a=fmtp:%d profile-level-id=42A01E; packetization-mode=1; sprop-parameter-sets=Z0IACpZTBYmI,aMljiA==\r\n""a=fmtp:%d packetization-mode=1\r\n""a=control:track0\r\n",RTP_PAYLOAD_TYPE_H26X, RTP_PAYLOAD_TYPE_H26X, info.video_type == H264 ? "H264" : "H265", RTP_PAYLOAD_TYPE_H26X);}if (info.has_audio){if (info.audio_type == AAC){char config[10] = {0};int index = get_sample_rate_index(info.audio_sample_rate, info.profile);sprintf(config, "%02x%02x", (uint8_t)((get_audio_obj_type(info.profile)) << 3)|(index >> 1), (uint8_t)((index << 7)|(info.audio_channels << 3)));sprintf(buffer + strlen(buffer), "m=audio 0 RTP/AVP %d\r\n""a=rtpmap:%d MPEG4-GENERIC/%u/%u\r\n""a=fmtp:%d streamtype=5;profile-level-id=1;mode=AAC-hbr;config=%04u;sizelength=13;indexlength=3;indexdeltalength=3\r\n""a=control:track1\r\n",RTP_PAYLOAD_TYPE_AAC, RTP_PAYLOAD_TYPE_AAC, info.audio_sample_rate, info.audio_channels, RTP_PAYLOAD_TYPE_AAC, atoi(config));}else{sprintf(buffer + strlen(buffer), "m=audio 0 RTP/AVP %d\r\n""a=rtpmap:%d PCMA/%u/%u\r\n""a=control:track1\r\n",RTP_PAYLOAD_TYPE_PCMA, RTP_PAYLOAD_TYPE_PCMA, info.audio_sample_rate, info.audio_channels);}}free_media_info(&info);return 0;
}
// aactype = ffmpeg --> AVCodecParameters *codecpar->profile
void adts_header(char *adts_header_buffer, int data_len, int aactype, int frequency, int channels){int audio_object_type = get_audio_obj_type(aactype);int sampling_frequency_index = get_sample_rate_index(frequency, aactype);int channel_config = get_channel_config(channels, aactype);int adts_len = data_len + 7;adts_header_buffer[0] = 0xff; //syncword:0xfff 高8bitsadts_header_buffer[1] = 0xf0; //syncword:0xfff 低4bitsadts_header_buffer[1] |= (0 << 3); //MPEG Version:0 for MPEG-4,1 for MPEG-2 1bitadts_header_buffer[1] |= (0 << 1); //Layer:0 2bitsadts_header_buffer[1] |= 1; //protection absent:1 1bitadts_header_buffer[2] = (audio_object_type - 1)<<6; //profile:audio_object_type - 1 2bitsadts_header_buffer[2] |= (sampling_frequency_index & 0x0f)<<2; //sampling frequency index:sampling_frequency_index 4bitsadts_header_buffer[2] |= (0 << 1); //private bit:0 1bitadts_header_buffer[2] |= (channel_config & 0x04)>>2; //channel configuration:channel_config 高1bitadts_header_buffer[3] = (channel_config & 0x03)<<6; //channel configuration:channel_config 低2bitsadts_header_buffer[3] |= (0 << 5); //original:0 1bitadts_header_buffer[3] |= (0 << 4); //home:0 1bitadts_header_buffer[3] |= (0 << 3); //copyright id bit:0 1bitadts_header_buffer[3] |= (0 << 2); //copyright id start:0 1bitadts_header_buffer[3] |= ((adts_len & 0x1800) >> 11); //frame length:value 高2bitsadts_header_buffer[4] = (uint8_t)((adts_len & 0x7f8) >> 3); //frame length:value 中间8bitsadts_header_buffer[5] = (uint8_t)((adts_len & 0x7) << 5); //frame length:value 低3bitsadts_header_buffer[5] |= 0x1f; //buffer fullness:0x7ff 高5bitsadts_header_buffer[6] = 0xfc;return;
}
五、总结
华为云正在举行其年度828 B2B企业节活动,期间提供了包括Flexus X实例在内的多种产品的优惠。对于那些对计算性能有较高要求,并且需要自行部署MySQL、Redis、Nginx等服务的用户来说,这次促销是一个很好的机会,建议有兴趣的朋友可以前往查看相关的优惠信息。
官网直达:https://activity.huaweicloud.com/828_promotion/index.html
相关文章:

828华为云征文|采用Flexus云服务器X实例部署RTSP直播服务器
一、前言 这篇文章讲解: 采用华为云最新推出的Flexus云服务器X实例搭建RTSP服务器,完成视频直播需求。 随着实时视频流传输需求的增长,RTSP(实时流协议)服务器成为了许多视频监控、直播和多媒体应用的核心组件。在当…...
Spring Cloud Gateway(二)
Spring Cloud Gateway(二) 文章目录 Spring Cloud Gateway(二)Gateway工作原理为什么使用API网关高并发Gateway性能优化 Gateway工作原理 Spring Cloud Gateway旨在为微服务架构提供简单、有效并且统一的API路由管理方式。它不仅…...
docker 简易入门
# docker 简易入门 docker由几个组成部分 docker client: 即 docker 命令行工具 docker host: 宿主机,docker daemon 的运行环境服务器 docker daemon: docker 的守护进程,docker client 通过命令行与 docker daemon 交互 container: 最小型的一个操…...

【看雪-注册安全分析报告】
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞…...
记录一个前端学习小组的收集的模版
问题1:输入“您的姓名”,选择“短答案”作为问题类型。问题2:输入“您是否愿意继续参加前端学习小组?”,选择“单选”作为问题类型,并添加选项“是”和“否”。问题3:输入“如果您选择‘是’&am…...

Rk3588 Android12 AIDL 开发
AIDL (Android Interface Definition Language) 和 HIDL (HAL Interface Definition Language) 都是 Android 系统中用于定义接口的工具,但它们有不同的用途和特性。 AIDL (Android Interface Definition Language) 用途: 主要用于应用程序之间的进程间…...
两个长整数字符串求和(不允许使用ES6+)
两个长整数字符串求和(不允许使用ES6), 面试手撸代码遇到到这个问题 1. 实现方式第一种 // 短整数字符串前边补 0; num需要补 0 的短整数字符串, len 长整数字符串的长度 function fillZero (num, len) {let str num.toString();if (str.length < len) {str 0.repeat(…...

11 Java 方法引用、异常处理、Java接口之函数式编程(接口知识补充Function<T,R>、BiFunction<T, U, R>和自定义泛型接口)
文章目录 前言一、Java接口之函数式编程 --- 接口知识补充1 Function<T,R>泛型接口2 BiFunction<T, U, R>泛型接口3 自定义泛型函数式编程接口4 使用lambda表达式、方法引用进行函数式编程二、方法引用1 方法引用初体验(以Array.sort()方法为例)(1)什么是方法引…...
深入探索 Go 语言的编译器与垃圾回收机制
Go 编译器 Go 编译器是通过 go 工具执行的,这个工具的功能不仅仅是生成可执行文件。你可以使用 go tool compile 命令来编译一个 Go 源文件。这个操作将生成一个目标文件,也就是 .o 后缀的文件。以下是在 macOS Mojave 系统上执行的命令和结果展示&…...

2024国赛数学建模-模拟火算法(MATLAB 实现)
模拟退火算法 1.1 算法原理 模拟退火算法的基本思想是从一给定解开始 ,从邻域 中随机产生另一个解 ,接受 Metropolis准则允许目标函数在 有限范围内变坏 ,它由一控制参数 t决定 ,其作用类似于物 理过程中的温度 T,对于控制参数的每一取值 ,算法持续进 行“产生 —判断 —接受…...
YOLOv8 只检测人 只画框不要标签
参考了这个:YOLOv8只检测人(或其他一种或者多种类别)_yolov8只检测指定类别-CSDN博客 1. 只检测人:predict的时候指定参数classes[0] 2. 只画框不要标签:plot的时候传入labelsFalse 3. 标签中去掉置信度:…...

如何将网络安全防范游戏化
组织对威胁的准备和恢复能力跟不上网络犯罪分子的进步。 一些首席执行官仍然认为网络安全需要偶尔干预,而不是持续关注。 但对于许多公司来说,情况并非如此;网络威胁准备需要协调一致的培训工作,因此网络安全团队在攻击发生时已…...

Qt QGraphicsView实现图片放缩、鼠标拖动移动、鼠标点位置放大缩小_图片查看
QtQGraphicsView实现图片放缩、鼠标拖动移动、鼠标点位置放大缩小 头文件: #ifndef TIMGWIDGET_H #define TIMGWIDGET_H#include <QGraphicsItem> #include <QMainWindow> #include <QObject> #include <QWidget>// class TImgWidget : pu…...

Percona Toolkit 神器全攻略(复制类)
Percona Toolkit 神器全攻略(复制类) Percona Toolkit 神器全攻略系列共八篇,前文回顾: 前文回顾Percona Toolkit 神器全攻略Percona Toolkit 神器全攻略(实用类)Percona Toolkit 神器全攻略(配…...
SQLite3 数据类型深入全面讲解
SQLite3,作为一款轻量级的数据库管理系统,在数据存储方面展现出了其独特的魅力。它不仅支持标准的SQL语法,还提供了丰富的数据类型供开发者选择。这些数据类型不仅涵盖了基本的数值和文本类型,还包括了日期时间、二进制数据等复杂…...
Python高效实现Trie(前缀树)及其插入和查找操作
Python高效实现Trie(前缀树)及其插入和查找操作 在Python面试中,考官通常会关注候选人的编程能力、问题解决能力以及对Python语言特性的理解。Trie(前缀树)是一种高效的数据结构,广泛应用于字符串处理、自动补全、拼写检查等场景。本文将详细介绍如何实现一个Trie,并提…...

傅里叶变换家族
禹晶、肖创柏、廖庆敏《数字图像处理(面向新工科的电工电子信息基础课程系列教材)》 禹晶、肖创柏、廖庆敏《数字图像处理》资源二维码...

深度学习——强化学习算法介绍
强化学习算法介绍 强化学习讨论的问题是一个智能体(agent) 怎么在一个复杂不确定的环境(environment)里面去极大化它能获得的奖励。 强化学习和监督学习 强化学习有这个试错探索(trial-and-error exploration),它需要通过探索环境来获取对环境的理解。强化学习 ag…...

轴承知识大全,详细介绍(附3D图纸免费下载)
轴承一般由内圈、外圈、滚动体和保持架组成。对于密封轴承,再加上润滑剂和密封圈(或防尘盖)。这就是轴承的全部组成。 根据轴承使用的工作状况来选用不同类型的轴承,才能更好的发挥轴承的功能,并延长轴承的使用寿命。我…...

【PyTorch】基础环境如何打开
前期安装可以基于这个视频,本文是为了给自己存档如何打开pycharm和jupyter notebookPyTorch深度学习快速入门教程(绝对通俗易懂!)【小土堆】_哔哩哔哩_bilibili Pycharm 配置 新建项目的时候选择解释器pytorch-gpu即可。 Jupyte…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)
目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 (1)输入单引号 (2)万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...

水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关
在水泥厂的生产流程中,工业自动化网关起着至关重要的作用,尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关,为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多,其中不少设备采用Devicenet协议。Devicen…...