WebRTC 的前世今生

本文由 rwebrtc 翻译

WebRTC 技术是激烈的开放的 Web 战争中一大突破。-Brendan Eich, inventor of JavaScript

无插件实时通信

想象一下手机、TV 和电脑都通过统一平台进行沟通。试想一下,很容易的在你的网站中添加视频聊天和 P2P 数据分享。这是 WebRTC 技术的愿景。

想试一试吗?WebRT C在 Chrome、Opera 和 Firefox 中就可以使用。在 apprtc.appspot.com 中可以试一试这些例子:

  1. 在 Chrome、Opera 或 Firefox 中打开 apprtc.appspot.com。
  2. 点击允许按钮让这个应用使用你的摄像头( Web 应用程序)。
  3. 在一个新的标签页中打开底部的 URL,最好在另外的电脑上打开。

在这篇 文章 中有一个这个应用的例子。

快速开始

没有时间阅读这篇文章,或者只想尽快写代码?

  1. 看一看关于 Google I/O 大会上关于 WebRTC 的概述。(幻灯片在 这里 )
  2. 如果你没有用过 getUserMedia,看一看项目中的这篇 文章,在 这里 可以看到示例代码。
  3. 了解一下 RTCPeerConnection 的 API,点击 这里 和 这里 ,这个例子在一个简单的界面上实现了 WebRTC。
  4. 了解更多关于 WebRTC 使用的服务器、防火墙和 NAT 穿透相关的内容。读 这里 看调试日志。
  5. 等不及了想试试 WebRTC 吗?这里有 20 多个 Demos,试试这些 javaScript 的 APIs。
  6. 使用你的机器有问题吗? 这里 可以找到测试用例。

或者,直接跳到我们的 WebRTC codelab:一步一步的跟着指导,构建一个完整的视频聊天应用程序,包括一个简单的信令服务器。

WebRTC 技术简史

让人类通过网络进行音视频通信是网络最后的巨大挑战:实时通信( RTC )。实时通信就像网络上在文本框中输入文本一样自然,没有它,就限制了我们开发新的方式使人们互动交流起来。

从历史上看,RTC 变化很大很复杂,需要昂贵的音视频技术授权或者花费巨大代价去开发,RTC 技术与现有的内容、数据和服务整合一直都很困难和耗时,在网络上尤其如此。

Gmail 视频聊天在 2008 年开始流行,在 2011 年 Google 推出视频群聊,它使用 GoogleTalk 服务,就像 Gmail 一样。Google 收购了 GIPS,它是一个为 RTC 开发出许多组件的一个公司,例如编解码和回声消除技术。Google 开源了 GIPS 开发的技术,与相关机构 IET 和 W3C 制定行业标准。在 2011 年 5 月,爱立信实现第一 个 WebRTC应用

WebRTC 已经实现了对于实时通信,免插件音频数据传输的标准制定。需求是:
- 许多网络服务已经使用了 RTC,但是需要下载,本地应用或者是插件。包括 Skype、Facebook、Google Hangouts。
- 下载安装升级插件是复杂的,可能出错的,令人厌烦的。
- 插件可能很难部署、调试、故障排除等——可能需要技术授权,复杂集成和昂贵的技术。说服人们去安装插件是很难的。

WebRTC 项目的指导原则是APIs应该是开源的,免费的,标准化的,浏览器内置的,比现有技术更高效的。

我们在哪

WebRTC 应用在了各种 App 上,包括 WhatsApp、Facebook Manager、appear.in 和 TokBox 平台上。甚至在 iOS 浏览器上的实验 WebRTC。WebRTC 也被 WebKitGTK+ 和 QT 内置使用。

微软在 Edge 中增加了 MediaCapture 和 Stream API。

WebRTC 实现了三个 APIs:

getUserMedia 在 Chrome、Opera、FireFox 和 Edge 中都实现了。看一看跨浏览器的 Demo 和亚马逊的 例子,使用 getUserMedia 作为音频的输入。

RTCPeerConnection 在 Chrome、Opera 和 FireFox 中实现。关于名字的解释:经过几次迭代,RTCPeerConnection 被 Chrome 和 Opera 实现为 webkitRTCPeerConnection,被 FireFox 实现为 mozRTCPeerConnection。其他的名字都是过时的,当标准稳定时,前缀会被删除。在Github 上有超级简单的演示项目,构建在 apprtc.appspot.com 上。这个应用使用 adapter.ja,一个 JS 库,来帮助 WebRTC 跨浏览器。

RTCDataChannel 被 Chrome、Opera 和 FireFox 支持。检出 Github 上的 Demo 看看怎么使用。

警告

有些平台宣称支持 WebRTC 其实可能仅仅支持 getUserMedia,而不支持其他的 RTC 组件。

我的第一个 WebRTC

WebRTC 需要做几件事:

  • 获取音视频流或其他数据流。
  • 获取网络信息例如 IP 地址和端口号,与其他客户端交换这些信息来进行连接,包括穿透防火墙。
  • 向 signaling 报告错误或启动会话关闭连接等。
  • 对于分辨率解码器等与其他客户端交换信息。
  • 传输音视频或其他数据信息。

为了获得和传输数据,WebRTC 实现了以下 APIs:

  • MediaStream:从客户摄像头或麦克风获取数据流。
  • RTCPeerConnection:音视频通话,包括加密和带宽等的管理。
  • RTCDataChannel:P2P 数据传输管道。

(接下来详细讨论 WebRTC 的网络和信道)

MediaStream(aka getUserMedia)

MediaStream API 代表同步流媒体。例如一个来自摄像头和麦克风的流媒体输入已经同步了音视频。(不要混淆 MediaStream 和 track 标签,它们完全不同)。

也许理解 MediaStream 的容易的方式是看它的表现:

  1. 在 Chrome、Opera 打开 demo:
    https://webrtc.github.io/samples/src/content/getusermedia/gum。
  2. 打开调试器。
  3. 检查全局范围内的流变量。

每个 MediaStream 有一个输入,navigator.getUserMedia(),也包括一个输出,输出到 video 标签或者是 RTCPeerConnection。

getUserMedia() 方法有三个参数:

  • 一个约束对象。
  • 成功回调,如果被调用,传递一个 MediaStream。
  • 失败回调,如果被调用,传递一个错误对象。

每个 MediaStream 有一个标签,例如
"as'Xk7EuLhsuHKbnjLWkW4yYGNJJ8ONsgwHBvLQ" 
一个 MediaStreamTracks 数组被 getAudioTracks() 和 getVideoTracks() 返回。

对于
https://webrtc.github.io/samples/src/content/getusermedia/gum/ 
例如 stream.getAudioTracks() 返回一个空的数组,因为没有音频,假设一个摄像头被连接上了,stream.getVideoTracks() 返回一个 MediaStreamTrack 数组,代表这个摄像头的流。每个 MediaStreamTrack 有一个类型(音频或视频),一个标签(有时就像 FaceTime HD 照相机),表示一个或多个音视频通道。在这个例子中,只有视频没有音频。很容易想象更多的例子:例如,一个聊天应用,前置想象头后置摄像头和麦克风,屏幕共享应用。

在 Chrome 或 Opera 中,URL.createObjectURL() 方法可以转换一个 MediaStream 到一个 Blog URL,可以被设置作为视频的源。(在 FireFox 和 Opera 中,这个视频源可以通过 stream 本身创建),自从 M25 版本,基于 Chromium 的浏览器( Chrome 和 Opera )允许通过 getUserMedia 获取的音频数据放置到 audio 和 video 标签上(但是注意到默认情况下将时静音状态)。

getUserMedia 可以被作为 网页音频输入API :

function gotStream(stream) {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext = new AudioContext(); // Create an AudioNode from the stream
var mediaStreamSource = audioContext.createMediaStreamSource(stream); // Connect it to destination to hear yourself
// or any other node for processing!
mediaStreamSource.connect(audioContext.destination);
} navigator.getUserMedia({audio:true}, gotStream);

基于 Chromium 的应用或扩展也可以使用 getUserMedia 。在安装的时候仅一次添加 audioCapture 和 / 或 videoCapture 权限。此后用户不被询问关于摄像头和麦克风的权限。

对于使用 HTTPS 的网页也是同样:对于 getUserMedia 只需呀授予一次权限。第一次时,在标签上信息栏上显示允许按钮。

TODO 同时,Chrome 将不鼓励 getUserMedia() 访问 http,在 2015 年末,在 M44 版本,你可能在访问 HTTP 时看到一个警告。

最终,不止照相机和麦克风,其他的任何数据流都可以放到 MediaStream 中。可以从硬盘中获取数据流,或者从任何其他传感器或其他输入获取数据。

注意到 getUserMedia() 必须在服务器上使用,不可以在本地文件中使用。否则会报错。

getUserMedia() 可以与其他 javaScript 类库一起使用:

  • Webcam Toy 是一个照片类 APP,使用 WebGL 来给照片添加奇怪的效果,可以被分享和保存到本地。
  • FaceKat 是一个脸部跟踪的游戏,使用了 headtrackr.js。
  • ASCII Camera 使用了 Ganvas API 来生成 ASCII 照片。

约束

已经被 Chrome、FireFox 和 Opera 实现。这些可以被用来设置由 getUserMedia() 和 RTCPeerConnection addStream() 获取到的视频分辨率。这个目的是实现其他约束,例如面对模式(前后摄像头),帧速率,高度和宽度,使用的是 applyconstraints() 方法。


https://webrtc.github.io/samples/src/content/getusermedia/resolution/ 
有一个例子。

一个难以解决的问题:浏览器中,一个标签中 getUserMedia 设置的约束影响随后打开的标签页。设置不允许的值时给出了一个错误信息:

navigator.getUserMedia error:
NavigatorUserMediaError {code: 1, PERMISSION_DENIED: 1}

屏幕和标签捕获

Chrome 应用也使得分享一个 video 标签在一个单一的浏览器标签中成为可能,或者整个桌面通过 chrome.tabCapture 和 chrome.desktopCapture 的 APIs 。在 这里 可以找到一个桌面 capture 的例子。对于截屏视频,代码和更多的信息查看 这里

TODO 使用 csreen capture 作为一个 MediaStream 在 chrome 的源也是可能的,其中使用了 chromeMediaSource 约束,看这里 Demo ,注意到屏幕抓取需要 HTTPS 并且应该用命令行标记,被 WebRTC 讨论解释。

Signaling:会话控制,网络和媒体信息

WebRTC 使用 RTCPeerConnection 在浏览器间传递数据流,但是也需要一个机器协调沟通发送控制 i 信息,这被称为 signaling 过程。Signaling 方法和协议没有被 WebRTC 规定:signaling 不是 RTCPeerConnection 的 API 的一部分。

相反,WebRTC 应用的开发者可以选择任何一种他们喜欢的消息协议,例如 SIP 或者是 XMPP,或者任何全双工的通信通道。这个 apprtc.appspot.com 例子使用了 XHR 和 Channel API 作为这个 Signaling。这个 Codelab 使用了 Socket.io,是一个 Node服务器

Signaling 被使用来交换三种信息:

  • 连接控制信息:初始化或者关闭连接报告错误。
  • 网络配置:对于外网,我们电脑的 IP 地址和端口?
  • 多媒体数据:使用什么编码解码器,浏览器可以处理什么信息?

在进行 P2P 数据传输前,这些信息必须全部通过 Signaling 进行交换。

例如,假设 Alice 想要与 Bob 通信。这是这个 例子 ,显示了这个过程中 Signaling 的信息。这个代码假设某些信号的存在,在 createSignaling() 方法中创建。也注意到有在 Chrome 和 Opera 上有前缀。

var signalingChannel = createSignalingChannel();
var pc;
var configuration = ...; // run start(true) to initiate a call
function start(isCaller) {
pc = new RTCPeerConnection(configuration); // send any ice candidates to the other peer
pc.onicecandidate = function (evt) {
signalingChannel.send(JSON.stringify({ "candidate": evt.candidate }));
}; // once remote stream arrives, show it in the remote video element
pc.onaddstream = function (evt) {
remoteView.src = URL.createObjectURL(evt.stream);
}; // get the local stream, show it in the local video element and send it
navigator.getUserMedia({ "audio": true, "video": true }, function (stream) {
selfView.src = URL.createObjectURL(stream);
pc.addStream(stream); if (isCaller)
pc.createOffer(gotDescription);
else
pc.createAnswer(pc.remoteDescription, gotDescription); function gotDescription(desc) {
pc.setLocalDescription(desc);
signalingChannel.send(JSON.stringify({ "sdp": desc }));
}
});
} signalingChannel.onmessage = function (evt) {
if (!pc)
start(false); var signal = JSON.parse(evt.data);
if (signal.sdp)
pc.setRemoteDescription(new RTCSessionDescription(signal.sdp));
else
pc.addIceCandidate(new RTCIceCandidate(signal.candidate));
};

首先,Alice 和 Bob 交换网络信息。("finding candidates" 指的是使用 ICE 框架寻找网络端口)

  1. Alice 使用 onicecandidate 句柄创建一个 RTCPeerConnection。
  2. 当网络处理程序可用时,句柄运行。
  3. Alice 向 Bob 发送序列化数据,通过无论那种方式:WebSocket 或者其他方式。
  4. 当 Bob 从 Alice 获取到数据后,调用 addIceCandidate 添加远程节点描述中。

WebRTC 客户端(被称为 peers,例如 Alice 和 Bob ),也需要交换本地和远程音视频媒体信息,例如使用的协议和编码器。TODO Signaling 来交换信息。

  1. Alice 执行 RTCPeerConnection 的 createOffer() 方法,这通过 RTCSessionDescription 回调:Alice 本地会话描述。
  2. 在这个回调中,Alice 通过 setLocationDescription() 设置本地描述然后给 Bob 发送阶段描述通过他们的信道。注意到 RTCPeerConnection 直到 setRemoteDescription 被调用才会开始发送数据:在 这里 被确定。
  3. Bob 将 Alice 发送过来的消息进行设置作为远程信息的描述,通过 setRemoteDescription() 方法。
  4. Bob 运行 RTCPeerConnection 的 createAnswer() 的方法,通过从 Alice 获取到的描述信息,本地会话可以适配她的。这个 createAnswer() 回调被一个 RTCSessionDescription 回调:Bob 设置本地描述,发送给 Alice。
  5. 当 Alice 获取到了 Bob 的本地描述,通过 setRemoteDescription 设置这个描述信息。
  6. 开始通信。

RTCSessionDescription 对象符合 SDP,一个 SDP 类似于如下:
```
v=0
o=- 3883943731 1 IN IP4 127.0.0.1
s=
t=0 0
a=group:BUNDLE audio video
m=audio 1 RTP/SAVPF 103 104 0 8 106 105 13 126

// ...

a=ssrc:2223794119 label:H4fjnMzxy3dPIgQ7HxuCTLb4wLLLeRHnFxh810
```

网络和媒体信息的采集和交换可以同时进行,但这两个过程必须完成之前,音频和视频流之间的同龄人可以开始。

网络信息的采集和交换可以同时进行,但是在发送音频和视频前都需要完成。。

这个 offer/andwer 被叫做 JSEP (JavaScript Session Establishment Protocol),在[这里]有一个很好的 WebRTC 实现的解释。

一旦这个 signaling 完成了,数据可以直接的在端到端之间进行数据传输。如果失败了,通过中介服务器 relay 服务进行转发。Streaming 是 RTCPeerConnection 的工作。

RTCPeerConnection

RTCPeerConnection 是 WebRTC 的一部分,它是稳定的有效率的端到端传输数据的句柄。

下面是一个 WebRTC 的架构,显示了 RTCPeerConnection 的作用,正如你看到的,绿色部分和复杂!

从 JavaScript 观点看,从图表中主要需要理解 RTCPeerConnection 向开发者屏蔽了底层复杂的东西。WebRTC 使用的协议和解码器做了大量的工作才使得实时通信成为了可能,甚至在不可靠的网络的情况下:

  • 数据包丢包隐藏
  • 回声消除
  • 宽带自适应
  • 动态抖动缓冲
  • TODO 自动增益控制
  • 降噪
  • 图片清除

在 W3C代码 从 signaling 观点显示了一个简化的例子。下面是两个使用 WebRTC 工作的两个应用,第一个是一个简单的 RTCPeerConnection 的例子;第二个时一个完全的可操作的视频客户端。

没有服务器的 RTCPeerConnection

下面的代码是从 这里 的 Demo 。包括网页上本地和远程的 RTCPeerConnection 。这个没有什么用,呼叫和被呼叫都在同一个网页上,但是确实使得理解 RTCPeerConnection 的 API 更清楚了,由于这个界面的 RTCPeerConnection 在没有 Signaling 时也可以直接交换信息。

其他问题:TODO 看 http://www.w3.org/TR/webrtc/#constraints 获取更多信息。

在这里例子中,pc1 代表本地,pc2 代表远程。

本地

  1. 创建一个新的 RTCPeerConnection 添加 getUserMedia 获取到的数据流。
// servers is an optional config file (see TURN and STUN discussion below)
pc1 = new webkitRTCPeerConnection(servers);
// ...
pc1.addStream(localStream);
  1. 创建一个 offer 并且设置本地的关于 pc1 的描述作为和远程 pc2 的描述。这在没有使用 signaling 的情况下可以直接完成,因为本地和远程都在一个界面上。
pc1.createOffer(gotDescription1);
//...
function gotDescription1(desc){
pc1.setLocalDescription(desc);
trace("Offer from pc1 \n" + desc.sdp);
pc2.setRemoteDescription(desc);
pc2.createAnswer(gotDescription2);
}

远程

创建 pc2,添加 pc1 的流,在视频节点中显示:

pc2 = new webkitRTCPeerConnection(servers);
pc2.onaddstream = gotRemoteStream;
//...
function gotRemoteStream(e){
vid2.src = URL.createObjectURL(e.stream);
}

RTCPeerConnection 和服务器

在真实的世界中,WebRTC 需要服务器,但是时很简单的,下面的可能是现实的:

  • 通过用户名可以找到对方。
  • WebRTC 客户端交换网络信息。
  • 端到端间交换媒体数据信息,例如分辨率和视频格式。
  • WebRTC 客户端进行 NAT 和防火墙穿透。

换一句话说,WebRTC 需要四中服务类型的信息。

  • 用户发现和通信。
  • Signaling。
  • NAT/防火墙穿透。
  • 当端到端通信失败时,采用 Relay 服务通信。

NAT 穿透、端到端连接、用户发现和建立信令服务器超过了本文范畴。我想说的是,STUN 协议和它的延伸 ICE 冰框架可以使用 RTCPeerConnection 处理 NAT 和其他网络变化。

ICE 是一个连接端到端的框架,例如视频客户端。最初,ICE 尝试直接连接,通过采用 UDP 实现更少延迟,在这个过程中,STUN 服务有一个简单的任务:使一个在防火墙后面的设备找出公网 IP 和端口。Google 有两个 STUN 服务器,其中一个在 apprtc.appspot.com 个例子中就使用了。

如果 UDP 失败了,ICE 尝试 TCP:首先是 HTTP,然后是 HTTPS 。如果直接连接失败了——特别的是因为企业的 NAT 和防火墙—— ICE 使用一个中继 relay 的 TURN 服务器。换句话说,ICE 首先使用 UDP 的 STUN 服务器直接与对端连接,如果失败了,后退到 TURN 服务器。这表达了寻找网络和端口的过程。

WebRTC 工程师 Jastin 提供了关于 ICE、STUN 和 TURN 的更多的信息在 2013 年 Google 的 IO 大会上。(幻灯片 中有关于 TURN 和 STUN 的例子)

一个简单的 Chat 客户端

下面使用的信令服务器使用的是:https://apprtc.appspot.com

如果你觉得这是有点令人困惑,你可能更喜欢我们的 WebRTC codelab 。这篇指南介绍了如何一步步构建一个完整的视频聊天应用,包括使用 Socket.io 搭建一个简单的信令服务器。

apprtc.appspot.com 是一个尝试 WebRTC 的好地方,这里使用了 Signaling 和 NAT/firewall 作为 STUN 服务器。这个应用使用 adapter.js 处理不同的 RTCPeerConnection 和 getUserMedia() 的实现。

这个代码是故意写的更长一点输出日志:查看控制台理解事件执行顺序。下面给出了一个详细的代码。

接下来

这个 Demo以initialize() 函数开始:

function initialize() {
console.log("Initializing; room=99688636.");
card = document.getElementById("card");
localVideo = document.getElementById("localVideo");
miniVideo = document.getElementById("miniVideo");
remoteVideo = document.getElementById("remoteVideo");
resetStatus();
openChannel('AHRlWrqvgCpvbd9B-Gl5vZ2F1BlpwFv0xBUwRgLF/* ...*/');
doGetUserMedia();
}

注意被 openChannel() 使用的 room 和 token 变量,是被 Google APP 自己提供的:看一下 这里,在这个库里看添加了什么值。

这个代码初始化 video 变量将显示本地摄像头视频流和远程客户端视频流。resetStatus() 简单的设置状态信息。

openChannel() 函数设置 WebRTC 客户端之间的信息。

function openChannel(channelToken) {
console.log("Opening channel.");
var channel = new goog.appengine.Channel(channelToken);
var handler = {
'onopen': onChannelOpened,
'onmessage': onChannelMessage,
'onerror': onChannelError,
'onclose': onChannelClosed
};
socket = channel.open(handler);
}

对于 Signaling,这个 Demo 是用的是 Channel API ,这使得客户端间 JavaScript 没有轮询也可以通信。( WebRTC Signaling 上面有详细介绍)。

使用 Channel API 创建一个通道就像这样:

  1. Client 生成一个独特的 ID。
  2. Client A 向 APP Engine 请求一个数据管道,发送这个 ID。
  3. APP Engine 通过 Channel API,为这个 ID 和 Token 请求一个数据管道。
  4. APP 将这个 token 返还给 A。
  5. Client 打开 Socket 连接监听这个服务器的管道。

发送消息像这样:

  1. Client B 发送一个 POST 请求给 App Engine。
  2. App Engine 通过一个请求给 Channel。
  3. Channel 携带着信息给 Client A。
  4. Client A 的接受消息回调被调用。

webRTC前世今生的更多相关文章

  1. 大话WebRTC的前世今生

    音视频的历史 音视频可以说是人类与生俱来的需求,人一出生就要用耳朵听,用眼睛看.中国的古代神话中为此还专门设置了两位神仙(千里眼和顺风耳),他们可以听到或看到千里之外的声音或景像. 为了解决听的远和看 ...

  2. 转:webRTC的前世今生

    https://blog.coding.net/blog/getting-started-with-webrtc

  3. 基于 WebRTC 技术的实时通信服务开发实践

    随着直播的发展,直播实时互动性变得日益重要.又拍云在 WebRTC 的基础上,凭借多年的开发经验,结合当下实际情况,开发 UPRTC 系统,解决了网络延时.并发量大.客户端解码能力差等问题. WebR ...

  4. 腾讯技术分享:微信小程序音视频与WebRTC互通的技术思路和实践

    1.概述 本文来自腾讯视频云终端技术总监rexchang(常青)技术分享,内容分别介绍了微信小程序视音视频和WebRTC的技术特征.差异等,并针对两者的技术差异分享和总结了微信小程序视音视频和WebR ...

  5. 了不起的WebRTC:生态日趋完善,或将实时音视频技术白菜化

    本文原文由声网WebRTC技术专家毛玉杰分享. 1.前言 有人说 2017 年是 WebRTC 的转折之年,2018 年将是 WebRTC 的爆发之年,这并非没有根据.就在去年(2017年),WebR ...

  6. 实时视频直播客户端技术盘点:Native、HTML5、WebRTC、微信小程序

    1.前言 2017 年 12 月,微信小程序向开发者开放了实时音视频能力,给业内带来广阔的想象空间.连麦互动视频直播技术在 2016 年直播风口中成为视频直播的标配,然而只有在原生的 APP 上才能保 ...

  7. 1.1、webrtc的历史和现状

    1.1.webrtc的历史和现状 本书目录 温馨提示:本书的内容,将按照顺序一一展开,上篇文章阐述本书的诞生的原因,推荐阅读方式等. 如果你还没有阅读上一篇文章(必读前言—— 作者的独白),我建议返回 ...

  8. 【调侃】IOC前世今生

    前些天,参与了公司内部小组的一次技术交流,主要是针对<IOC与AOP>,本着学而时习之的态度及积极分享的精神,我就结合一个小故事来初浅地剖析一下我眼中的“IOC前世今生”,以方便初学者能更 ...

  9. [C#] 回眸 C# 的前世今生 - 见证 C# 6.0 的新语法特性

    回眸 C# 的前世今生 - 见证 C# 6.0 的新语法特性 序 目前最新的版本是 C# 7.0,VS 的最新版本为 Visual Studio 2017 RC,两者都尚未进入正式阶段.C# 6.0 ...

随机推荐

  1. 文件系统inodes使用率过高问题处理

    运维过程中经常碰见文件系统inodes使用率过高导致文件系统不可写的问题,常见场景如下 .Oracle产生的审计文件,特别是DG备库或者审计设置为OS时 .crontab产生大量邮件,导致/var/s ...

  2. 谭浩强 c++程序设计第一章课后习题 第10题

    #include <iostream> using namespace std; int main() { int a,b,c; cout<<"请输入三个整数类型的数 ...

  3. 已解决: mybatis报错 org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'xxx' in 'class java.lang.String'

    最近在练习MyBatis时 进行姓名的模糊查询时候出现 org.apache.ibatis.exceptions.PersistenceException: ### Error querying da ...

  4. Python基础-Python注释

    一.什么是注释.特性 1.一段文字性的描述,通过注释,可以解释和明确Python代码的功能,并记录将来要修改的地方. 2.当程序处理时,Python解释器会自动忽略,不会被当做代码进行处理 二.注释的 ...

  5. http请求中客户端真实的ip

    private String getRemoteAddr() { String ip = ""; String unknow = "unknown"; try ...

  6. JavaScript常用八种继承方案

    更新:在常用七种继承方案的基础之上增加了ES6的类继承,所以现在变成八种啦,欢迎加高级前端进阶群一起学习(文末). --- 2018.10.30 1.原型链继承 构造函数.原型和实例之间的关系:每个构 ...

  7. php 数据脱敏显示

    /** * 数据脱敏 * @param $string 需要脱敏值 * @param int $start 开始 * @param int $length 结束 * @param string $re ...

  8. 数据存储之json文件处理和csv文件处理

    什么是json: JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式.它基于 ECMAScript (w3c制定的js规范)的一个子集,采用 ...

  9. 第2章 CentOS7集群环境配置

    目录 2.1 关闭防火墙 2.2 设置固定IP 2.3 修改主机名 2.4 添加用户 2.5 修改用户权限 2.6 新建目录 2.7 安装JDK 1.卸载系统自带的JDK 2.安装JDK 2.8 克隆 ...

  10. Sublime Text配置python以及快捷键总结

    1.打开Tools > Build System > New Build System.. 2.点击New Build System后,会生成一个空配置文件,在这个配置文件内覆盖配置信息, ...