本文介绍了一种实用的安全聚合算法(Secure Aggregation Protocol),主要参考了Google的论文Practical Secure Aggregation for Privacy-Preserving Machine Learning 。

本文首发于我的博客,
原文地址请点击这里!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

谷歌的Bonawitz等人,在2017年的CCS中提出了一种安全聚合加密方案(SMPC),服务器只能看到聚合完成之后的梯度,不能知道每个用户的私有的真实梯度值。这种方法适用于大规模终端(例如手机)通过一个服务器,共同计算各自输入之和的情形,但是前提是不泄露某个特定的终端的输入,无论是对服务器还是对其他的终端。

在介绍该算法之前,首先介绍几个该算法中需要用的几个算法。

  • 秘密共享(Secret Sharing)

    Shamir算法是秘密共享协议的一种实现,本文使t用的就是这种算法,其基本思想是分发者通过秘密多项式,将秘密s分解为n个秘密分发个持有者,其中任意不少于t个秘密均能恢复密文,而任意少于t个秘密均无法得到密文的任何信息。

    本文使用{(u,su)}u∈U←SS.share (s,t,U),V⊆U,∣V∣≥t\left\{\left(u, s_{u}\right)\right\}_{u \in \mathcal{U}} \leftarrow \text { SS.share }(s, t, \mathcal{U}), \mathcal{V} \subseteq \mathcal{U},|\mathcal{V}| \geq t{(u,su​)}u∈U​← SS.share (s,t,U),V⊆U,∣V∣≥t,来表示将秘密s拆分成n份,传入集合U(大小为n,表示n个秘密分发持有者),s,t\mathcal{U}(大小为n,表示n个秘密分发持有者),s,tU(大小为n,表示n个秘密分发持有者),s,t,针对每一个集合中的每一个u输出它自己那一条拆分后的密文。

    使用SS.recon ({(u,su)}u∈V,t)=s\text { SS.recon }\left(\left\{\left(u, s_{u}\right)\right\}_{u \in \mathcal{V}}, t\right)=s SS.recon ({(u,su​)}u∈V​,t)=s来表示恢复密文。

  • 密钥协商(Key Agreement)

    DH密钥交换是一个常用的密钥交换协议,DH密钥交换的目的,是让想要通信的Alice、Bob双方,他们之间能够拥有一个私密的密钥,这个密钥只有A和B两个人知道。DH密钥交换包含如下步骤:

    • 首先,Alice和Bob商量好DH的参数,一个大数素数q\mathcal{q}q,和zp\mathbb{z}_pzp​上的一个生成元ggg(1<ggg<q\mathcal{q}q,一种比较特殊的素数)。这一步我们用公式表示为,KA.param(k)→(G′,g,q,H),H为哈希函数KA.param(k) \rightarrow \left(\mathbb{G}^{\prime}, g, q, H\right),H为哈希函数KA.param(k)→(G′,g,q,H),H为哈希函数

    • 然后通过公共参数生成各自的公钥和私钥:

      KA.gen⁡(G′,g,q,H)→(x,gx),x为私钥,gx为生成的公钥KA. \operatorname{gen}(G',g,q,H) \rightarrow\left(x, g^{x}\right),x为私钥,g^{x}为生成的公钥KA.gen(G′,g,q,H)→(x,gx),x为私钥,gx为生成的公钥

    • Alice和Bob分别将公钥发送给对方

    • 然后双方通过公钥生成一个用于通信的密钥,su,v=H((gxv)xu)s_{u, v}=H\left(\left(g^{x_{v}}\right)^{x_{u}}\right)su,v​=H((gxv​)xu​),用公式表示为,KA.agree(xu,gxv)→su,vKA.agree\left(x_{u}, g^{x_{v}}\right) \rightarrow s_{u, v}KA.agree(xu​,gxv​)→su,v​

  • 认证加密(Authenticated Encryption)

    其实就是使用一个key对一段明文进行加密,同时使用相同的key可以进行解密。用公式表示为:

    AE.dec⁡(c,AE.enc⁡(c,x))=x,x表示需加密的明文,c表示key\operatorname{AE.dec}(c, \operatorname{AE.enc}(c, x))=x,x表示需加密的明文,c表示keyAE.dec(c,AE.enc(c,x))=x,x表示需加密的明文,c表示key

  • 伪随机数生成器(Pseudorandom Generator)

    给定一个随机数种子,生成无关的随机数,使用PRG(seed)PRG(seed)PRG(seed)来表示输出的随机数。

  • 数字签名(Signature Scheme )

    对一段明文进行数字签名,以保证其内容没有遭到篡改。

    • 首先输入k,生成一对公钥和私钥,SIG.gen⁡(k)→(dPK,dSK)\operatorname{SIG.gen}(k) \rightarrow\left(d^{P K}, d^{S K}\right)SIG.gen(k)→(dPK,dSK)
    • 然后通过签名算法对一段消息m用私钥进行签名, SIG.sign(dSK,m)→σSIG.sign\left(d^{S K}, m\right) \rightarrow \sigmaSIG.sign(dSK,m)→σ
    • 然后用公钥进行验证,SIG.ver(dPK,m,σ)→{0,1}SIG.ver \left(d^{P K}, m, \sigma\right) \rightarrow\{0,1\}SIG.ver(dPK,m,σ)→{0,1}

算法具体实现步骤

  1. 初始阶段

    • 首先,每个客户端初始化一个安全参数k用来生成DH的相关参数,pp←KA⋅param⁡(k)p p \leftarrow \mathbf{K A} \cdot \operatorname{param}(k)pp←KA⋅param(k)
    • 规定秘密分享协议中的数值n和阈值t。
    • 规定m为传输数据向量的大小,xux_uxu​为隐私数据向量。
    • 每个客户端和服务器有一个经过身份验证的通道。
    • 客户端u从可信第三方针获得一个私钥,对所有的其他用户v第三方给予一个公钥,(dPK,dSK)\left(d^{P K}, d^{S K}\right)(dPK,dSK)
  2. 第0轮(通知Keys)

    • 对于客户端u:

      • 根据根据pp分别生成两对公钥和私钥,分别为,(cuPK,cuSK)←KA.gen⁡(pp)\left(c_{u}^{P K}, c_{u}^{S K}\right) \leftarrow \operatorname{KA.gen}(p p)(cuPK​,cuSK​)←KA.gen(pp)和(suPK,suSK)←KA⋅gen⁡(pp)\left(s_{u}^{P K}, s_{u}^{S K}\right) \leftarrow \mathbf{K A} \cdot \operatorname{gen}(p p)(suPK​,suSK​)←KA⋅gen(pp),然后使用数字签名算法对两个公钥进行签名,生成 σu←SIG.sign⁡(duSK,cuPK∥suPK)\sigma_{u} \leftarrow \operatorname{SIG.sign}\left(d_{u}^{S K}, c_{u}^{P K} \| s_{u}^{P K}\right)σu​←SIG.sign(duSK​,cuPK​∥suPK​)
      • 将生成的两个公钥,连同签名结果即,(cuPK∥suPK∥σu)\left(c_{u}^{P K}\left\|s_{u}^{P K}\right\| \sigma_{u}\right)(cuPK​∥∥​suPK​∥∥​σu​),通过经过验证的通道发送给服务器。
    • 对于服务器端:
      • 检查所有客户端中收集消息数是否小于t,否则中止算法,将收到消息的客户端集合记为U1\mathcal{U}_1U1​
      • 向U1\mathcal{U}_1U1​中的所有客户端广播其与其他客户端对应的客户端id,两个公钥,以及数字签名的结果,{(v,cvPK,svPK,σv)}v∈U1\left\{\left(v, c_{v}^{P K}, s_{v}^{P K}, \sigma_{v}\right)\right\}_{v \in \mathcal{U}_{1}}{(v,cvPK​,svPK​,σv​)}v∈U1​​
  3. 第1轮(分享Keys)

    • 对于客户端u:

      • 收到来自服务器的{(v,cvPK,svPK,σv)}v∈U1\left\{\left(v, c_{v}^{P K}, s_{v}^{P K}, \sigma_{v}\right)\right\}_{v \in \mathcal{U}_{1}}{(v,cvPK​,svPK​,σv​)}v∈U1​​,为了验证∣U1∣≥t\left|\mathcal{U}_{1}\right| \geq t∣U1​∣≥t是否真的成立,防止服务器伪造客户端来套取客户端数据,先对U1\mathcal{U}_1U1​中的所有客户端进行消息验证,即

        ∀v∈U1,\forall v \in \mathcal{U}_{1},∀v∈U1​, SIG.ver (dvPK,cvPK∥svPK,σu)=1\left(d_{v}^{P K}, c_{v}^{P K} \| s_{v}^{P K}, \sigma_{u}\right)=1(dvPK​,cvPK​∥svPK​,σu​)=1

        由于公钥,私钥是可信第三方分发的,如果服务器伪造客户端,那么这个公式就不成立,那么客户端立即中止算法。

      • 随机抽样一个bub_ubu​,用于PRG(伪随机数生成器)的生成种子。

      • 通过秘密共享算法生成SuPKS_u^{PK}SuPK​和bub_ubu​的分享内容(shares),

        {(v,su,vSK)}v∈U1←\left\{\left(v, s_{u, v}^{S K}\right)\right\}_{v \in \mathcal{U}_{1}} \leftarrow{(v,su,vSK​)}v∈U1​​← SS.share (suSK,t,U1)\left(s_{u}^{S K}, t, \mathcal{U}_{1}\right)(suSK​,t,U1​)和{(v,bu,v)}v∈U1←\left\{\left(v, b_{u, v}\right)\right\}_{v \in \mathcal{U}_{1}} \leftarrow{(v,bu,v​)}v∈U1​​← SS.share (bu,t,U1)\left(b_u, t, \mathcal{U}_{1}\right)(bu​,t,U1​)

      • 对于U1\mathcal{U}_{1}U1​中的其他客户端,针对每个客户端使用认证加密技术,使用的密钥是两个客户端经过密钥协商出来的结果,计算一个

        eu,v←e_{u, v} \leftarroweu,v​← AE.enc (KA.agree (cuSK,cvPK),u∥v∥su,vSK∥bu,v)\left(\text { KA.agree }\left(c_{u}^{S K}, c_{v}^{P K}\right), u\|v\| s_{u, v}^{S K} \| b_{u, v}\right)( KA.agree (cuSK​,cvPK​),u∥v∥su,vSK​∥bu,v​),

        eu,ve_{u, v}eu,v​携带了share的信息,这个东西后面就会被传给相应的其他客户端。

      • 将所有eu,ve_{u, v}eu,v​传输给服务器,这个东西每个都暗中包含了u和v地址数据(大概是客户端id一类的东西)

      • 存储本轮中生成的所有信息和收到的信息。

    • 对于服务器端:

      • 检查所有客户端中收集消息数是否小于t,否则中止算法,将收到消息的客户端集合记为U2\mathcal{U}_2U2​,注意U2⊆U1\mathcal{U}_{2} \subseteq \mathcal{U}_{1}U2​⊆U1​
      • 将收集到的所有eu,ve_{u, v}eu,v​,分别广播给相应的客户端。
  4. 第2轮(收集已经加密过的信息)

    • 对于客户端u:

      • 收集来自服务器端的与其他客户端对应的eu,ve_{u, v}eu,v​,并且得到集合U2\mathcal{U}_2U2​,如果发现大小小于t,则立即中止算法(因为少于t个客户端的信息无法恢复)。

      • 对于每个客户端u,计算他与U2\mathcal{U}_2U2​中其他客户端的共享mask(用来掩盖真实的数据),通过密钥协商得到密钥,su,v←KA.agree(suSK,svPK)s_{u, v} \leftarrow KA.agree \left(s_{u}^{S K}, s_{v}^{P K}\right)su,v​←KA.agree(suSK​,svPK​),然后用这个密钥作为种子,送入PRG(伪随机数生成器),生成与隐私数据向量大小相同的m个,作为mask向量。

        即pu,v=Δu,v⋅PRG(su,v)\boldsymbol{p}_{u, v}=\Delta_{u, v} \cdot \mathbf{P} \mathbf{R} \mathbf{G}\left(s_{u, v}\right)pu,v​=Δu,v​⋅PRG(su,v​),当 u>v,u>v,u>v, Δu,v=1\Delta_{u, v}=1Δu,v​=1 当u<vu<vu<v,Δu,v=−1\Delta_{u, v}=-1Δu,v​=−1(注意 pu,v+pv,u=0,∀u≠v)\left(\text {注意 } p_{u, v}+p_{v, u}=0, \forall u \neq v\right)(注意 pu,v​+pv,u​=0,∀u​=v),定义pu,u=0\boldsymbol{p}_{u, u} = 0pu,u​=0

      • 计算每个客户端u的个人mask向量pu=PRG(bu)\boldsymbol{p}_{u} = PRG(b_u)pu​=PRG(bu​),然后计算将隐私数据向量与个人mask向量和共享mask向量的加和,用来mask个人隐私数据,

        yu←xu+pu+∑v∈U2pu,v(modR)\boldsymbol{y}_{u} \leftarrow \boldsymbol{x}_{u}+\boldsymbol{p}_{u}+\sum_{v \in \mathcal{U}_{2}} \boldsymbol{p}_{u, v}(\bmod R)yu​←xu​+pu​+∑v∈U2​​pu,v​(modR)

      • 如果上述过程中任意操作,例如密钥协商,PRG操作失败,那么直接中止操作,否则将生成的yny_nyn​发送给服务器。

    • 对于服务器端:

      • 检查所有客户端中收集yny_nyn​数量是否小于t,否则中止算法,将收到消息的客户端集合记为U3\mathcal{U}_3U3​,注意U3⊆U2\mathcal{U}_{3} \subseteq \mathcal{U}_{2}U3​⊆U2​,将U3\mathcal{U}_3U3​的列表发送给每个用户。
  5. 第3轮(一致性检查,我认为该部分主要是为了防止服务器恶意模拟客户端以套取信息)

    • 对于客户端u:

      • 收集收到的客户端集合U3\mathcal{U}_3U3​,查看U3\mathcal{U}_3U3​的大小是否大于t,小于t直接中止算法。

      • 若大于等于t,那么将U3\mathcal{U}_3U3​使用数字签名进行某种形式的签名,σu′←SIG.sign⁡(duSK,U3)\sigma_{u}^{\prime} \leftarrow \operatorname{SIG.sign}\left(d_{u}^{S K}, \mathcal{U}_{3}\right)σu′​←SIG.sign(duSK​,U3​),得到一个σu′\sigma_{u}^{\prime}σu′​。

        这个东西主要是为了防止服务器在上一步恶意欺骗大多数客户端以套取信息,因为如果上一步中服务器故意伪造某一个客户端掉线的假象,那么想要套取信息的那个客户端和其他客户端收到的U3\mathcal{U}_3U3​就会是不一样的,这样签名就会出问题,可以被客户端发现服务器端造假。

    • 对于服务器端:

      • 收集所有σu′\sigma_{u}^{\prime}σu′​,确认收到的消息数是否大于t,否则中止,如果大于t,那么将这部分用户记为,U4⊆U3\mathcal{U}_{4} \subseteq \mathcal{U}_{3}U4​⊆U3​,对于该集合中的每一个用户,发送对应的签名结果{v,σv′}v∈U4\left\{v, \sigma_{v}^{\prime}\right\}_{v \in \mathcal{U}_{4}}{v,σv′​}v∈U4​​
  6. 第4轮(汇聚信息并解密)

    • 对于客户端u:

      • 收到来自服务器端的签名结果{v,σv′}v∈U4\left\{v, \sigma_{v}^{\prime}\right\}_{v \in \mathcal{U}_{4}}{v,σv′​}v∈U4​​,确认U4⊆U3\mathcal{U}_{4} \subseteq \mathcal{U}_{3}U4​⊆U3​,且数量大于等于t,然后确认SIG.ver (dPK,U3,σv′)=1\left(d^{P K}, \mathcal{U}_{3}, \sigma_{v}^{\prime}\right)=1(dPK,U3​,σv′​)=1 对于所有的 v∈U4v \in \mathcal{U}_{4}v∈U4​,这一步主要是防止服务器端故意欺骗客户端以套取客户端信息。如果以上任何环节出问题,直接中止算法。
      • 在U2\mathcal{U}_{2}U2​中除了u的其他客户端中,对于每个客户端v,可以使用密钥来解密认证加密过的eu,ve_{u, v}eu,v​,v′∥u′∥sv,uSK∥bv,u←AE.dec⁡(KA.agree⁡(cuSK,cvPK),ev,u)v^{\prime}\left\|u^{\prime}\right\| s_{v, u}^{S K} \| b_{v, u} \leftarrow \operatorname{AE.dec}\left(\mathrm{KA} . \operatorname{agree}\left(c_{u}^{S K}, c_{v}^{P K}\right), e_{v, u}\right)v′∥u′∥sv,uSK​∥bv,u​←AE.dec(KA.agree(cuSK​,cvPK​),ev,u​),得到这四个结果,其中前两个首先验证一下是否满足u=u′∧v=v′u=u^{\prime} \wedge v=v^{\prime}u=u′∧v=v′,如果不满足说明发错了,或者信息丢失了,直接中止算法。
      • 将得到的两个share有选择的发送给服务器,对于客户端 v∈U2\U3v \in \mathcal{U}_{2} \backslash \mathcal{U}_{3}v∈U2​\U3​,即掉线的客户端,发送Sv,uSKS_{v, u}^{S K}Sv,uSK​ , 对于v∈U3v \in \mathcal{U}_{3}v∈U3​,即正常的客户端,发送bv,ub_{v, u}bv,u​
    • 对于服务器端:

      • 收集从客户端中收集最少t个信息,这群客户端记为U5\mathcal{U}_{5}U5​,否则中止算法

      • 对于客户端 v∈U2\U3v \in \mathcal{U}_{2} \backslash \mathcal{U}_{3}v∈U2​\U3​,即掉线的客户端,使用秘密分享中的恢复算法,恢复出suSKs_{u}^{S K}suSK​,即,suSK←SS.recon⁡({su,vSK}v∈U5,t)s_{u}^{S K} \leftarrow \operatorname{SS.recon}\left(\left\{s_{u, v}^{S K}\right\}_{v \in \mathcal{U}_{5}}, t\right)suSK​←SS.recon({su,vSK​}v∈U5​​,t),将它送入PRG计算出针对他和其他客户端的所有pv,up_{v,u}pv,u​

      • 同理,对于v∈U3v \in \mathcal{U}_{3}v∈U3​,即正常的客户端,使用秘密分享中的恢复算法,恢复出bub_ubu​,bu←SS.recon⁡({bu,v}v∈U5,t)b_{u} \leftarrow \operatorname{SS.recon}\left(\left\{b_{u, v}\right\}_{v \in \mathcal{U}_{5}}, t\right)bu​←SS.recon({bu,v​}v∈U5​​,t),送入PRG计算出自己的pup_upu​。

      • 最后计算出聚合后的结果,即z=∑u∈U3xuz=\sum_{u \in \mathcal{U}_{3}} x_{u}z=∑u∈U3​​xu​,

        ∑u∈U3xu=∑u∈U3yu−∑u∈U3pu+∑u∈U3,v∈U2\U3pv,u\sum_{u \in \mathcal{U}_{3}} x_{u}=\sum_{u \in \mathcal{U}_{3}} y_{u}-\sum_{u \in \mathcal{U}_{3}} p_{u}+\sum_{u \in \mathcal{U}_{3}, v \in \mathcal{U}_{2} \backslash \mathcal{U}_{3}} p_{v, u}∑u∈U3​​xu​=∑u∈U3​​yu​−∑u∈U3​​pu​+∑u∈U3​,v∈U2​\U3​​pv,u​

几个需要注意的地方

  • 该算法原理是针对隐私数据向量xux_uxu​,对他进行一个mask的操作,mask主要分为两块,一块是pup_upu​,另一块是pu,vp_{u,v}pu,v​,这两个mask可以保证隐私数据不被服务器所获取。
  • 服务器是否可以通过欺骗其他客户端以套取某一个客户端的信息?答曰,不行,因为有第3轮的认证加密机制,会在第4轮被客户端发现从而发现发送的客户端集合不一致导致算法中止。
  • 在恢复阶段,诚实用户v不会把关于某个用户u的信息bub_ubu​和suSKs_u^{SK}suSK​的秘密同时说出去,对于在线的用户u,则会说出bub_ubu​,不在线的用户,会说出suSKs_u^{SK}suSK​,bub_ubu​和suSKs_u^{SK}suSK​就是用来生成pup_upu​和pu,vp_{u,v}pu,v​的。
  • 如果没有pup_upu​,那么如果用户不是真的掉线,而是延迟较大,在服务器已经得到所有的pu,vp_{u,v}pu,v​时,他就可以恢复出该用户的数据,有了pup_upu​就难以恢复出数据。

参考资料

1.论文Practical Secure Aggregation for Privacy-Preserving Machine Learning

2.https://blog.csdn.net/qq_37734256/article/details/104223580

3.https://www.cnblogs.com/20189223cjt/p/12529827.html

4.课件:http://jakubkonecny.com/files/2018-01_UW_Federated_Learning.pdf

5.ACM CCS视频:https://www.youtube.com/watch?v=OCy9gPjl-XM&t=1153s

联邦学习入门(二)-Practical Secure Aggregation for Privacy-Preserving Machine Learning论文算法详解相关推荐

  1. 【Java学习笔记之十一】Java中常用的8大排序算法详解总结

    分类: 1)插入排序(直接插入排序.希尔排序) 2)交换排序(冒泡排序.快速排序) 3)选择排序(直接选择排序.堆排序) 4)归并排序 5)分配排序(基数排序) 所需辅助空间最多:归并排序 所需辅助空 ...

  2. 联邦学习入门(一)-Advances and Open Problems in Federated Learning详解

    本文主要是联邦学习的入门级笔记,主要参考了论文Advances and Open Problems in Federated Learning和微众银行的联邦学习白皮书,笔者作为初次接触该领域的小白, ...

  3. [联邦学习] FedAvg聚合算法详解及代码实现

    该文章首发于若绾 [联邦学习] FedAvg聚合算法详解及代码实现,转载请标注出处. 论文原文:Communication-Efficient Learning of Deep Networks fr ...

  4. 【机器学习】集成学习及算法详解

    集成学习及算法详解 前言 一.随机森林算法原理 二.随机森林的优势与特征重要性指标 1.随机森林的优势 2.特征重要性指标 三.提升算法概述 四.堆叠模型简述 五.硬投票和软投票 1.概念介绍 2.硬 ...

  5. c linux time微秒_学习linux,看这篇1.5w多字的linux命令详解(6小时讲明白Linux)

    用心分享,共同成长 没有什么比每天进步一点点更重要了 本篇文章主要讲解了一些linux常用命令,主要讲解模式是,命令介绍.命令参数格式.命令参数.命令常用参数示例.由于linux命令较多,我还特意选了 ...

  6. SAP UI5 初学者教程之二十六 - OData 服务配合 Mock 服务器的使用步骤详解试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 初学者教程之一:Hello World SAP UI5 初学者教程之二:SAP UI5 ...

  7. 离线强化学习(Offline RL)系列3: (算法篇) IQL(Implicit Q-learning)算法详解与实现

    [更新记录] 论文信息:Ilya Kostrikov, Ashvin Nair, Sergey Levine: "Offline Reinforcement Learning with Im ...

  8. 离线强化学习(Offline RL)系列3: (算法篇) AWAC算法详解与实现

    [更新记录] 论文信息:AWAC: Accelerating Online Reinforcement Learning with Offline Datasets [Code] 本文由UC Berk ...

  9. 【ros学习】14.urdf、xacro机器人建模与rviz、gazebo仿真详解

    一.起因 学校的这学期课程是ros机器人开发实战,我们学习小组也要搞一个自己的机器人模型,我们组又叫葫芦组,所以我就做了个葫芦形状的机器人,虽说有点丑,本来想用maya建模再导入的,奈何不太懂maya ...

最新文章

  1. winform窗体自由拖拽控件
  2. 实践:大规模混合部署项目在字节跳动的落地
  3. 使用 Oracle GoldenGate 进行实时数据集成
  4. JAVA技术周刊第一期:关于JVM你了解多少?看这篇文章就够了!
  5. Linux、Windows、Mac下Docker的安装与使用
  6. 鱼眼镜头的distortion校正【matlab】
  7. Openstack的RPC通信代码调用架构
  8. Spring AOP 功能使用详解
  9. calc(~,mac电脑set-cookies要域名和请求域名相同
  10. 详解display:inline | block |inline-block的区别(转)
  11. c++编写函数判断整数的位数
  12. deepin15.9的linux内核版本,深度Deepin 15.9操作系统首次更新内容
  13. html中选择器的优先级别是,CSS优先级之计算选择器的特殊性( Selector’s specificity )...
  14. 【Netty】Netty+springboot实现IM即时通讯服务端
  15. 2020牛客暑期多校训练营(第九场)——Groundhog and 2-Power Representation
  16. 呼叫系统的技术实现原理和运作流程
  17. 2021年推土机司机(建筑特殊工种)考试及推土机司机(建筑特殊工种)找解析
  18. Ubuntu 桌面便签小工具-Indicator Stickynotes
  19. 管理经济学简答题、计算题与案例分析题
  20. 蒙太奇服务器维修,蒙太奇服务器多台互连导片方法

热门文章

  1. 客户管理系统,电销企业应该怎么选
  2. 【linux】循序渐进学运维-服务篇-nginx的虚拟主机
  3. nuxt。js 部署上线后页面一直加载_应用系统增量部署通用流程实践
  4. axios 拦截器分析
  5. 无人驾驶---1 激光雷达的地面-非地面分割和pcl_ros实践
  6. 区间二分法(Bisection Method)迭代求根的python程序
  7. c语言设计模式代码完整实现-状态机模式
  8. 重建大师技术分享:多人协同刺点
  9. oracle查看所有报表,Oracle Report Viewer 以及怎么查看Oracle 报表
  10. 笔记本连接投影仪常见问题及解决方案