Janus网关的集成与优化

2023-10-29

本文由学霸君音视频架构师李桥平在LiveVideoStackCon 2020 线上峰会的演讲内容整理而成,内容主要包括Janus网关的集成过程以及集成过程中遇到的一些问题。

文 / 李桥平

整理 / LiveVideoStack

大家好,我是李桥平,来自学霸君上海互动产品研发中心,本次分享的主题是Janus网关的集成与优化。Janus网关是WebRTC的媒体服务器,它可以接收来自WebRTC客户端的音视频数据,根据业务需要对媒体数据进行处理,再转发到其他WebRTC客户端上, 以此完成音视频互动。

本次分享的主要内容是如何把Janus网关集成到我们公司内部的自研RTC系统中,并对其做了一些优化,在集成之后就可以通过浏览器和客户端进行实时互动了。

1

背景介绍

背景介绍主要从三个方面来进行切入,分别是:业务场景、自研RTC体系以及为何要做集成。

1.1 业务场景

我们所做的业务是一个多人在线实时互动的教育场景,基本需求是老师和学生之间能够进行音视频实时互动。除了音视频之外,还需要有一些其他的辅助教学内容,也需要进行实时的交互,比如老师和学生的手写笔迹、PPT课件、控制的状态(课件翻页)等。为了满足这些功能,从技术上分解来看,首先需要支持多对多的音视频连麦,其次是课件、手写笔迹的实时同步。

1.2 自研RTC体系

为了实现这些功能,在袁荣喜老哥的带领下, 我们开发了自己的RTC系统。自研RTC系统主要包含服务端和客户端两大块,它们都是通过自研实现的(语音处理借助了WebRTC的APM模块)。客户端和服务器之间使用UDP协议来进行媒体通信, 数据包采用的是私有格式, 在此基础之上完成传输的控制, 比如数据包排序重组, FEC, 丢包重传, 主动Get以及拥塞控制等. 整个体系以客户端的形式提供给用户,支持Windows、安卓、MAC、iOS这几个主流平台,在使用之前需要下载客户端。

1.3 为何要做集成

我们主要是从用户接入的易用性来考虑的. 首先是我们的客户端需要用户自己去下载,安装成本是比较高,然后才是注册账号、登录这些步骤。而WebRTC可以在浏览器上运行, 而大部分用户对于浏览器是非常熟悉的. 其次, WebRTC的功能通过JS API进行调用,天然跨平台, 不需要过多的考虑设备兼容性这些问题, 它们都封装在WebRTC内部了。

通过集成,用户可以通过浏览器来接入我们的产品,对于没有使用过我们产品的用户来说, 它提供了一种更加便捷的方式。

上图是完成集成后的一个效果,左图是浏览器,登录的是学生端。右图的窗口是我们PC上的客户端,登录的是老师。老师和学生可以进行实时的视频互动,同时还可以通过PPT课件和手写笔迹来辅助课堂教学。

2

WebRTC与Janus网关

WebRTC与Janus网关部分包含三个小节:首先是P2P传输通道的建立,介绍WebRTC的媒体传输是如何建立起来的,其次是介绍WebRTC网关以及Janus网关。

2.1 P2P传输通道的建立

P2P是指通信的内容可以不经过服务器, 直接发送给对方,省去了中间服务器的开销。WebRTC的P2P传输底层采用的是UDP协议,从传输特性上说,它是无连接、不可靠的协议。当然,WebRTC在进行传输时会有比如包确认、包重传等措施来弥补这些问题。

图中下方是两台需要进行音视频互动的电脑,电脑中的五色圆圈图案是WebRTC的logo,表示这个电脑上运行的WebRTC的客户端,这种客户端最常见的就是浏览器了。实际上只要实现WebRTC的模块功能,它们都可以进行音视频的会话,比如WebRTC网关就实现了WebRTC模块的功能,这里认为这两台电脑上运行了支持WebRTC的浏览器就可以了。这两个浏览器要进行音视频互动至少需要两方面的信息:一是双方采用怎样的音视频编解码以及相应的编解码参数,比如采样率、分辨率、帧率等参数。二是使用UDP发送数据需要知道对方UDP的地址信息,主要包括IP地址和端口。要交换获取这两方面的信息的话, 需要借助到一个位于外网的服务器,我们称之为信令服务器。

接下来我们来分析一下连接建立的过程. 首先,左边浏览器发起一个SDP offer的请求,在SDP中携带了它支持的音视频编解码和ICE参数。这里引入了两个概念:SDP和ICE。SDP(Session Description Protocol)是会话描述协议,这里只需知道它封装了协商的参数就可以了。ICE(Interactive Connectivity Establishment)是互动连接建立,它负责UDP下媒体会话的建立. 在ICE参数里包含了UDP的地址信息(访问外网的NAT地址需要借助STUN服务, 为了简单起见, 可以先不考虑)以及建立ICE连接所需要的用户名跟密码。

右边的浏览器在接收到SDP offer工作请求以后,会根据自己所支持的编码器情况进行匹配和筛选,然后生成SDP answer作为响应,通过信令服务器中转返回给左边的浏览器,这样双方就完成了SDP的协商和交换。

在交换了SDP之后, 双方通信需要的信息都完备了. 随后这两个浏览器会分别初始化好各自的音视频设备,比如麦克风、摄像头设备。然后根据协商好的编解码, 初始化编解码器. 于此同时, 它们会向对方发送ICE建立请求的消息,该消息会带上双方协商好的ICE参数,主要是携带用户名和密码的信息(后面的单端口改造借助了这里的用户名字段)。在完成ICE的请求交换后进行握手认证,这样就建立起了ICE的连接,双方随后以P2P的方式通过ICE连接发送编码后的媒体数据。

直接将媒体数据发送给对方的这种形式被称之为P2P直连,这种方式看似很好,因为它中间不需要经过服务器,但在一些情况下会有问题。

首先,一般的设备都没有公网IP地址,在访问外网时需要经过路由器,路由器上的NAT转换会分配相应的外网地址,再进行设备到外网的访问工作. 这时路由器上的NAT策略直接影响到ICE连接是否能够建立起来。整个过程涉及到UDP穿透问题,比如在对称型、限制型锥形NAT上,穿透是很难完成的。

其次,在P2P直连的方式下,中间链路我们无法控制,因此传输质量难以保证。假设图中这两台电脑,一个位于电信,一个位于网通,即使它们能够完成UDP的穿透,它们之间的传输延迟大概率也是很高的。

最后,因为数据不经过服务器,行为监管和媒体录制都难以实现,, 尤其对教育行业来讲,行为监管这块是一个必不可少的需求。

2.2 WebRTC网关架构

这是WebRTC网关的架构图。通常情况下我们将WebRTC网关部署到外网,这两个浏览器分别通过NAT连接到网关,并通过网关来转发相应的媒体数据。网关上的WebRTC logo表示在网关上实现了WebRTC模块的功能. 因此它可以和浏览器上的WebRTC模块进行通信。浏览器和WebRTC网关之间的红色箭头表示信令消息的交互,绿色箭头表示媒体消息。

下面来看看关于上个小节中的几个问题在WebRTC网关上是如何解决的。

首先穿透问题,因为WebRTC网关是部署到外网的,浏览器通过内网去访问外网. 只要能够正常上网,访问外网是没有问题的,因此不会有穿透失败的问题, 同时也可以省去STUN服务.

其次是联通到电信的情况,可以把WebRTC网关部署到BGP的多线机房, 电信和联通到BGP的延迟可以做到很低,通过一个中转,整体的中转质量反而比P2P直连质量更好。

最后是监管和录制,因为媒体数据会经过WebRTC网关,可以方便地在网关上进行录制,同时也可以在网关上针对媒体内容进行相应的数据分析,实现对其监管的功能。

在讨论WebRTC网关时,一般会根据网关对媒体消息的处理方式划分为两类:SFU和MCU。

SFU在收到媒体数据以后,不会对媒体数据本身进行处理,只做一些基本处理(SSRC, timestamp等转换)和转发。左图是SFU的示意图,不同颜色所表示的媒体数据在进入SFU之后,它是以原来的形态发送到其他浏览器上的。

右图是MCU的示意图,媒体数据在进入MCU以后,MCU会对媒体内容进行深度处理,比如把多路的声音合并成一路或者把多路的头像合并成一个大头像,再根据需要做转码,并转发到其他浏览器上。合流的一个好处是可以节省相应的带宽,同时可以在发送媒体数据的时候, 根据浏览器所支持的编解码情况进行转码,因此它的适应性会比较好。

2.3 Janus网关

Janus网关是SFU. 它是用C语言来实现的。其次, 在Janus上,业务模块以插件的形式实现,部署是以SO动态库的形式进行部署的,所以它的主程序和插件开发是一个分离的方式。最后,Janus Demo非常简单直观,很容易上手。

接下来这部分介绍Janus网关的软件架构。从层级上分析,Janus网关主要分为三层,从上至下分别是插件层、核心层和传输层。

插件层主要是决定SFU的转发逻辑,比如决定转发给房间里面的所有人,还是只转发给其中的一部分人,是转发音频或者视频,还是音视频同时转发。一个完整的插件方案,除了Janus网关服务器上的插件实现之外,还包括浏览器上的JS SDK。JS SDK处理的逻辑主要包括进出房间、订阅相应的媒体流等. 除此之外, 调用WebRTC的API获取麦克风和摄像头的数据,还有播放音频和视频数据,都是通过JS SDK来完成的。

核心层主要负责SDP的协商以及ICE连接的建立,UDP媒体数据的接收和转发也在核心层里完成。而插件和JS SDK的通信使用的是TCP协议, 它是通过传输层来完成的.

传输层主要负责在JS SDK和网关之间传输控制数据, 插件自定义消息等。传输层支持多种常见的传输协议,比如HTTP、WebSoket等。

3

Janus与自研RTC的集成

第三部分是Janus与自研RTC的集成,主要包含三个小节,分别是系统架构、音视频互通、集成效果。

3.1 系统架构

这张图片是高度简化后的结果,像自研RTC集群里的媒体调度、负载均衡、线性扩展等内容都没有在这里表达出来,主要是希望能突出与集成相关的内容。图中大致包含三个部分:自研RTC系统、Janus网关以及中间绿色箭头代表的媒体通道。

我们按上图从左至右, 来看一下通信流程。

首先是用户A通过任意一个平台的客户端连接到自研RTC集群,通过中间的媒体通道,间接地和连接到网关上的浏览器用户B进行音视频互动。在Janus网关和浏览器用户B之间主要传输RTP格式的音视频数据和自定义格式的笔迹数据。其中的音视频数据走的是P2P的传输通道,笔迹数据走的是WebSocket通道。整个集成核心的部分是位于Janus网关和自研RTC集群中间的绿色箭头所代表的音视频转换,更具体的来说, 就是自定义封装格式和RTP封装格式的转换。

前面介绍P2P媒体传输通道时提到RTP最终是通过UDP的传输协议发送出去的。

为了避免IP分片, 发送的UDP包不能太大, 具体一点是不能超过路径上MTU的限制,一般来说,以太网上的MTU的最大限制是1500个字节。实际过程中需要除去IP协议头和UDP协议头开销,剩下大概也就1400多个字节, 因此RTP包不能超过这个限制, 这个限制会影响到RTP的封包过程。

3.2 音视频互通

在我们的系统中音频采用Opus编码,视频采用H.264编码,WebRTC(主要是Chrome浏览器)也支持这两种编码,因此不需要在网关上进行转码了。

图中展示的是音频数据的转换, 包含了音频数据从采集到封装成RTP的过程。从上往下, 首先是声卡采集到PCM数据,一般是按10毫秒或者20毫秒这种固定长度进行组织. 经过Opus编码器, 根据PCM数据的内容特征, 编码成长度不一样的编码数据. 编码后的音视频数据一般是几十到几百个字节左右。这样的数据量可以直接在单个RTP包中进行携带,因此声音的RTP封装非常简单,只需要在数据的前面追加上RTP头部就行。

RTP头部中主要的两个字段是sequence number和timestamp, 即序列号和时间戳。因为UDP传输是一个不可靠的协议,在传输的过程中可能会发生丢包或者乱序到达。序列号可以帮助接收端正确地组织接收到的数据, 根据序号的缺失情况可以知道哪些数据包丢失,根据丢失包的序号可以要求发送端进行重传,从而保证传输质量。时间戳主要是辅助播放端进行声音的同步播放。

整个过程倒过来看,就是如何从浏览器发过来的RTP数据中提取编码数据的过程。在提取出编码数据以后就可以封装成自研RTC格式,通过自研RTC集群再转发到客户端上,并在客户端上进行播放。

接下来是视频的转换。

H.264视频转换在RFC6184文档里有详细的规定和说明。相对于音频来说,视频转换要复杂一些,这是因为图像数据编码后,它的数据帧往往比较大,会超过RTP包的大小限制。

该图是视频数据转换成RTP包的示意图。还是从上往下看,首先摄像头采集原始的视频图像,一般是YUV格式的,经过H.264编码后生成H.264的数据帧。数据帧本身是有内部结构的,它包含一个起始码,后面跟着NAL单元,由多个这样的结构组成编码后的数据帧,在转换的过程中,第一步是要把起始码去掉,再提取出单个的NAL单元数据。然后根据NAL单元数据能否封装到单个RTP包中,分别封装成三种不同的封装格式。

图中左边是单个NAL单元的封装, 在NAL单元比较小的情况下使用. 中间是单元片段的封装, 在单个NAL单元大小超过RTP包限制的情况下,采用该封装格式。

右边是多个NAL单元聚集到一个RTP包的封装过程,这里主要针对NAL单元很小,RTP包可以同时携带多个NAL单元的情况,封装到一个包里,可以减少发包的数量。同样,封包过程需要正确的填充RTP头部的时间戳和序列号。

整个图从下往上看,就是从RTP数据流中提取出来H.264编码数据的过程,完成提取后再封装成自研RTC系统的格式,发送到客户端上进行数据的还原,再经过H.264解码器的解码,得到原始的视频数据并在界面上渲染出来.

3.3 集成效果

这个测试主要是想知道中间转换部分的开销, 因此这里不考虑客户两端到服务器的弱网情况. 首先是稳定WiFi,到服务器RTT是30毫秒,视频分辨率是320×240,帧率是20帧。整个过程下来音视频流畅,媒体延迟小于100毫秒。

测试方法借助了一个在线秒表的时间跳动的画面,虚拟摄像头采集在线秒表的动画,通过PC端进行编码,然后上传到自研RTC服务器, 转换成RTP格式, 通过RUDP通道传输到Janus网关, 再通过网关发送到浏览器上还原出视频画面。对比PC端和Web端看到的视频画面,就可以得出他们观看的时间差。

图中可以看出PC客户端的画面时间和Web的画面时间相差大概几十个毫秒。由于PC端有一些相应的处理(如美颜),而且存在渲染的时间消耗, 实际的差值会比这个大一些, 整体的时间延迟估计是100毫秒左右,效果还是不错的。

4

Janus网关优化

这部分我会从现象入手,介绍集成过程中所做的一些优化,这里主要介绍CPU优化和端口优化。

首先在CPU方面,在测试时我们发现,在同一个房间里进入12个人,八个人开麦进行音视频互动的话,Janus近程的CPU大概占到30%多。如果是一个四核的CPU, 算打到300%的话,也只能支撑120多个人,这样的话承受能力会非常有限。因此需要对CPU进行优化。

其次是端口,Janus在服务部署的过程中需要开放大量的端口. 这是因为Janus对于每一路上传和每一路观看都需要为它分配一个外网端口。分配过多的端口不管从安全管理上还是运维部署上都会带来不便。在我们实验室实际开发过程中就遇到过,当同时开3、4个视频时,整个视频的数据下发不来, Web上看到的画面是黑的. 经过找相应的IT人员一起定位分析后,发现是办公室交换机出口对UDP访问端口做了限制导致的,因为每一路视频上传下载都需要分配端口, 在交换机策略看来, 多个内网的机器访问了同一个外网IP(janus网关的IP)的大量不同端口, 被判定是异常状态了。因此,不管从安全性、运维部署,还是服务质量上来讲,最好是用少量的端口来完成同样的事情。

4.1 CPU优化

这部分介绍针对上述两个问题的分析和相应的解决方法。CPU问题的原因主要有3个:一是SFU的转发关系复杂度为M*N,其中M是上麦人数,N是房间内的总人数。假设房间里有100个人,其中10个人上麦,那么转发的复杂度就是10×100,因为对每一路上传的视频都需要转给其他99个人,一路上传加上99路转发就是100处理量,10个人就是1000。

二是对于每一路上传和转发,Janus都分配一个对应的UDP端口和socket描述符,该分配行为是Janus所使用的网络库Libnice决定的。

三是Libnice的内部采用poll做事件处理,在描述符量很大时,它的效率很低。因为poll在调用时, 需要把所有描述符以数组的形式传递到内核, 内核需要对每个描述符进行查询处理,并且还要注册相应的事件监听。如果当前这次调用没有收集到任何事件的话, 它会进行等待, 在等待过程中, 它会把当前线程注册到所有描述符的通知等待队列里,然后被动等待相应事件的唤醒。在事件到达唤醒后, 返回的过程中又需要把当前线程移出所有描述符的等待队列,这其中涉及到大量锁操作。以上三个原因叠加起来,就造成了高CPU的情况。

CPU优化的对策主要是从两方面入手: 减少端口的使用, 以及把glib内的poll调用改为epoll。

在使用上,端口的问题的使用可以采用以下一些办法来缓解:

一是通过ice_enforce_list限定ICE收集candidate的网卡。默认情况下,Janus会对所有的网卡都做端口收集。我们在开发的过程中所部署的机子上正好有两个网卡,测试时发现,它所收集的端口数量比单网卡下多了一倍,在开启这个的配置后,数据数量立马减半,CPU也降低了很多。

二是确保Janus服务配置中, ice_tcp=false。这是在使用TCP穿透时所需要收集的端口,在实际应用中很少用到,所以将其设置为“false”禁止掉就可以。

其次,把glib内的poll调用改为epoll. 可以采用两种方式 :一是修改glib代码,把事件处理的poll调用替换成epoll. 这种方式需要把glib代码拉下来修改并测试,整个工作需要比较长的时间;二是采用github上第三方的扩展实现 。

4.2 端口优化

对于端口优化,我们采用了端口复用方案. 实现端口复用的情况下, 可以做到减少端口使用, 同时降低CPU使用率。具体的方法如下, 首先在Janus上接管ICE的处理,通过SDP中的ICE用户名参数来识别发送端身份。在上文提到的P2P连接建立的过程中,首先要经历ICE认证的过程,在认证消息里包含了用户名信息,而用户名信息是通过SDK的的ICE参数来传递给对方的,因此可以在用户名中添加业务标识的内容,然后在ICE握手的过程中识别出对方的身份,然后将身份和发送的IP地址关联,这样只要对方发送消息我们就可以知道是谁发送的,从而实现端口复用。

在实现单端口方案的过程中,  采用epoll来实现描述符事件管理,去掉对libnice和glib的依赖。最终可以通过单一(或少量)的端口对外提供网关的服务,同时降低CPU的消耗。

在方案实施后, 同样的场景下, CPU占用从30%降到了10%左右, 仍然有点高, 不过已经好很多了。

相比前面的几种方案,这种方案会复杂很多,首先需要实现ICE逻辑并在Janus Core中把libnice替换成自实现方案,同时还需要实现相关的辅助结构,如ICE定时器等, 总体来看有一定的工程复杂度,但从效果上来说是值得一做的。

LiveVideoStackCon 2020 北京

2020年10月31日-11月1日

点击【阅读原文】了解更多详细信息

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Janus网关的集成与优化 的相关文章

  • CompletableFuture:几个任务

    如何使用 5 个 CompletableFutures 异步执行 20 个 Runnable 任务 或 1 个任务 20 次 这就是我所拥有的 Runnable task gt long startTime System currentTi
  • 测试前设置TestNG的输出目录

    我正在使用 Eclipse 运行一些 TestNG 测试 使用 XML 文件 右键单击 作为 TestNG 套件运行 我仅将 Maven 用于依赖项 而不用于运行测试 有没有办法在代码中设置输出目录 例如 System out printl
  • 对 Java 安全性和 BouncyCastle API 感到茫然和困惑

    我一直在尝试理解 Java 的 BouncyCastle 加密 API 不幸的是 我发现 Java 密码学总体上被服务提供者接口和术语所掩盖 以至于我无法理解任何东西的实际作用 我已经尝试反复阅读必要的文档 但它仍然难以理解 引入了许多远远
  • Google 语音 API 凭据

    我正在尝试使用 Google Speech API 但从 Google Cloud 外部进行一些测试 在旧的测试版中 我能够指定凭据文件 但现在我无法在SpeechClient class 如何使用 Google Speech API Ja
  • 将 1GB 文件的内容流式传输到单列下的 sqlite 表

    下面的实现给出了具有 4 GB 堆空间的 1 GB 大小的文件的内存不足错误 Files lines 将返回一个流 但在运行 Collectors joining 时会出现堆错误 我们可以使用 jooq 和 jdbc 保留原始行分隔符来流式
  • 纯java adb客户端[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 ADB http developer android com tools help adb html分
  • 不支持的major.minor版本52.0错误[重复]

    这个问题在这里已经有答案了 我有在 JDK 1 8 上编译的代码 我创建了 war 文件并将其部署在运行 JRE 1 8 的 Ubuntu 服务器中的 Tomcat8 上 我打开主页的时候出现这个错误 java lang Unsupport
  • Java中如何保存DOM文档?

    我在用DOM解析器和XPATH解析我的XML文件 我改变了一个节点的值Document Object 然而当我打开我的XML文件 它没有向我显示任何反射 我的DOM解析器代码如下 private void setPortNumber int
  • gradlew:appengineEnhance 失败

    我正在使用 Java 创建移动后端Google App Engine with Android Studio 为了启动公开我的 API 的本地服务器 我使用gradlew module name appengineRun 然而 当我去htt
  • gwt rpc 中的会话 ID cookie

    假设我正在滚动自己的会话代码 那么在 java 中生成唯一且安全的会话 id cookie 的正确方法是什么 我不应该自己推出而是使用已经标准化的东西吗 我正在使用 gwt 和 google app engine 平台 如何使会话在浏览器
  • 使用超类创建构建器时,父类无法返回子类的实例[重复]

    这个问题在这里已经有答案了 如果我使用构建器模式来配置新对象 我可能有两个类 例如Game and HockeyGame 如下所示 当我想创建一个新的HockeyGame 我得到它的构建器并开始调用方法来根据需要配置对象 我遇到的问题显示在
  • JSF中直接更改URL来限制用户访问页面

    我的应用程序中有两种用户 客户和卖家 我正在使用一个PhaseListener在JSF中防止用户在未登录的情况下访问页面 但在登录后我不知道如何防止用户更改地址栏中的URL并访问他也不允许的页面 例如 阻止客户访问卖家页面 有谁知道我如何防
  • 在 pom 中添加 selenium 依赖项后,AWS Lambda Jar 无法压缩

    这是一个奇怪的错误 将 selenium 依赖项添加到我的 maven 项目的 pom 并将其上传到 lambda 后 它说无法解压缩文件 然而 在删除依赖项之后 lambda 能够很好地解压缩文件 但是它会出现一个随后找不到的类 我尝试一
  • 在Java中执行.lnk文件

    我需要在java中执行 lnk文件 指向exe文件的lnk文件 我能怎么做 在 VB net 中我做 Process Start path 它有效 谢谢你的帮助 Use a 流程构建器 http download oracle com ja
  • 将自定义方法映射器映射到 Mapstruct

    我正在创建一个 poc 以便在我未来的项目中使用 Mapstruct 现在我有一个问题如何将自定义方法映射到特殊目标 例如我有以下接口映射器 Mapper public interface ItemMapper static ItemMap
  • 将 s:element 和 s:complexType 命名为相同的名称

    将 s element 和 s complexType 命名为相同名称是否合法 可以看到下面的代码 element和complextype具有完全相同的名称 这是 wsdl 文件的一大块
  • ibatis spring java.lang.NoSuchMethodError com.ibatis.sqlmap.engine.builder.xml.SqlMapConfigParser.parse

    我在 weblogic 10 3 6 中使用 spring 3 2 0 和 ibatis 2 3 4 在weblogic中部署时 我得到这个 NoSuchMethodError 如下 User defined listener org sp
  • 如何使用 Jackson 将列表内容序列化为平面 JSON 对象?

    给定以下 POJO public class City private String title private List
  • 用于只读 DB 的 java ORM

    我了解 hibernate 但我想知道是否有一个更轻的 ORM 引擎只读数据库 我的意思是 我不需要一些事务查询或更新一些记录 另一方面 我需要处理一些大的记录列表 List
  • 没有 WindowManager.LayoutParams.TYPE_PHONE 的粘性覆盖

    我所说的粘性是指一个不会通过调用启动器意图而关闭的窗口 intent addCategory Intent CATEGORY HOME 以前这是用完成的WindowManager LayoutParams TYPE PHONE 但此类型现已

随机推荐

  • html a标签链接 点击下载文件

    通常在咱们写项目的时候会遇到附件上传 下载功能 在上传完文件后会把文件的路径发送给后端保存到数据库里以便下载 如果不通过后台直接下载的话 可以把文件路径给a标签的属性href 就可以实现下载 a href 文件路径 点击下载 a 但是有个情
  • 局域网内window10和Windows7共享只有USB接口打印机的方法——以sharp2048D为例子

    问题背景 办公室内有一台sharp2048d打印机 但是只有一个USB接口 没有网络打印功能 在办公室有多台电脑的情况下 打印文件不方便 所有产生了打印机共享的问题 1 夏普sharp2048D打印机驱动安装 此处教程参考了官方客服 1 在
  • [Python人工智能] 八.卷积神经网络CNN原理详解及TensorFlow编写CNN

    从本专栏开始 作者正式开始研究Python深度学习 神经网络及人工智能相关知识 前一篇文章介绍了什么是过拟合 并采用droput解决神经网络中过拟合的问题 以TensorFlow和sklearn的load digits为案例讲解 本篇文章详
  • uni-app 笔记 条件编译 多端兼容

    uniapp提供了非常强大的条件编译功能 你可以在在h5中 小程序中 app中 分别执行不同的代码 html标签 js css均可用 解决了多端适配的问题 简单高效 可以使用的平台有 https uniapp dcloud io platf
  • 【HIT-计算机系统】ICS-Lab5 LinkLab

    第1章 实验基本信息 1 1 实验目的 理解链接的作用与工作步骤 掌握ELF结构与符号解析与重定位的工作过程 熟练使用Linux工具完成ELF分析与修改 1 2 实验环境与工具 1 2 1 硬件环境 x64 CPU 1 60GHz 8G R
  • Nginx 安装第三方模块 不停机 平滑升级 方法2

    1 安装步骤略 可以参考https blog csdn net qq 29974229 article details 126114380 1和2 至此nginx已经启动 备份nginx文件 cp apps nginx 1 20 sbin
  • ES-数据建模

    数据模型是描述现实世界某种现象或者状态的物理抽象 比如我们之前用FSA来描述周老师的一天这种现象 就是把现实世界抽象成某种模型 现实世界有很多重要的关联关系 博客帖子有一些评论 银行账户有多次交易记录 客户有多个银行账户 订单有多个订单明细
  • 推荐一款日志切割神器

    点击上方 Java后端 选择 设为星标 优质文章 及时送达 链接 https urlify cn F3Uzmi 对于 Linux 系统安全来说 日志文件是极其重要的工具 不知为何 我发现很多运维同学的服务器上都运行着一些诸如每天切分 Ngi
  • mysql 文本处理函数_Mysql语法之使用数据处理函数(文本,时间,数值)

    SQL支持利用函数来处理数据 函数一般是在数据上执行的 它给数据的转换和处理提供了方便 一 文本处理函数 之前咱们已经看过一个文本处理函数的例子 RTrim 函数去除列值右边的空格 这次试用Upper 函数 mysql gt select
  • SQL手工注入探索旅程(一)

    SQL注入漏洞原理 SQL 注入是一种将 SQL 代码插入或添加到应用 用户 的输入参数中 之后再将这些参数传递给后台的 SQL 服务器加以解析并执行的攻击 攻击者能够修改 SQL 语句 该进程将与执行命令的组件 如数据库服务器 应用服务器
  • 【python学习】第一节:Python编程规范与代码优化建议

    写在前面的话 学python好处多多 相信有兴趣看这类文章的朋友一定对python多少也有一定的了解 文章不过多赘述python安装方法 直接开门见山讲一些python的基础知识 有哪些不对或者缺少的内容 请在评论区积极发言 我早日修改 谢
  • 什么是webSocket?

    什么是webSocket WebSockets是一种协议 它允许在Web应用程序中建立持久连接 这意味着当客户端与服务器建立连接后 它们可以始终保持连接状态 直到其中一个终止连接 相比于传统的HTTP协议 WebSockets提供了更高效的
  • git rev-parse --git-dir的使用

    如果某次修改仅仅改动几个字 不想重新生成一条记录的话 可以在git add 之后执行git commit amend命令 但是执行git commit amend不生成新的目录的前提是配置hook 也就是需要用到git rev parse
  • 2021-04-14

    eslint 自动修复 现在写项目一般都要用到eslint规范代码格式 但是在开发阶段要时刻注意空格 缩进 分号也是挺影响效率的 所以需要用eslint 的自动修复命令 在package json中的命令 lint eslint ext j
  • 网络安全通识全解

    01 什么是区块链 区块链 Blockchain 是指通过去中心化和去信任的方式集体维护一个可靠数据库的技术方案 从本质上讲 它是一个共享数据库 存储于其中的数据或信息 具有 不可伪造 全程留痕 可以追溯 公开透明 集体维护 等特征 是一种
  • 成功解决keil识别不到单片机芯片,下载不了程序

    成功解决keil识别不到单片机芯片 下载不了程序 我的芯片是STM32F429 正点原子的阿波罗 今天使用开发板做实验 突然找不到芯片了 以前下载的PWM波也运行不了 查找了好久 原来是芯片锁了 终于解决了 我是第二种办法实现给芯片解锁的
  • 网络安全等保:Oracle数据库测评

    以下结果以Oracle 11g为例 通过PL SQL进行管理 未进行任何配置 按照等保2 0标准 2021报告模板 三级系统要求进行测评 一 身份鉴别 a 应对登录的用户进行身份标识和鉴别 身份标识具有唯一性 身份鉴别信息具有复杂度要求并定
  • MySQL之体系结构

    一 MySQL系统体系结构 1 MySQL系统体系结构 1 1 数据库 1 2 数据库实例 1 3 MySQL体系结构 1 4 逻辑存储结构 1 5 MySQL物理存储结构 二 MySQL主要文件 1 慢查询日志 1 1 慢查询日志相关参数
  • 电路基础(4) 电阻电路的一般分析

    1 电路的图 将上面的电路图 抛开其中元器件的性质 可以提取出 只有线和结点的图 如果考虑电流等的流向 则可以变化位 有向图 提取的有向图少了8那条支路 是因为把元件的并联组合也作为一条支路了 提取的有向图少了7那条支路 是因为把元件的串联
  • Janus网关的集成与优化

    本文由学霸君音视频架构师李桥平在LiveVideoStackCon 2020 线上峰会的演讲内容整理而成 内容主要包括Janus网关的集成过程以及集成过程中遇到的一些问题 文 李桥平 整理 LiveVideoStack 大家好 我是李桥平