流媒体视频基础 MSE 入门 & FFmpeg 制作视频预览缩略图和 fmp4

如果检查主流视频网站的视频,就会发现网站的 video 元素的 src 属性都是 blob 开头的字符串。

为什么视频链接前面会有 blob 前缀?这是因为视频网站使用了这篇文章要讲的 MSE 来播放视频。

Media Source Extensions

虽然 video 很强大,但是还有很多功能 video 并不支持,比如直播,即时切换视频清晰度,动态更新音频语言等功能。

MSE(Media Source Extensions)就来解决这些问题,它是 W3C 的一种规范,如今大部分浏览器都支持。

它使用 video 标签加 JS 来实现这些复杂的功能。它将 videosrc 设置为 MediaSource 对象,然后通过 HTTP 请求获取数据,然后传给 MeidaSource 中的 SourceBuffer 来实现视频播放。

如何将 MediaSourcevideo 元素连接呢?这就需要用到 URL.createObjectURL 它会创建一个 DOMString 表示指定的 File 对象或 Blob(二进制大对象) 对象。这个 URL 的生命周期和创建它的窗口中的 document 绑定。

const video = document.querySelector('video')
const mediaSource = new MediaSource()

mediaSource.addEventListener('sourceopen', ({ target }) => {
    URL.revokeObjectURL(video.src)
    const mime = 'video/webm; codecs="vorbis, vp8"'
    const sourceBuffer = target.addSourceBuffer(mime) // target 就是 mediaSource
    fetch('/static/media/flower.webm')
        .then(response => response.arrayBuffer())
        .then(arrayBuffer => {
            sourceBuffer.addEventListener('updateend', () => {
                if (!sourceBuffer.updating && target.readyState === 'open') {
                    target.endOfStream()
                    video.play()
                }
            })
            sourceBuffer.appendBuffer(arrayBuffer)
        })
})

video.src = URL.createObjectURL(mediaSource)
复制代码

addSourceBuffer 方法会根据给定的 MIME 类型创建一个新的 SourceBuffer 对象,然后会将它追加到 MediaSourceSourceBuffers 列表中。

我们需要传入相关具体的编解码器(codecs)字符串,这里第一个是音频(vorbis),第二个是视频(vp8),两个位置也可以互换,知道了具体的编解码器浏览器就无需下载具体数据就知道当前类型是否支持,如果不支持该方法就会抛出 NotSupportedError 错误。更多关于媒体类型 MIME 编解码器可以参考 RFC 4281

这里还在一开始就调用了 revokeObjectURL。这并不会破坏任何对象,可以在 MediaSource 连接到 video 后随时调用。 它允许浏览器在适当的时候进行垃圾回收。

视频并没有直接推送到 MediaSource 中,而是 SourceBuffer,一个 MeidaSource 中有一个或多个 SourceBuffer。每个都与一种内容类型关联,可能是视频、音频、视频和音频等。

视频格式

HTML5 标准指定时,想指定一种视频格式作为标准的一部分,所有浏览器都必须实现。但是对于 H.264 视频编码各个厂商产生的争论,主要是 H.264 非常强大(高画质、高压缩比、成熟的编解码器…),但是它也要高昂的授权费。Mozilla 这类免费浏览器,并没有从其开发的浏览器上获得直接收入,但是让 H.264 加入标准,它就要支付相应的授权费,所有认为是不可接受的。于是后来放弃了视频格式指定的统一,浏览器厂商可以自由选择支持的格式。

不过现在所有主流浏览器都支持 H.264 编码格式的视频,所有选择视频编码时优先选择 H.264 编码。

MediaSource API

MediaSource 属性

属性描述
sourceBuffers返回包含 MediaSource 所有 SourceBufferSourceBufferList 对象
activeSourceBuffers上个属性的子集,返回当前选择的视频轨道,启用的音频规定和显示或隐藏的文本轨道
duration获取或者设置当前媒体展示的时长,负数或 NaN 时抛出 InvalidAccessError
readyState 不为 openSourceBuffer.updating 属性为 true 时抛出 InvalidStateError
readyState表示 MediaSource 的当前状态

readyState 有以下值:

  • closed 未附着到一个 media 元素上
  • open 已附着到一个 media 元素并准备好接收 SourceBuffer 对象
  • ended 已附着到一个 media 元素,但流已被 MediaSource.endOfStream() 结束

MediaSource 方法

方法描述
addSourceBuffer(mime)根据给定 MIME 类型创建一个新的 SourceBuffer 对象,将它追加到 MediaSource 的 SourceBuffers 列表中
removeSourceBuffer(sourceBuffer)移除 MediaSource 中指定的 SourceBuffer。如果不存在则抛出 NotFoundError 异常
endOfStream(endOfStreamError)向 MediaSource 发送结束信号,接收一个 DOMString 参数,表示到达流末尾时将抛出的错误
setLiveSeekableRange(start, end)设置用户可以跳跃的视频范围,参数是以秒单位的 Double 类型,负数等非法参数会抛出异常
clearLiveSeekableRange清除上次设置的 LiveSeekableRange

其中 addSourceBuffer 可能会抛出一下错误:

错误描述
InvalidAccessError提交的 mimeType 是一个空字符串
InvalidStateErrorMediaSource.readyState 的值不等于 open
NotSupportedError当前浏览器不支持的 mimeType
QuotaExceededError浏览器不能再处理 SourceBuffer 对象

endOfStream 的参数可能是如下两种字符串:

  • network 终止播放并发出网络错误信号
  • decode 终止播放并发出解码错误信号

MediaSource.readyState 不等于 open 或有 SourceBuffer.updating 等于 true,调用 endOfStream 会抛出 InvalidStateError 异常。

它还有一个静态方法

静态方法描述
isTypeSupported(mime)是否支持指定的 mime 类型,返回 true 表示可能支持并不能保证

MediaSource 事件

错误描述
sourceopenreadyStateclosedendedopen
sourceendedreadyStateopenended
sourceclosereadyStateopenendedclosed

SourceBuffer API

SourceBuffer 通过 MediaSource 将一块块媒体数据传递给 meida 元素播放。

SourceBuffer 属性

属性描述
mode控制处理媒体片段序列,segments 片段时间戳决定播放顺序,sequence 添加顺序决定播放顺序,
MediaSource.addSourceBuffer() 中设置初始值,如果媒体片段有时间戳设置为 segments,否则 sequence
自己设置时只能从 segments 设置为 sequence,不能反过来。
updating是否正在更新,比如 appendBuffer()remove() 方法还在处理中
buffered返回当前缓冲的 TimeRanges 对象
timestampOffset控制媒体片段的时间戳偏移量,默认是 0
audioTracks返回当前包含的 AudioTrack 的 AudioTrackList 对象
videoTracks返回当前包含的 VideoTrack 的 VideoTrackList 对象
textTracks返回当前包含的 TextTrack 的 TextTrackList 对象
appendWindowStart设置或获取 append window 的开始时间戳
appendWindowEnd设置或获取 append window 的结束时间戳

append window 是一个时间戳范围来过滤 append 的编码帧。在范围内的编码编码帧允许添加到 SourceBuffer,之外的会被过滤。

SourceBuffer 方法

方法描述
appendBuffer(source)添加媒体数据片段(ArrayBufferArrayBufferView)到 SourceBuffer
abort中断当前片段,重置段解析器,可以让 updating 变成 false
remove(start, end)移除指定范围的媒体数据

SourceBuffer 事件

方法描述
updatestartupdatingfalse 变为 true
updateappendremove 已经成功完成,updatingtrue 变为 false
updateendappendremove 已经结束,在 update 之后触发
errorappend 时发生了错误,updatingtrue 变为 false
abortappendremoveabort() 方法中断,updatingtrue 变为 false

注意事项

  1. 几乎所有 MediaSource 方法调用或设置属性,在 MediaSource.readyState 不是 open 时会抛出 InvalidStateError 错误,应该在调用方法或设置属性前查看当前状态,即使是在事件回调中,因为可能在回调执行之前改变了状态。此外 endOfStream 方法还会因为 SourceBufferupdatingtrue 时也抛出该异常
  2. 在调用 SourceBuffer 方法或设置属性时,应该检查 SourceBuffer 是否为 false
  3. MediaSource.readyState 的值是 ended 时,调用 appendBuffer()remove() 或设置 modetimestampOffset 时,将会让 readyState 变为 open,并触发 sourceopen 事件,所有应该要有处理多个 sourceopen 事件准备

Initialization Segment

如果随便找一个 mp4 文件,使用上面那个例子播放,就会发现播放不了。这是因为 SourceBuffer 接收两种类型的数据:

  1. Initialization Segment 视频的初始片段,其中包含媒体片段序列进行解码所需的所有初始化信息。
  2. Media Segment 包含一部分媒体时间轴的打包和带时间戳的媒体数据。

MSE 需要使用 fmp4 (fragmented MP4) 格式,MP4 文件使用面向对象格式其中包含 Boxes (或叫 Atoms),可以使用 这个网站 查看 Mp4 文件信息。

这是一个普通的 MP4 文件,可以看到它有一个很大的 mdat (电影数据)box

这是 fragmented MP4 的截图,ISO BMFF 初始化段定义为单个文件类型框(File Type Box ftyp)后跟单个电影标题框(Movie Header Box moov),更多信息可以查看 ISO BMFF Byte Stream Format

moov 只包含一些视频基础的信息(类型,编码器等),moof 存放样本位置和大小,moof 框后都有一个 mdat,其中包含如前面的 moof 框中所述的样本。

要查看当前视频是不是 fmp4,就可以看 ftyp 后面是不是跟着 moov,然后是 moof mdat 对就行了。

要将普通 MP4 转换成 FMP4 可以下载 Bento4。然后用命令行输入如下命令就可以了。

mp4fragment ./friday.mp4 ./friday.fmp4
复制代码

Bento4 /bin 目录中有非常多好用的 mp4 工具,/utils 目录中都是 python 实用脚本。

工具

除了上面介绍的 Bento4,还有很多其他好用的工具。有了下面的工具,就可以快速制作 MSE 实践的视频素材了。

chrome://media-internals/

chrome://media-internals/ 是 chrome 浏览器用来调试多媒体的工具,直接在地址栏输入该值就可以。

Shaka Packager

Shaka Packager 是 Google 出的一个小巧视频工具,它只有 5M 左右,它可以用来查看视频信息,分离音频和视频,还支持 HLS 和 DASH。

它的命令行使用格式如下:

packager stream_descriptor[,stream_descriptor] [flags]
复制代码

比如用它查看视频信息:

packager in=friday.mp4 --dump_stream_info # 查看视频信息

File "friday.mp4":
Found 2 stream(s).
Stream [0] type: Audio
 codec_string: mp4a.40.2 # 音频的编码器字符串
 time_scale: 44100
 duration: 271360 (6.2 seconds)
 is_encrypted: false
 codec: AAC
 sample_bits: 16
 num_channels: 2
 sampling_frequency: 44100
 language: und

Stream [1] type: Video
 codec_string: avc1.4d401e # 视频的编码器字符串
 time_scale: 3000
 duration: 18500 (6.2 seconds)
 is_encrypted: false
 codec: H264
 width: 640
 height: 480
 pixel_aspect_ratio: 1:1
 trick_play_factor: 0
 nalu_length_size: 4
复制代码

更多请查看官方文档

FFmpeg

FFmpeg 是功能非常强大的视频处理开源软件,很多视频播放器就是使用它来做为内核。后面文章的实例都会使用这个工具。

比如上面将普通 MP4 转换为 FMP4,可以使用如下命令:

ffmpeg -i ./friday.mp4 -movflags empty_moov+frag_keyframe+default_base_moof f.mp4
复制代码

它的命令行格式如下:

ffmpeg [global_options] {[input_file_options] -i input_url} ... {[output_file_options] output_url} ...
复制代码

它可以有不限数量的输入和输出文件,-i 后面是输入 url,后面不能解析为参数的为输出文件。

 _______              ______________
|       |            |              |
| input |  demuxer   | encoded data |   decoder
| file  | ---------> | packets      | -----+
|_______|            |______________|      |
                                           v
                                       _________
                                      |         |
                                      | decoded |
                                      | frames  |
                                      |_________|
 ________             ______________       |
|        |           |              |      |
| output | <-------- | encoded data | <----+
| file   |   muxer   | packets      |   encoder
|________|           |______________|
复制代码

它将输入文件的容器解开,然后对里面的数据进行解码,然后按照指定的格式进行编码,然后使用指定的容器进行封装生成输出文件。在 decoded frames 后 FFmpeg 可以使用 filter 进行处理,比如添加滤镜、旋转、锐化等操作,filter 分为简单和复杂,复杂可以处理多个输入流。

如果我们只是想改变视频的容器,那么就可以省略解码和编码过程,来提升速度。

ffmpeg -i input.avi -c copy output.mp4
复制代码

-c 是指定编码器,-c copy 表示直接复制编码,-c:v 表示视频编码,-c:a 表示音频编码,比如 -c:v libx264 表示使用 CPU 将视频编码为 h.264-c:v h264_nvenc 则是使用 N卡,这样速度更快。-c:a copy 表示直接复制音频编码。

ffmpeg -y -i myvideo.mp4 -c:v copy -an myvideo_video.mp4
ffmpeg -y -i myvideo.mp4 -c:a copy -vn myvideo_audio.m4a
复制代码

-an 去除音频流 -vn 去除视频流。-y 是不经过确认,输出时直接覆盖同名文件。

ffmpeg -help #查看帮助
ffmpeg -i input.mp4 # 查看视频信息
ffmpeg -formats # 查看支持的容器
ffmpeg -codecs # 查看支持的编码格式
ffmpeg -encoders # 查看内置的编码器
复制代码

更多关于 FFmpeg 请查看官方文档

视频缩略图预览

了解了上面好用的工具,就来用 FFmpeg 来实现一个视频播放器小功能吧。

现在视频网站,当鼠标放到进度条上时就会出现,一个小缩略图来预览这个时间点内容。

ffmpeg -i ./test.webm -vf 'fps=1/10:round=zero:start_time=-9,scale=160x90,tile=5x5' M%d.jpg
复制代码

我们可以通过上面这个命令生成一个雪碧图,由 25 张 160×90 预览图组成。

-vf 参数后面跟着过滤器,多个过滤器用 , 分开,一个过滤器多个参数使用 : 分开。

fps=1/10 表示 10 秒输出一张图,fps=1/60 为一分钟一张,round=zero 时间戳向 0 取整,start_time=-9 是因为 fps 是每多少秒生成一张,并不是从 0 秒开始 -9 是让它从 1 秒开始截取,忽略掉 0 秒的黑屏帧。

scale=160x90 设置输出图像分辨率大小,tile=5x5 将小图用 5×5 的方式组合在一起,M%d.jpg 表示输出为 jpg,而且文件是 M1.jpg M2.jpg... 这样递增。

如果想用 NodeJS,可以用 node-fluent-ffmpeg 的 thumbnails 方法来生成。

有了雪碧图,我们就在上篇文章实现的播放器的基础上在加个视频缩略图功能。主要通过 css 的 background 来实现。

const thumb = document.querySelector('.thumb')
const gapSec = 10 // 一张图片显示几秒
const images = [...] // 图片
const row = 5, col = 5 // 一张图有几行几列
const width = 160, height = 90; // 缩略图的宽高
const thumbQuantityPerImg = col * row

function updateThumbnail(seconds) { // 传入要显示缩略图的秒数
    const thumbNum = (seconds / gapSec) | 0; // 当前是第几张缩略图
    const url = images[(thumbNum / thumbQuantityPerImg) | 0];
    const x = (thumbNum % col) * width; // x 的偏移量
    const y = ~~((thumbNum % thumbQuantityPerImg) / row) * height; // y 的偏移量

    thumb.style.backgroundImage = `url(${url})`;
    thumb.style.backgroundPosition = `-${x}px -${y}px`;
}
复制代码

效果如下

image.png

这里只展示缩略图更新的逻辑,忽略了时间显示,事件处理和样式等代码。

完整的代码请参考 NPlayer

在线演示:nplayer.js.org/

视频切片

有了 MSE 我们就可以将一个视频分割成多个小视频,然后可以自己控制缓存进度来节省流量,还可以将视频压缩成不同的分辨率,在用户网不好的情况动态加载码率低的分段,分成多段还可以实现插入广告,动态切换音频语言等功能。

./audio/
  ├── ./128kbps/
  |     ├── segment0.mp4
  |     ├── segment1.mp4
  |     └── segment2.mp4
  └── ./320kbps/
        ├── segment0.mp4
        ├── segment1.mp4
        └── segment2.mp4
./video/
  ├── ./240p/
  |     ├── segment0.mp4
  |     ├── segment1.mp4
  |     └── segment2.mp4
  └── ./720p/
        ├── segment0.mp4
        ├── segment1.mp4
        └── segment2.mp4
复制代码

当然也可以不切割视频,而使用 HTTP Range 来范围请求数据。

ffmpeg -i ./friday.mp4 -f segment -segment_time 2 -segment_format_options movflags=dash ff%04d.mp4
复制代码

我们使用上面命令将一个视频切成 2 秒的 fmp4 视频片段。

window.MediaSource = window.MediaSource || window.WebKitMediaSource
const video = document.querySelector('video')
const mime = 'video/mp4; codecs="avc1.4d401e, mp4a.40.2"'
const segments = [
    '/static/media/ff0000.5b66d30e.mp4', 
    '/static/media/ff0001.89895c46.mp4', 
    '/static/media/ff0002.44cfe1e4.mp4'
]
const segmentData = []
let currentSegment = 0

if ('MediaSource' in window && MediaSource.isTypeSupported(mime)) {
    const ms = new MediaSource()
    video.src = URL.createObjectURL(ms)
    mediaSource.addEventListener('sourceopen', sourceOpen)
}

function sourceOpen({ target }) {
    URL.revokeObjectURL(video.src)
    target.removeEventListener('sourceopen', sourceOpen)
    const sb = target.addSourceBuffer(mime)
    fetchSegment(ab => sb.appendBuffer(ab))
    sb.addEventListener('updateend', () => {
        if (!sb.updating && segmentData.length ) {
            sb.appendBuffer(segmentData.shift())
        }
    })
    video.addEventListener('timeupdate', function timeupdate() {
        if (
            currentSegment > segments.length &&
            !sb.updating &&
            target.readyState === 'open'
        ) { // 所有片段全部加载完成
            target.endOfStream()
            video.removeEventListener('timeupdate', timeupdate)
        } else if (
            video.currentTime > (currentSegment * 2 * 0.8) 
            // 一个片段时长 2 秒
            // 在一个片段播放到 80% 的时候才去请求下一个片段
        ) {
            fetchSegment(ab => {
                if (sb.updating) {
                    segmentData.push(ab)
                } else {
                    sb.appendBuffer(ab)
                }
            })
        }
    })
    video.addEventListener('canplay', () => {
        video.play()
    })
}

function fetchSegment(cb) {
    fetch(segments[currentSegment])
        .then(response => response.arrayBuffer())
        .then(arrayBuffer => {
            currentSegment += 1
            cb(arrayBuffer)
        })
}
复制代码

这个实例非常简单,并没有处理 seek,自适应码率等复杂功能。

总结

现在视频网站几乎全部都在使用 MSE 来播放视频。使用 MSE 有提供更好的用户体验,更加节约成本等好处。虽然视频播放一般使用 hls dash 等协议的开源客户端来播放视频,我们自己不会使用到 MSE,但这些客户端底层都是使用 MSE,了解 MSE 才更了解这些客户端。

这是弹幕播放器系列文章第二篇,上篇请查看 从零开发弹幕视频播放器1。下篇请查看 原来爱优腾等视频网站都是用这个来播放流媒体的

作者:wopen
链接:https://juejin.cn/post/6953777965838630926

正向代理、反向代理、透明代理

代理:它位于用户客户端和服务端之间,顾名思义它是一个中间商,代替客户端向服务端请求资源,并把服务端响应的资源传递给客户端而不用客户端自己去访问。

根据所处位置的不同分为正向代理、反向代理

一、正向代理:

  正向代理主要是代替客户端请求和接受互联网资源,比如当我们直接访问youtube的时候,你知道结果肯定是要扑街的,这时候可以去找个代理,通过代理我们就能访问了。这时候我们把代理服务看着是客户端。

  正向代理的用途 :

(1)访问本地无法访问的服务器;

(2)缓存作用,加速访问资源;

(3)客户端访问授权,进行上网认证;

(4)代理会记录访问记录,对外可以隐藏访问者的行踪。

 二、反向代理:

   它位于服务端,主要是将用户的web请求分发到后面的真实服务器上(负载均衡),并将真实服务器返回的结果交给用户,此时代理服务器位于服务端,对外就表现为一个服务器。

图2反向代理

反向代理的作用:

(1)保证服务端内部网路的安全,大型网站,通常将反向代理作为公网访问地址,Web服务器是内网。

(2)负载均衡,通过反向代理服务器来优化网站的负载,降低服务器压力。

三、透明代理:

   透明代理的意思是客户端根本不需要知道代理服务器的存在,它改变你的request fields(报文),并会传送真实IP,多用于路由器的NAT转发中。

透明代理也称为强制代理、内联代理或拦截代理。与常规代理相反,透明代理不需要对现有设置进行任何更改。它在浏览互联网时在用户不知情的情况下实施。

透明代理不会操纵请求或更改您的IP。它执行重定向并可用于身份验证。透明代理充当由ISP实现的缓存代理。

透明代理常用于缓存某些高带宽资源。例如,视频将被缓存到代理服务器,当请求查看该视频时,将呈现缓存副本而不向目标服务器发送请求。这是为了有效地使用带宽并缩短响应时间。

组织使用透明代理来限制对特定网站的访问。这用于审查内容。透明代理可以根据特定规则阻止对Internet的访问。此类代理服务器也用于防止DDoS攻击。它可以防止此类冗余请求泛滥并关闭实际的目标服务器。

可以在路由器刷OpenWRT安装shadowsocks使用透明代理+去DNS污染

拥有透明代理的缺点之一是用户控制权被透明代理带走,而用户没有意识到这一点。这是一个隐私问题。其次,透明代理不健壮,容易受到中间人攻击。网络流量可以重新路由到不同的代理。

透明代理的主要问题是可以监控您的所有流量。ISP 可以完全跟踪您的网络跟踪。如果您不希望被监控,第一步是检测您是否使用透明代理。一种常见的方法是在 Chrome 地址栏中输入无效的 URL。未找到网站时来自 Chrome 的典型错误消息与使用透明代理连接时显示的错误消息不同。

如果您正在浏览HTTPS网站,您可以通过单击浏览器上的锁定符号来查看SSL证书信息。当它来自 ISP 时,此证书看起来会有所不同。

Nmap 等命令也用于检测透明代理。

通过PHP脚本自动登录WP

WordPress的密码不记得了怎么办?除了通过邮件找回密码、登录phpmyadmin修改密码以外,还可以通过PHP脚本自动登录Wordpress。比如创建一个autologin.php放到wp根目录下,然后访问这个文件,就自动用指定的用户名登录站点,随后删除该文件即可。

为什么要自动登录WordPress

  • 知道网站管理的用户名以及cpanel信息,不知道密码,也不能修改用户的密码。
  • 本地开发图省事,不想每次都要登录站点。

自动登录Wordpress的PHP脚本

将如下内容写入一个文件,名字叫什么都可以,比如autologin.php,放到网站根目录下。

<?php
require('wp-blog-header.php');
// Automatic login //
$username = "admin";
$user = get_user_by('login', $username );

// Redirect URL //
if ( !is_wp_error( $user ) )
{
    wp_clear_auth_cookie();
    wp_set_current_user ( $user->ID );
    wp_set_auth_cookie  ( $user->ID );

    $redirect_to = user_admin_url();
    wp_safe_redirect( $redirect_to );
    exit();
}

然后打开浏览器,输入该文件地址,例如:

https://www.solagirl.net/autologin.php

这样就能用指定的用户名自动登录WordPress了,在线站点上请勿保留此文件。

让Wordpress时刻保持登录状态

如果要让WordPress不论何时都自动登录,可以用下面的代码,代码写成插件或者放到主题的functions.php里。

function auto_login() {

    if( is_user_logged_in() ){
        return;
    }
    $loginusername = 'admin'; //username of the WordPress user account to impersonate

    // get this username's ID
    $user = get_user_by( 'login', $loginusername );

    if( ! $user ){
        return;
    }

    $user_id = $user->ID;

    // login as this user
    wp_set_current_user( $user_id, $loginusername );
    wp_set_auth_cookie( $user_id );
    do_action( 'wp_login', $loginusername, $user );

    // redirect to home page after logging in
    wp_redirect( home_url() );
    exit;
}

add_action( 'wp', 'auto_login', 1 );

这段代码的作用:只要访问站点,就会自动登录。适合某些特殊用途,比如本地站点测试不想重复登录。

国家-防火墙实现原理

不同ISP建立的不同骨干网之间也有数据交换中心,使得信息和数据包可以自由的从全国任何地方流向其他地方。
而数据包在庞大的“网上公路”迷路的可能性。在各个级别的网路(局域网->域域网->广域网)中,分布着无数路由节点,每一张骨干网都有自己负责的路由群组和节点,整个群组统称为as自治系统(Autonomous system)

每一个骨干网管理的as自治系统都经过名为互联网号码分配局的国际机构分配唯一识别代码,例如,中国电信163骨干网的as自治系统编号为AS4134。每一张骨干网都有内部路由协议,每一个节点都在依据某种规定互相交换他们所连通的ip地址信息,作为数据包在“旅行”过程中的指路人。而全国性的骨干网之间也依靠外部路由协议互相交换它们所掌握的“服务器地图“,典型的有BGP协议。

边界网关协议(BGP)是运行于 TCP 上的一种自治系统的路由协议。 BGP 是唯一一个用来处理像因特网大小的网络的协议,也是唯一能够妥善处理好不相关路由域间的多路连接的协议。 BGP 构建在 EGP 的经验之上。 BGP 系统的主要功能是和其他的 BGP 系统交换网络可达信息。网络可达信息包括列出的自治系统(AS)的信息。这些信息有效地构造了 AS 互联的拓扑图并由此清除了路由环路,同时在 AS 级别上可实施策略决策。

国家-防火墙原理

1.基于UDP协议的域名解析服务劫持/DNS缓存污染
GFW会对所有经过骨干出口路由的基于UDP的DNS域名查询请求进行Intrusion Detection Systems(入侵检测系统)检测,一旦发现处于黑名单关键词中相匹配的域名查询请求,防火长城作为中间设备会向查询者返回虚假结果。
也就是说浏览器无法查询到域名对应的正确IP,也就无法对其进行访问,数据包会传输到虚假的IP,从而没有响应。

2.IP地址或传输层端口人工封锁——BGP路由劫持/“路由黑洞”
GFW的重要工作方式之一是在网络层的针对IP的封锁。事实上,GFW采用的是一种比传统的访问控制列表(Access Control List,ACL)高效得多的控制访问方式——路由扩散技术。分析这种新的技术之前先看看传统的技术,并介绍几个概念。

访问控制列表(ACL)

ACL可以工作在网络的二层(链路层)或是三层(网络层),以工作在三层的ACL为例,基本原理如下:想在某个路由器上用ACL控制(比如说是切断)对某个IP地址的访问,那么只要把这个IP地址通过配置加入到ACL中,并且针对这个IP地址规定一个控制动作,比如说最简单的丢弃。当有报文经过这个路由器的时候,在转发报文之前首先对ACL进行匹配,若这个报文的目的IP地址存在于ACL中,那么根据之前ACL中针对该IP地址定义的控制动作进行操作,比如丢弃掉这个报文。这样通过ACL就可以切断对于这个IP的访问。ACL同样也可以针对报文的源地址进行控制。如果ACL工作在二层的话,那么ACL控制的对象就从三层的IP地址变成二层的MAC地址。从ACL的工作原理可以看出来,ACL是在正常报文转发的流程中插入了一个匹配ACL的操作,这肯定会影响到报文转发的效率,如果需要控制的IP地址比较多,则ACL列表会更长,匹配ACL的时间也更长,那么报文的转发效率会更低,这对于一些骨干路由器来讲是不可忍受的。

路由协议与路由重分发

而GFW的网络管控方法是利用了OSPF等路由协议的路由重分发redistribution)功能,可以说是“歪用”了这个本来是正常的功能。

动态路由协议

说路由重分发之前先简单介绍下动态路由协议。正常情况下路由器上各种路由协议如OSPF、IS-IS、BGP等,各自计算并维护自己的路由表,所有的协议生成的路由条目最终汇总到一个路由管理模块。对于某一个目的IP地址,各种路由协议都可以计算出一条路由。但是具体报文转发的时候使用哪个协议计算出来的路由,则由路由管理模块根据一定的算法和原则进行选择,最终选择出来一条路由,作为实际使用的路由条目。

静态路由

相对于由动态路由协议计算出来的动态路由条目,还有一种路由不是由路由协议计算出来的,而是由管理员手工配置下去的,这就是所谓的静态路由。这种路由条目优先级最高,存在静态路由的情况下路由管理模块会优先选择静态路由,而不是路由协议计算出来的动态路由。

路由重分发

刚才说到正常情况下各个路由协议是只维护自己的路由。但是在某些情况下比如有两个AS(自治系统),AS内使用的都是OSPF协议,而AS之间的OSPF不能互通,那么两个AS之间的路由也就无法互通。为了让两个AS之间互通,那么要在两个AS之间运行一个域间路由协议BGP,通过配置,使得两个AS内由OSPF计算出来的路由,能通过BGP在两者之间重分发。BGP会把两个AS内部的路由互相通告给对方AS,两个AS就实现了路由互通。这种情况就是通过BGP协议重分发OSPF协议的路由条目。

另外一种情况,管理员在某个路由器上配置了一条静态路由,但是这条静态路由只能在这台路由器上起作用。如果也想让它在其他的路由器上起作用,最笨的办法是在每个路由器上都手动配置一条静态路由,这很麻烦。更好的方式是让OSPF或是IS-IS等动态路由协议来重分发这条静态路由,这样通过动态路由协议就把这条静态路由重分发到了其他路由器上,省去了逐个路由器手工配置的麻烦。

GFW路由扩散技术的工作原理

前面说了是“歪用”,正常的情况下静态路由是由管理员根据网络拓扑或是基于其他目的而给出的一条路由,这条路由最起码要是正确的,可以引导路由器把报文转发到正确的目的地。而GFW的路由扩散技术中使用的静态路由其实是一条错误的路由,而且是有意配置错误的。其目的就是为了把本来是发往某个IP地址的报文统统引导到一个“黑洞服务器”上,而不是把它们转发到正确目的地。这个黑洞服务器上可以什么也不做,这样报文就被无声无息地丢掉了。更多地,可以在服务器上对这些报文进行分析和统计,获取更多的信息,甚至可以做一个虚假的回应。

有了这种新的方法,以前配置在ACL里的每条IP地址就可以转换成一条故意配置错误的静态路由信息。这条静态路由信息会把相应的IP报文引导到黑洞服务器上,通过动态路由协议的路由重分发功能,这些错误的路由信息可以发布到整个网络。这样对于路由器来讲现在只是在根据这条路由条目做一个常规报文转发动作,无需再进行ACL匹配,与以前的老方法相比,大大提高了报文的转发效率。而路由器的这个常规转发动作,却是把报文转发到了黑洞路由器上,这样既提高了效率,又达到了控制报文之目的,手段更为高明。

这种技术在正常的网络运营当中是不会采用的,错误的路由信息会扰乱网络。正常的网络运营和管控体系的需求差别很大,管控体系需要屏蔽的IP地址会越来越多。正常的网络运营中的ACL条目一般是固定的,变动不大、数量少,不会对转发造成太大的影响。而这种技术直接频繁修改骨干路由表,一旦出现问题,将会造成骨干网络故障。

所以说GFW是歪用了路由扩散技术,正常情况下没有那个运营商会把一条错误的路由信息到处扩散,这完全是歪脑筋。或者相对于正常的网络运营来说,GFW对路由扩散技术的应用是一种小聪明的做法。正常的路由协议功能被滥用至此,而且非常之实用与高效,兲朝在这方面真是人才济济。

3.TCP RST重置
旁路监听的方式一般是将主交换机的数据镜像到控制系统,控制系统可以采用libpcap捕获数据包。在这种情况下要阻断tcp连接的建立只要在监听到第一次握手的时候,控制系统伪造服务器发起第二次握手回应,就能阻断客户端与服务器连接的建立。因为我们的系统在内网,发出的报文肯定比服务器快,这样客户端接收到我们伪造的报文以后会回应第三次握手,当服务器真正的报文到达的时候客户端将不再处理,此时客户端再向服务器请求数据,因为seq号和ack号出错,服务器不会受理客户端的请求。

4.协议检测→根据流量协议拆包→关键词匹配→封锁
HTTP协议有非常明显的特征,可以轻易被GFW系统检测和识别,GFW进而依据HTTP协议规则对数据包进行拆解,由于其表现为明文,所以可以直接进行关键词匹配。例如,从HTTP的GET请求中取得请求的URL。然后GFW拿到这个请求的URL去与关键字做匹配,比如查找Twitter是否在请求的URL中。而关键字匹配使用的依旧是一些高效的正则表达式算。

5.深度包检测(机器学习识别翻墙流量→直接阻断)
对于混淆流量和非传统加密协议,GFW正在使用大家耳熟能详的“人工智能”技术,将这些各种各样难以判断和识别的翻墙流量与正规的政企跨境流量相区分开来。

以上为网友分析,非专业资料

光纤通信网络安全

目前,光纤窃听的方法主要包括光纤弯曲法、V 型槽切口法、散射法、光束分离法、渐近耦合法等

1、直接切断光缆,接入光耦合器,(光分束器),使目标信号分为两个完全相同的信号

2、光纤弯曲在5至10MM可正确导光,用夹式耦合器不破坏光纤就可,

3、V 型槽切口法是通过一个接近纤心的V 型槽导出光纤信号进行窃听的方法。它要求V 型槽的切面与光纤信号传输方向之间的夹角大于完全反射的临界角。当达到这个条件后,在保护层中传输的部分信号和在V 型槽切面发生迭加效应的信号发生完全反射,导致信号通过光纤边界泄露。

  由于这种窃听方法导致的信号衰减很小,因此很难被发现。V 型槽切口法需要精确的切割和切面抛光设备,窃听部署需要持续较长时间,因此,光纤保护层的切割和抛光过程将面临被发现的危险。

4、散射法是采用光纤Bragg 光栅技术实现的一种隐蔽窃听方法,它采用一个紫外光激态激光器产生紫外光的迭加并影响目标光纤信号,通过在目标光纤纤心形成的Bragg 光栅反射出的一部分光信号实现对目标光纤的隐蔽窃听,是目前最先进的光纤窃听技术,常规的网络检测和监控手段都很难识别这种窃听行为。散射法不需要对光纤进行弯曲、切割或抛光,但是它需要更精密的窃听设备并且部署非常困难,比如产生有效的外部干扰干涉光束,并在目标光纤纤心产生光栅耀斑都需要精密的控制技术,而对于光栅耀斑反射出的光信号的检测也需要精密的检测技术。

5、渐近耦合法,渐近耦合法首先抛光光纤的保护层,使窃听光纤纤心尽可能贴近目标光纤纤心,通过减少保护层的反射引出部分信号到窃听光纤里面,由于光纤纤心非常细,实施这种方法非常困难,又由于光纤的保护层被抛光将产生1~2 dB 的光纤损耗,因此很难实现隐蔽的窃听。

  以上几种窃听光纤信号的方法都可以通过一些技术手段得到光纤信号,特别是光纤弯曲法、V 型槽切口法,能够实现隐蔽窃听,又由于实施相关窃听相对容易一些,因此具有较高的实战应用价值。

如何检测是否有窃听:

用光测试仪测试光衰,用光时域反射仪来看光纤中的断点,变形等。

光时域反射仪(OTDR)

  OTDR 的原理是通过精确地发射各种波长的有规律的光脉冲并测量反射光信号返回的时间和反射光信号的强度来分析光纤信道情况。通过跟踪反射光信号的时间和强度,OTDR 能够确定光环路的完整路径。另外,OTDR还可以识别光纤断路的距离。通过测试和保存OTDR 的参数,终端用户可以监控光路的变化并识别任何可能的光路入侵。由于OTDR( 包括偏振OTDR) 能够识别不连续的损耗,可以检测双折射、压力和其他由窃听引起的光信号变形等,因此,具有检测光纤断裂、弯曲、异常损耗和各种窃听等异常情况的能力。

另外可以从技术上防止光纤被窃听而造成的敏感信息泄露。比如加密,目前,比较实用的光纤信号保护手段主要有无规律载波光纤通信技术、基于混沌保密的光纤通信方式和光纤信道加密技术等。使用这些技术,可以在一定程度上增强光纤信号的保密性,这种信息保护方式对于业余窃听爱好者来说可能是一筹莫展,但是它对具有超强计算和分析能力的专业窃听机构并不能提供完全的保密性。

阿里云打开mysql远程连接

先进阿里云控制台,安全组规则打开请允许入方向的3306端口TCP连接

然后再进操作系统

解决mysql不能使用IP链接的问题

1) 修改%xampp%\mysql\bin\my.ini文件,搜索

bind-address=“127.0.0.1”

改成

bind-address=“0.0.0.0”

2) 修改%xampp%\phpMyAdmin\config.inc.php文件,搜索:

/* Authentication type and info */

$cfg[‘Servers’][$i][‘auth_type’] =‘config’;

$cfg[‘Servers’][$i][‘user’] =‘root’;

$cfg[‘Servers’][$i][‘password’] =”;

$cfg[‘Servers’][$i][‘extension’] =‘mysql’;

$cfg[‘Servers’][$i][‘AllowNoPassword’] = true;

改成:

/* Authentication type and info */

$cfg[‘Servers’][$i][‘auth_type’] =‘cookie’;

$cfg[‘Servers’][$i][‘user’] =‘root’;

$cfg[‘Servers’][$i][‘password’] =‘root’;

$cfg[‘Servers’][$i][‘extension’] =‘mysql’;

$cfg[‘Servers’][$i][‘AllowNoPassword’] = true;

3) 启动mysql、apache,登录phpMyAdmin。在“权限”部分添加一个root@%用户,内容参照 root@localhost 的设置;两者的区别就是主机字段,一个写%,一个写localhost;

xampp下mysql开启远端连接

1、进入phpmyadmin管理界面首页,点击“权限”后,再“添加新用户”,用户名:root(随意),主机:%(必须是%),对应的密码可以设置可以不设置,即可开通远程连接;2、或者用工具Navicat链接到本地数据,点击“管理用户”,后添加用户,同上操作即可开通远端连接。

关于房地产的思考-20220804

现在很多房地产陷入困境,归因在前期杠杆放得太大,且预售制度的盛行让地产商以十之一的资金就敢上项目,大量的建筑商等其它行业进来掘金,特别是4,5线城市新开盘太多,现在银行对房产贷款收紧,销售又下滑使预售回款也不顺利,所以出现恒大等的困局,在国家政策一再的放松下,民众还是在观望,以至销售本月还在下滑,

展望后市,国家应会对预售制度进行改革,提高项目自有资金量,门槛,这样会淘汰部分地产商,减少新建房,但同时会提高开发商的建房资金成本,新建少加资金成本更高可能提高房价。有效稳住房价,良性循环,

对4,5线城市来说,容积率高的小高层优质使用寿命可能在20年以下。如在房产税不出的情况下,消费升级下别墅更有入手价值,

沿海5省经济占全国1/3上,财政收入占近4成,地方对中央财政净上缴中贡献近8成

从2021年地区生产总值(GDP)总量来看,广东是全国经济第一大省,高达12.44万亿元;江苏为11.64万亿元,排名第二。山东、浙江则在7万亿元以上,河南、四川、湖北都在5万亿元以上,另有福建、湖南、上海、安徽、河北、北京的GDP都超过4万亿元。

 图为2021年全国各省(市)GDP与财政收入概况 

  因此,从数据上看,广东、江苏、山东、浙江、上海、北京等可视作经济大省(市)。此外,GDP排名前十的河南、四川、湖北、福建、湖南等省份也可视作广义的经济大省。

  东南沿海5省市经济体量占全国1/3以上,财政收入占比近4成,在地方对中央财政净上缴中贡献近8成,有力支撑了国家财力和中央财政对中西部地区转移支付。

  2021年的数据显示,在对中央财政的税收贡献中,全国仅8个省市有净贡献,其中就包括东南沿海5省市,其2021年合计净上缴财政收入超过3万亿元,成为中央财政转移支付体系的重要支撑。

人力资源路径演进升级版-学校

侄子因成绩不好,家在农村,所以初中毕业,高中不收,入了一所职业学校,学习船务,目标是以后上船,首先交学费,在当地学两年,然后给拉到深圳,以实习的名义,其实是到工厂打螺丝,还赚一笔介绍费,然后毕业证拉到,告之考英语,过不了只能走内航运,工资低一大截,再告之上海海事大学好不容易给了5个名额,可以到海事大学学习,学费先给个4万5吧。