企业网站用什么cms比较好,网站开发外包 价格,可以做公众号的网站,媒体电商概念前言
项目的 Web 端是 Vue3 框架#xff0c;后端是 GO 框架。需要实现将客户端的本地摄像头媒体流推送至服务端#xff0c;而我自己从未有媒体流相关经验#xff0c;最初 leader 让我尝试通过 RTSP 协议推拉流#xff0c;我的思路就局限在了 RTSP 方向。
最初使用的服务端…前言
项目的 Web 端是 Vue3 框架后端是 GO 框架。需要实现将客户端的本地摄像头媒体流推送至服务端而我自己从未有媒体流相关经验最初 leader 让我尝试通过 RTSP 协议推拉流我的思路就局限在了 RTSP 方向。
最初使用的服务端流媒体处理服务器是RTSPToWeb
GitHub - deepch/RTSPtoWebRTSP 流到 WebBrowser
RTSPtoWeb 可以将RTSP 流转换为可在 Web 浏览器中使用的格式如 MSE媒体源扩展、WebRTC 或 HLS。
我打算在 Web 端将本地摄像头数据流以RTSP协议发送至服务端通过RTSPtoWeb处理为Web可以使用的格式。客户端的推流软件我选择FFmpeg我找到了可以在Vue中使用FFmpeg的方法
FFmpeg——在Vue项目中使用FFmpeg安装、配置、使用、SharedArrayBuffer、跨域隔离、避坑…_vue ffmpeg-CSDN博客
在浏览器中我们是无法直接使用 FFmpeg 软件的但好在有个东西叫FFmpeg.wasm它可以让 FFmpeg 的功能在浏览器中使用。我们在 Vue 项目中使用 FFmpeg.wasm来代替手动输入命令行操作的 FFmpeg 软件。FFmpeg.wasm 是 FFmpeg 的纯 WebAssembly 接口可以在浏览器内录制音频和视频并进行转换和流式传输。但后面实际操作我发现现在FFmpeg.wasm在0.12.0版本之后不再支持 NodeJS
FAQ | ffmpeg.wasm (ffmpegwasm.netlify.app)
但使用 FFmpeg.wasm 旧版本时我遇到好多报错。。。我第一次写前端能力属实不足最后选择放弃了这条思路。。。有能力或者使用的不是 NodeJS 的小伙伴可以用 FFmpeg.wasm 在 Web 推流很方便好用。
后面我又有一个歪点子用 GO 编写从命令行端操作 FFmpeg 推拉流 API 再打包为 exe 可执行文件运行在客户端。但在小组开会后这个方案被毙了。。。因为没有考虑客户需求首先客户在 PC 端访问我们的 Web 不仅需要下载 FFmpeg 现在还得多下载一个 exe 文件其次是考虑客户要在移动端使用。第一次实习第一次做客户项目考虑的没有很全面。
后面我发现为什么不直接用WebRTC呢这可是专门用来解决Web媒体流的好东西
于是我更改了方案将mediamtx作为新的服务器
GitHub - bluenviron/mediamtx: Ready-to-use SRT / WebRTC / RTSP / RTMP / LL-HLS media server and media proxy that allows to read, publish, proxy, record and playback video and audio streams.
mediamtx支持多种协议可以解决很多需求强推
WebRTC简介
搞懂WebRTC 看这一篇就够了-CSDN博客
WebRTC提供了基础的前端功能实现仅仅通过JavaScriptWeb端即可实现点对点的视频流、音频流或者其他数据的传输所用到的知识点如下
WHIP /WHEP 协议
WTN普及一WHIP/WHEP标准信令 - 知乎 (zhihu.com)
WebRTCWebRTC-HTTP Ingestion Protocol通过 WHIP 协议将音视频流从客户端传输到服务器。WHEPWebRTC-HTTPEgressProtocol允许基于浏览器的流媒体内容的低延迟观看。
WHIP /WHEP 不仅仅可以用作流媒体的传输。在未建立 WebRTC 之前通讯双方需要商议彼此的媒体协议也可能无法访问彼此的 IP故我们需要信令服务器传递双方的 SDP 和 candidates 信息而俩个协议在 WebRTC 之上增加了一个简单的信令层解决了这个问题我们可以通过 WebStock 或者 http 向信令层发送信息。
SDP 协议
WebRTC通话原理SDP、STUN、 TURN、 信令服务器_webrtc stun服务器-CSDN博客
通信双方需要发送媒体流而视频和音频都涉及到编码格式故双方需要先协商统一编码格式保证媒体流顺利发送。
SDPSession Description Protocol是一种用于描述多媒体会话的格式。它包含了会话的媒体类型、格式、传输协议和网络信息等。SDP 在 WebRTC 中用于协商音视频通话的各种参数确保两个端点可以兼容并顺利进行通信。
NAT 穿透
NATNetwork Address Translation网络地址交换主要解决 IPv4 地址不够用和安全问题。通过多台主机共用一个公网 IP 地址来减缓 IPv4 地址不够用的问题。使用 NAT 后主机隐藏在内网这样黑客很难访问到内网主机从而达到保护内网主机的目的。NAT 其实就是一种地址映射技术它在内网地址与外网地址之间建立了映射关系。
通讯双方不在一个局域网内则无法访问直接彼此的 IP故需要 NAT 将双方的内网 IP 转换为 公网 IP以便于双方可以互相访问。为实现穿透我们需要用到 ICEInteractive Connectivity Establishment交互式连接创建建立双方的网络连接。
ICE
WebRTC技术文档 – 5.ICE笔记_webrtc ice-CSDN博客
ICE 是一种基于 offer/answer 模式解决 NAT 穿越的协议集合。它结合STUN和TURN协议使客户端无需考虑网络位置和NAT类型即可动态发现最优传输路径。
实现的具体过程为收集网络信息 Candidate、交换 Candidate、按优先级尝试连接。Candidate指可连接的候选者。每个候选者是包含addressIP地址、port端口号、protocol传输协议、CandidateTypeCandidate类型、ufrag用户名等内容的信息集。WebRTC将Candidate分为host、srflx、prflx和relay四类优先级依次由高到低。
STUN / TRUN
WebRTC学习之路—TURN/STUN服务原理及搭建_webrtc 客户端建立连接 stun-CSDN博客
ICE 使用 STUN Binding Request 和 Response来获取公网映射地址和进行连通性检查。客户端向 STUN 服务器发送请求STUN 服务器返回其看到的客户端的公共地址和端口。这样客户就可以告诉其他对等方Peer自己的公共地址以便建立直接连接。 ICE 使用 TURN 协议作为 STUN 的辅助在点对点穿越失败的情况下借助于 TURN 服务的转发功能来实现互通。客户端首先尝试使用 STUN 获取公共地址。如果双方无法通过公共地址直接连接客户端可以将媒体发送到 TURN 服务器由 TURN 服务器转发到对等方。这种方式虽然增加了延迟但可以保证连接的建立。
WebRTC 流程图
WebRTC的建立如下图 在下面代码中Web端client与远端mediamtx 服务器通过HTTP 请求进行交互实现信令。
具体实现
安装运行mediamtx
mediamtx 我们只需要直接下载独立二进制文件运行即可。
下载地址Releases · bluenviron/mediamtx (github.com)
windows 系统下载圈出来的即可解压后里面有一个 exe 文件打开即可 通过WebRTC发送媒体流的示例网址
注意以下项目和mediamtx 都运行在一个 PC 上
mediamtx提供了一个发送媒体流的示例网址的源代码
mediamtx/internal/servers/webrtc/publish_index.html at main · bluenviron/mediamtx · GitHub
URLlocalhost:8889/1/publish
其中1代表的是路径也是后面查询媒体流和保存媒体流的路径示例页面如下 我们看到video device为OBS数据流的默认选项是OBS虚拟摄像头当有外部设备接入如USB摄像头会默认选择为 USB 摄像头设备。video device 还可选择 screen 即本地屏幕推流。
其他的选项依次是视频的编码、波特率、帧率、分辨率和音频的设备、编码、波特率、优化
我接入设备后选项都是默认的 publish 画面如下 mediamtx 的info信息为
WebRTC 创建新的 session
对等连接peer connection成功建立本地Web候选地址和远端mediamtx候选地址
[path 1] 代表录制的路径这里会录制是因为我在mediamtx.yaml 文件中配置了录制其他配置还要保存路径、格式、最大录制时间、录制片段时间和自动删除时间 正在录制音视频轨道Opus 格式的音频轨道AV1 格式的视频轨道。
Vue3中实现WebRTC发送媒体流
根据示例网址的源代码我们可以修改 WebRTC 代码格式如下
HTML 元素
templatedivvideo refvideoElement autoplay playsinline/video/div
/template导包和定义的参数
import { ref } from vue;// 其中1为路径
// whip 用于身份验证
const webrtcUrl http://localhost:8889/1/whip; // 1代表路径可改为你自己的路径
const retryPause 2000;
const videoElement refHTMLVideoElement | null(null);let pc: any null;
let stream: any null;
let restartTimeout: number | null null;
let sessionUrl ;
let offerData: OfferDescription;
let queuedCandidates: RTCIceCandidate[] [];interface OfferDescription {iceUfrag: string; // 唯一标识 sdp 的短字符串icePwd: string; // sdp 对应密码medias: any[]; // 媒体描述编码率等信息
}主函数
const onPublish () {postMessage(connecting);const videoId videoForm.device;const audioId audioForm.device;let videoOpts: { deviceId: string } | boolean false;let audioOpts {deviceId: ,autoGainControl: true, //自动增益控制echoCancellation: true, //启用回声消除noiseSuppression: true, //噪音抑制};if (videoId ! screen) {if (videoId ! none) {videoOpts {deviceId: videoId,};}if (audioId ! none) {audioOpts.deviceId audioId;const voice audioForm.voice;if (!voice) {// 如果没有声音选择则关闭声音audioOpts.autoGainControl false;audioOpts.echoCancellation false;audioOpts.noiseSuppression false;}}navigator.mediaDevices.getUserMedia({video: videoOpts,audio: audioOpts,}).then((str) {stream str;if (videoElement.value) {//将得到的媒体流赋予videoElement显示在 HTML 元素中videoElement.value.srcObject stream; }requestICEServers();}).catch((err) {onError(err.toString(), false);});} else {navigator.mediaDevices.getDisplayMedia({video: {width: { ideal: parseInt(videoForm.width) },height: { ideal: parseInt(videoForm.height) },frameRate: { ideal: parseInt(videoForm.framerate) },},audio: true,}).then((str) {stream str;if (videoElement.value) {videoElement.value.srcObject stream;}requestICEServers();}).catch((err) {onError(err.toString(), false);});}
};Web 端获取STUN 服务器收集本地网络信息candidate通过 ICE 服务器获取 Web 端的公网ip并添加至 candidate
const requestICEServers () {//请求 STUN 服务器fetch(webrtcUrl.value, {method: OPTIONS,}).then((res) {// 通过返回值中的头获取 STUN 服务器信息// STUN 服务器信息在yaml文件中设置// 我在mediamtx.yaml设置 STUN 为 url: stun:stun.l.google.com:19302pc new RTCPeerConnection({iceServers: linkToIceServers(res.headers.get(Link)),});pc.onicecandidate (evt: RTCPeerConnectionIceEvent) onLocalCandidate(evt);pc.oniceconnectionstatechange () onConnectionState();stream.getTracks().forEach((track: any) {pc.addTrack(track, stream);});createOffer();}).catch((err) {onError(err.toString(), true);});
};const linkToIceServers (links: any): any {if (links null) return []; // 检查 links 是否为 nullreturn links.split(, ).map((link: any) {const m link.match(/^(.?); relice-server(; username(.*?); credential(.*?); credential-typepassword)?/i);if (!m) return null; // 如果没有匹配返回 nullconst ret {urls: [m[1]],} as {urls: any[];username?: string;credential?: string;credentialType?: string;};if (m[3] ! undefined) {ret.username unquoteCredential(m[3]);ret.credential unquoteCredential(m[4]);ret.credentialType password;}return ret; // 始终返回 ret}).filter(Boolean); // 筛选掉 null 值
};// 带有引号的凭证字符串解析为 JSON 格式
const unquoteCredential (v: string) JSON.parse(${v});// 监听并收集本地的网络信息 candidate
const onLocalCandidate (evt: any) {if (restartTimeout ! null) {return;}// 检测到新的 candidateif (evt.candidate ! null) {// 代表尚未建立连接if (sessionUrl ) {// 将 candidate 加入队列queuedCandidates.push(evt.candidate);} else {sendLocalCandidates([evt.candidate]);}}
};// 发送 SDP 主要信息和网络信息 candidate 完成WebRTC 建立
const sendLocalCandidates async (candidates: any) {await fetch(sessionUrl, {method: PATCH,headers: {Content-Type: application/trickle-ice-sdpfrag,If-Match: *,},body: generateSdpFragment(offerData, candidates),}).then((res) {if (res.status ! 204) {throw new Error(bad status code ${res.status});}}).catch((err) {onError(err.toString(), true);});
};// 使用 SDP 主要信息和网络信息 candidate生成片段
const generateSdpFragment (od: any, candidates: any) {const candidatesByMedia: any {};for (const candidate of candidates) {const mid candidate.sdpMLineIndex;if (candidatesByMedia[mid] undefined) {candidatesByMedia[mid] [];}candidatesByMedia[mid].push(candidate);}let frag aice-ufrag: od.iceUfrag aice-pwd: od.icePwd
;let mid 0;for (const media of od.medias) {if (candidatesByMedia[mid] ! undefined) {frag m media amid: mid
;for (const candidate of candidatesByMedia[mid]) {frag a candidate.candidate
;}}mid;}return frag;
};Web 端和远端mediamtx交换 SDP
// 创建 SDP 描述本端浏览器支持哪些能力
const createOffer () {pc.createOffer().then((offer: any) {offerData parseOffer(offer.sdp);if (pc) {// offer 设置为本地描述pc.setLocalDescription(offer).then(() {sendOffer(offer.sdp);}).catch((err: any) {onError(err.toString());});}}).catch((err: any) {onError(err.toString());});
};// 解析 SDP 得到 SDP 中的主要信息
const parseOffer (offer: any) {const ret: OfferDescription {iceUfrag: ,icePwd: ,medias: [],};for (const line of offer.split(
)) {if (line.startsWith(m)) {ret.medias.push(line.slice(m.length));} else if (ret.iceUfrag line.startsWith(aice-ufrag:)) {ret.iceUfrag line.slice(aice-ufrag:.length);} else if (ret.icePwd line.startsWith(aice-pwd:)) {ret.icePwd line.slice(aice-pwd:.length);}}return ret;
};// 发送 SDP 到远端mediamtx
const sendOffer async (offer: any) {console.log(sendOffer, offer);offer editOffer(offer);await fetch(webrtcUrl.value ?video-device${videoForm.device},{method: POST,headers: {Content-Type: application/sdp,},body: offer,}).then((res) {switch (res.status) {case 201:break;case 400:return res.json().then((e) {throw new Error(e.error);});default:throw new Error(bad status code ${res.status});}const locationHeader res.headers.get(location);if (!locationHeader) {throw new Error(Location header is missing);}sessionUrl new URL(locationHeader, http://localhost:8889).toString();return res.text().then((answer) onRemoteAnswer(answer));}).catch((err) {onError(err.toString(), true);});
};const editOffer (sdp: any) {console.log(editOffer, sdp);const sections sdp.split(m);console.log(sections, sections);for (let i 0; i sections.length; i) {if (sections[i].startsWith(video)) {// 设置 SDP 中 vedio 的编码率 sections[i] setCodec(sections[i], videoForm.codec);} else if (sections[i].startsWith(audio)) {// 设置 SDP 中 audio 的编码率和波特率sections[i] setAudioBitrate(setCodec(sections[i], audioForm.codec),audioForm.bitrate,audioForm.voice);}}return sections.join(m);
};// // 接受远端 SDP信息的 Answer
const onRemoteAnswer (sdp: string) {if (restartTimeout ! null) {return;}sdp editAnswer(sdp);// 保存远端 SDP信息的 Answerpc.setRemoteDescription(new RTCSessionDescription({type: answer,sdp,})).then(() {if (queuedCandidates.length ! 0) {sendLocalCandidates(queuedCandidates);queuedCandidates [];}}).catch((err: any) {onError(err.toString());});
};const editAnswer (sdp: any) {const sections sdp.split(m);for (let i 0; i sections.length; i) {if (sections[i].startsWith(video)) {sections[i] setVideoBitrate(sections[i], videoForm.bitrate);}}return sections.join(m);
};设置 vedio 和 audio 编码格式
// 设置 video 波特率
const setVideoBitrate (section: any, bitrate: any) {let lines section.split(
);for (let i 0; i lines.length; i) {if (lines[i].startsWith(c)) {lines [...lines.slice(0, i 1),bTIAS: (parseInt(bitrate) * 1024).toString(),...lines.slice(i 1),];break;}}return lines.join(
);
};//设置编码格式
const setCodec (section: any, codec: any) {const lines section.split(
);const lines2 [];const payloadFormats [];for (const line of lines) {if (!line.startsWith(artpmap:)) {lines2.push(line);} else {if (line.toLowerCase().includes(codec)) {payloadFormats.push(line.slice(artpmap:.length).split( )[0]);lines2.push(line);}}}const lines3 [];let firstLine true;for (const line of lines2) {if (firstLine) {firstLine false;lines3.push(line.split( ).slice(0, 3).concat(payloadFormats).join( ));} else if (line.startsWith(afmtp:)) {if (payloadFormats.includes(line.slice(afmtp:.length).split( )[0])) {lines3.push(line);}} else if (line.startsWith(artcp-fb:)) {if (payloadFormats.includes(line.slice(artcp-fb:.length).split( )[0])) {lines3.push(line);}} else {lines3.push(line);}}return lines3.join(
);
};const setAudioBitrate (section: string, bitrate: string, voice: any) {let opusPayloadFormat ;let lines section.split(
);for (let i 0; i lines.length; i) {if (lines[i].startsWith(artpmap:) lines[i].toLowerCase().includes(opus/)) {opusPayloadFormat lines[i].slice(artpmap:.length).split( )[0];break;}}if (opusPayloadFormat ) {return section;}for (let i 0; i lines.length; i) {if (lines[i].startsWith(afmtp: opusPayloadFormat )) {if (voice) {lines[i] afmtp: opusPayloadFormat minptime10;useinbandfec1;maxaveragebitrate (parseInt(bitrate) * 1024).toString();} else {lines[i] afmtp: opusPayloadFormat maxplaybackrate48000;stereo1;sprop-stereo1;maxaveragebitrate (parseInt(bitrate) * 1024).toString();}}}return lines.join(
);
};错误处理函数
const onError (err: string, retry?: boolean) {if (!retry) {console.error(err:, err);} else {if (restartTimeout null) {console.error(err , retrying in some seconds);if (pc ! null) {pc.close();pc null;}restartTimeout window.setTimeout(() {restartTimeout null;startTransmit();}, retryPause);if (sessionUrl) {fetch(sessionUrl, {method: DELETE,});}sessionUrl ;// 清空 STUN 服务器候选队列queuedCandidates [];}}
};注意
关于 vedio 设置
const videoForm {device: , // 设备IDnonescreen屏幕空值默认为外部设备若没有则为OBS虚拟设备codec: h264/90000, // 编解码器格式有bitrate: 10000, // 比特率framerate: 30, // 帧率width: 1920,height: 1080,
};例如其中 codec 的设置为h264/90000其中90000是时钟频率用于时间戳的单位它表示每秒钟可以产生90000个时间单位用于确保视频流和音频流的同步。若设置为 h264 则会导致发送的 SDP 中缺少编码协议导致 WebRTC 建立失败。
搜集到的网络信息candidates
host候选
candidate:1799829579 1 udp 2122260223 10.102.24.113 51222 typ host generation 0 ufrag 1Phf network-id 1
10.102.24.113 是我电脑内WSL虚拟网络适配器的IP
acandidate:66318701 1 udp 2122194687 192.168.64.1 51223 typ host generation 0 ufrag 1Phf network-id 2
192.168.64.1 电脑以太网适配器的地址
这些是主机候选表示的是客户端本地网络中的IP地址如10.102.24.113和192.168.64.1。这些地址通常是私有IP地址无法被公网直接访问。
添加STUN/TRUN
srflx候选
acandidate:2861133569 1 udp 1686052607 221.xx.xx.xxx 51222 typ srflx raddr 10.102.24.113 rport 51222 generation 0 ufrag 1Phf network-id 1
这个候选是通过STUN服务器获取的反射候选srflx显示外部的可路由地址即公网IP在这个例子中为221.xx.xx.xxx。这意味着 STUN 服务器成功返回了一个公网 IP 地址。
结果
当 mediamtx 反馈下面 info即代表 WebRTC 连接和传输媒体流成功 这样媒体流就可以保存在 mediamtx 服务器上了。服务器上查询、转发媒体流等方法均可以在手册中获取。
GitHub - bluenviron/mediamtx: Ready-to-use SRT / WebRTC / RTSP / RTMP / LL-HLS media server and media proxy that allows to read, publish, proxy, record and playback video and audio streams.
菜鸟第一次写文章对自己项目中用到的模块通过查阅和学习完成自己的见解如果可以帮助到你请帮忙点点赞。可能有用词不当和错误的地方请大家斧正感谢阅读 文章转载自: http://www.morning.wfbs.cn.gov.cn.wfbs.cn http://www.morning.mhlkc.cn.gov.cn.mhlkc.cn http://www.morning.bwhcl.cn.gov.cn.bwhcl.cn http://www.morning.rmtmk.cn.gov.cn.rmtmk.cn http://www.morning.zpyxl.cn.gov.cn.zpyxl.cn http://www.morning.dydqh.cn.gov.cn.dydqh.cn http://www.morning.lxfqc.cn.gov.cn.lxfqc.cn http://www.morning.hqzmz.cn.gov.cn.hqzmz.cn http://www.morning.djmdk.cn.gov.cn.djmdk.cn http://www.morning.kfyjh.cn.gov.cn.kfyjh.cn http://www.morning.zdgp.cn.gov.cn.zdgp.cn http://www.morning.jwqqd.cn.gov.cn.jwqqd.cn http://www.morning.kdpal.cn.gov.cn.kdpal.cn http://www.morning.hbtarq.com.gov.cn.hbtarq.com http://www.morning.tgpgx.cn.gov.cn.tgpgx.cn http://www.morning.tzcr.cn.gov.cn.tzcr.cn http://www.morning.wkknm.cn.gov.cn.wkknm.cn http://www.morning.rwbx.cn.gov.cn.rwbx.cn http://www.morning.kjcll.cn.gov.cn.kjcll.cn http://www.morning.gbsby.cn.gov.cn.gbsby.cn http://www.morning.pffx.cn.gov.cn.pffx.cn http://www.morning.lwsct.cn.gov.cn.lwsct.cn http://www.morning.xnymt.cn.gov.cn.xnymt.cn http://www.morning.hbqfh.cn.gov.cn.hbqfh.cn http://www.morning.dblfl.cn.gov.cn.dblfl.cn http://www.morning.nqmwk.cn.gov.cn.nqmwk.cn http://www.morning.qfrsm.cn.gov.cn.qfrsm.cn http://www.morning.jqwpw.cn.gov.cn.jqwpw.cn http://www.morning.fykqh.cn.gov.cn.fykqh.cn http://www.morning.yllym.cn.gov.cn.yllym.cn http://www.morning.xsgxp.cn.gov.cn.xsgxp.cn http://www.morning.pcqdf.cn.gov.cn.pcqdf.cn http://www.morning.fbdtd.cn.gov.cn.fbdtd.cn http://www.morning.snrbl.cn.gov.cn.snrbl.cn http://www.morning.qrgfw.cn.gov.cn.qrgfw.cn http://www.morning.bnpcq.cn.gov.cn.bnpcq.cn http://www.morning.ddrdt.cn.gov.cn.ddrdt.cn http://www.morning.yqhdy.cn.gov.cn.yqhdy.cn http://www.morning.lonlie.com.gov.cn.lonlie.com http://www.morning.qwzpd.cn.gov.cn.qwzpd.cn http://www.morning.pwmm.cn.gov.cn.pwmm.cn http://www.morning.cyfsl.cn.gov.cn.cyfsl.cn http://www.morning.yaqi6.com.gov.cn.yaqi6.com http://www.morning.yxlhz.cn.gov.cn.yxlhz.cn http://www.morning.fnmtc.cn.gov.cn.fnmtc.cn http://www.morning.yggwn.cn.gov.cn.yggwn.cn http://www.morning.zyrcf.cn.gov.cn.zyrcf.cn http://www.morning.bztzm.cn.gov.cn.bztzm.cn http://www.morning.mxbks.cn.gov.cn.mxbks.cn http://www.morning.zqcdl.cn.gov.cn.zqcdl.cn http://www.morning.fhbhr.cn.gov.cn.fhbhr.cn http://www.morning.fyglr.cn.gov.cn.fyglr.cn http://www.morning.wnhsw.cn.gov.cn.wnhsw.cn http://www.morning.ubpsa.cn.gov.cn.ubpsa.cn http://www.morning.gwsll.cn.gov.cn.gwsll.cn http://www.morning.zxfdq.cn.gov.cn.zxfdq.cn http://www.morning.hmbxd.cn.gov.cn.hmbxd.cn http://www.morning.kpbgp.cn.gov.cn.kpbgp.cn http://www.morning.rdzlh.cn.gov.cn.rdzlh.cn http://www.morning.gqnll.cn.gov.cn.gqnll.cn http://www.morning.gassnw.com.gov.cn.gassnw.com http://www.morning.thlzt.cn.gov.cn.thlzt.cn http://www.morning.kwqwp.cn.gov.cn.kwqwp.cn http://www.morning.jqsyp.cn.gov.cn.jqsyp.cn http://www.morning.mgzjz.cn.gov.cn.mgzjz.cn http://www.morning.xjwtq.cn.gov.cn.xjwtq.cn http://www.morning.tpps.cn.gov.cn.tpps.cn http://www.morning.nrqtk.cn.gov.cn.nrqtk.cn http://www.morning.bcnsl.cn.gov.cn.bcnsl.cn http://www.morning.bmrqz.cn.gov.cn.bmrqz.cn http://www.morning.snxbf.cn.gov.cn.snxbf.cn http://www.morning.mtgkq.cn.gov.cn.mtgkq.cn http://www.morning.4q9h.cn.gov.cn.4q9h.cn http://www.morning.rjhts.cn.gov.cn.rjhts.cn http://www.morning.cfrz.cn.gov.cn.cfrz.cn http://www.morning.yrdn.cn.gov.cn.yrdn.cn http://www.morning.nwynx.cn.gov.cn.nwynx.cn http://www.morning.rgwrl.cn.gov.cn.rgwrl.cn http://www.morning.smpb.cn.gov.cn.smpb.cn http://www.morning.rbcw.cn.gov.cn.rbcw.cn