有关 Session 的那些事儿
haoteby 2024-11-18 12:51 21 浏览
原文链接: https://blog.by24.cn/archives/about-session.html
Web 开发中,Session 是经常用到的概念,但是在日常交流中,似乎又经常引起误解。在我看来,引发误解的原因主要有两个:
- 大量使用简称,导致混淆了 数据 与 索引
- 不同 语言/框架 对 Session 做了不同形式的封装,导致特征不同
下面,我就尝试着捋一下整个问题,看看能不能尽量消除这些误会。
注:其它通讯领域也有使用 Session 这个词汇,本文仅探讨 Web 开发中的 Session 使用。
TL;DR
本文确实冗长无比,此处列出简要观点方便『太长不看』(Too Long; Didn't Read.)的读者。
- 除了常见的 Server Side Session,应当注意还有 Client Side Session 的存在
- Session 是虚拟概念,应当注意区分 Session-ID 和 Session-Data
- Session 的具体实现,与 Cookies 没有绑定关系
- JWT 和 Session 压根不在一个维度,不应该放在一起讨论
Session 是什么
名词约定
由于大家使用的『简称』含义往往差别很大,在此我们先约定一些称呼。
这些称呼可能和你惯用的不太一样,但统一定义是高效讨论的重要基础,还请不要抗拒。
Session
Session 经常被翻译为 『会话』,其实还是很贴切的,它代表了『一次』相互沟通,这次会话中,可能包括了多次通信。
需要注意的是:此处 Session 是一个整体性的虚拟概念,不代指任何实体或数据。
Session-Data
看到 Data 后缀就知道,这个词代表了一次 Session 中,暂存或使用 的一些数据。
具体是什么数据取决于业务实现,例如可能存储了一些用户的 ID 信息或短效配置等。
需要注意的是,此处指的是数据的『实体』,而不是『索引』。
Session-ID
上面说到的数据实体 Session-Data ,未必会直接放在程序内部,而是可能存储在某一个具体的位置(后文会详细讨论)。
在大部分 Server Side Session 实现中(即 Session-Data 存储在服务端时),为了方便取用,就需要记录一个『索引』,用来找到具体的 Session-Data ,我们将这个『索引』称为 Session-ID 。
需要注意的是:在某些实现中,可能并没有 Session-ID 的概念(例如 Client Side Session)。
存储的差异
正如我前面所强调的,在不同的实现中,Session-Data 可能存储在不同的位置:可能在客户端,也可能在服务端。
Server Side Session
在大部分实现中,并不会直接传输 Session-Data ,而是传输它的索引:Session-ID ,服务端程序收到 Session-ID 后,从存储着 Session-Data 的地方取出具体的 Session-Data 来使用。
在这种模式下,更容易被我们接触到的,其实是存储在 Cookies 里的 Session-ID ,而许多人存在的一个误区,便是将 Session-ID 直接等同于 Session 本身,无视了 Session-Data 的存在,这其实是不正确的。
上图表示了这种模式的大致运行方式,绿色的 Session 是一个老用户会话, Alice 在访问时发送了自己持有的 Session-ID AA01 来标记当前会话,服务器程序根据相应的 Session-id 查询出了相应的 Session Data ,并在程序中应用。
对于新用户,因为自己没有存储 Session-id,只好不发送,服务器为了标记本次会话,生成了新的 Session-ID NN02 并返回给客户端,这样就开启了一个新的会话,图中用红色 Session 表示。
Client Side Session
除了上面提到的常见模式,其实还存在一种实现被许多人忽视,服务端并不存储 Session-Data,而是直接将 Session-Data 存储在客户端(浏览器)的 Cookies 里。
在这种情况下,你甚至找不到 Session-ID 的存在,许多人也会因此而搞不清 Session 究竟存储在哪里。
上图表示了这种模式的大致运行方式,与上面那种方式不同的地方在于,客户端直接告诉服务端,我自己是 Alice ,我的基本信息是 xxx ,于是服务端使用这些数据返回了相应的页面。
对于新用户,本地一丁点儿信息都没有,于是服务端返回了一个空的 Session-Data 用来初始化,在后续的通信中,这个 Session-Data 会被继续填充上有意义的数据。
很显然,这里存在着巨大的安全问题:我直接修改存储在本地的 Session-Data,声称我自己是管理员怎么办?
为此,Client side session 需要加入加密机制或签名机制来进行校验,以确保所有的 Session-Data 都是由服务端生成的,具体会在下文讨论。
和 Cookies 的关系
提到 Session 就不能不提 Cookies,毕竟很多人会条件反射的把这二者弄混(苦笑脸)。
Cookies 自身就有非常多的玩儿法,抛开那些权限相关的内容,此处我们先只它最基础的几个特性:
- 在客户端(浏览器)内存储一些数据(而不是在服务端)
- 这些信息,可以由服务端设置,也可以由客户端设置
- 当用户发起 HTTP 请求时,匹配的 Cookies 会嵌入请求头部(Header)一起发送
正是这些特征,让 Cookies 成为存储 Session-Data / Session-ID 的绝佳场所,在几乎所有的常见实现中,均默认使用 Cookies 来存储 Session 相关信息。
但是需要注意,Session-Data / Session-ID 选择 Cookie 作为容器只是因为『方便』,但这并不是必然情况:
我们完全可以实现一个 Web 框架,它使用 LocalStorage 来存储 Session-Data / Session-ID ,在发起 XHR 请求时,主动将其加进 Header 内(甚至 URL 内),服务端则从相应位置读取信息完成业务逻辑,在这套框架体系内这是完全没有问题的。
实际上,许多框架都支持 Session with out Cookies 的实现,虽然存在各种各样的小问题或限制,但是确实可用。
因此,需要再次强调:对于 Session 机制来说,Session-Data / Session-ID 才是本体,Cookies 只是一个存储容器,这二者没有捆绑关系。
Session 机制的实现
对于 Session 机制,之所以总是产生种种误解, 和各大 语言 / 框架 的实现有着很大的关联,由于大部分人都更熟悉自己常用的框架,导致很容易被当前框架的思路带进去。
那么接下来,就看一下各种常用 语言 / 框架 在 Session 机制上的实现与差异。
注:以下只是各个 语言 / 框架 默认支持的实现,实际中我们完全可以自定义任何形式的实现。
PHP
原生 PHP
PHP 不愧是为 Web 而生的语言,在语言层面就提供了 Session 机制的支持,按默认配置就可以使用 $_SESSION 这个超全局变量。
原生 PHP 默认使用的 Session-Data 存储方式为 Server Side Session ,使用 Session-ID 进行索引,Session-ID 默认存储在 Cookies 的 PHPSESSID 字段。
Session-Data 默认以文件形式存储在 session_save_path 所配置的路径中, PHPSESSID字段的内容就是文件名,文件内容就是序列化后的 Session-Data 数据。
通过配置 session.save_handler 字段,我们可以将存储方式切换为 redis 或者 memcache 。
以 redis 为例,Session-Data 会被存储在PHPREDIS_SESSION:PHPSESSID 中,PHPREDIS_SESSION:为默认的统一前缀。
除此之外,还有一些其它的配置可以参考官方文档,原生 PHP 也支持其它的相关扩展,不再一一赘述。
Laravel
Laravel 果然也很强,不但默认提供了常规的 Server Side Session ,也支持配置为 Client Side Session 形式。
通过配置 config/session.php 的 driver 字段,可以切换 file 、cookie 、database 、memcached 、redis 、array 这 6 种不同的存储方式。
这里面, file 、memcached 、redis 都是比较常见的存储方式,就不再赘述。
database 可以将 Session-Data 存储在你定义的数据库内,但是你需要先建好一张表,里面配置好要存储的字段。
array 是一种比较奇怪的方式,只是用在开发阶段,它并没有任何持久化功能,随着 PHP 脚本的运行结束而消失。
cookie 则是比较少见的** Client Side Session**了,Laravel 使用 EncryptedStore.php 对 Session-Data 进行 『加密』后存储于客户端浏览器的 Cookies 中。
这里需要注意,Laravel 在实现 **Client Side Session **的时候使用的是 『加密』方式来保障信息安全。
CodeIgniter
接下来看看老牌框架 CI 怎么样,翻看 配置文档 可以看到,CI4 默认支持 file 、database 、memcached 、redis 这几种 Server Side Session,看起来似乎平平无奇,都是常见套路。
但是文档上有一行备注:
In previous CodeIgniter versions, a different, “cookie driver” was the only option and we have received negative feedback on not providing that option. While we do listen to feedback from the community, we want to warn you that it was dropped because it is unsafe and we advise you NOT to try to replicate it via a custom driver.
这就很有意思了,看起来 CI 框架以前也支持Client Side Session 嘛?翻出了 CI2 的文档 ,果然里面默认使用 Cookies 存储 Session-Data ,也提供了加密的选项。
那为什么 CI2 当时会因为这个被锤呢?我找到一篇相关的文章:(IN)secure session data in CodeIgniter - Websec ,文章中的观点来看:
- 虽然提供了对 Cookies 进行加密的选项,但默认配置是关闭的,这就导致了 Session-Data 会以明文展示,可能会泄漏信息。
- 虽然 CI2 在加密的基础上,还对明文 Session-Data 进行了签名,但是在用户配置了弱密钥的情况下,签名密钥可能会被暴力破解,导致 Session-Data 被篡改。
其实这两个问题都更应该归咎于用户的配置不当,但是毕竟和框架的默认配置有关,被锤也就在情理之中了。
可能也是因为这件事,才导致了 CI 现在只提供 Server Side Session 实现吧。
其它 PHP 框架
翻看了一下 Symfony、Yii 、CakePHP、ThinkPHP 等热门框架关于 Session 的文档,都使用了比较常规的 Server Side Session 方式,也都支持将 Session-Data 存储在文件、数据库、内存数据库等,在此就不一一介绍了,有需要的可以点链接查看相应文档。
Java
Tomcat
Tomcat 也是 Server Side Session 一派,默认在 Cookies 中使用 JSESSIONID 字段存储 Session-ID,Session-Data 默认存储在内存中 ,翻阅 org.apache.catalina.session.ManagerBase 的源码,可以看到它使用了一个ConcurrentHashMap 结构来存储所有的 Session-Data 实例。
Tomcat 默认使用 StandardManager 来管理 Session-Data,当容器退出时,在退出时将所有的数据进行持久化;你也可以选择使用 PersistentManager 来管理,它可以更加灵活的执行持久化(但是 Session-Data 还是会存储在你的内存里)。
Jetty
Jetty 也比较中规中矩,参照文档来看,支持将 Session-Data 配置为存储在 内存、文件、JDBC 等位置。
比较不同的是,Jetty 支持为 Session-Data 配置 L1、 L2 两级缓存,在对性能有需求的场景下还是挺友好。
Spring Session
果然是家大业大的框架,Spring Session 自己就是个完整的组件了,毫无意外的支持把 Session-Data 储存在 redis 、mongodb ,jdbc 等地方,方便扩展。
不知如此,它还有一些特有的特性:
- 支持将 Session-ID 配置在 Request Header 的 X-Auth-Token 字段,更好的兼容 RESTful API
- 可以在 WebSockt 中使用 ,且可以和 HTTP 侧的 Session 机制相互配合使用
- 可以和 Spring Security 配合,实现更多权限管控相关功能
在旧一些的版本中,Spring Session 还支持 在一个浏览器会话中,维护多个 Session ,不过在新版本中,这个特性被删除了,也许以后才会加回来。
这个巨无霸框架其实还支持其它一大堆特性,这里就不太多介绍了。
Python
Django
作为一个功能完善的重量级框架,Django 默认直接将 Session-Data 存储在了数据库,当然,你也可以配置为其它的存储方式,不再赘述。
这些存储方式中,Client Side Session 的代表 Cookies 再次出现,Session-Data 在序列化并『签名』后,存储在客户端 Cookies 中。
这里需要注意,此处是『签名』而不是『加密』,也就是说,客户端虽然不能篡改 Session-Data,但是可以自己反序列化后查看内容。
另外,Django 的 Session-data 还支持使用 Pickle 进行序列化,对于某些场景会非常有用(同时也带来了一些潜在风险)。
Flask
翻下文档,专注于轻量的框架 Flask ,毫无疑问的选择了实现起来最简单的方案:直接用Client Side Session 就行了。
把 Session-Data 序列化并『签名』后,存储在客户端 Cookies 中就搞掂啦。这里同样需要注意,是『签名』而不是『加密』。
不过这毕竟是个 Python 框架,人家包多啊,使用 Flask-Session 就可以获得更多功能,支持将 Session-Data 存储在Cookies、文件、Redis、数据库等各种地方。
Tornado
这个已经接近凉凉的框架表示:不好意思,我们没有现成的 Session 模块,想要用?那你自己写去~
Why doesn't Tornado have session - Stack Exchange
Node.js
Express
Express 自身是最小化实现,Session 相关的功能由各种中间件来实现,此处主要提两个中间件。
express-session 是典型的 Server Side Session 实现,依靠强大的包数量支持很多种存储后端。
比较有意思的是,虽然是 Server Side Session 实现,express-session 依然不放心,给 Cookies 里的 Session-ID 加了一层『签名』做校验,可以说非常心细了。
cookie-session 相对就比较普通,就是一个常规的 Client Side Session 实现, Session-Data 在序列化并『签名』后,存储在客户端的 Cookies 内。
Koa.js
包多就是好啊,koa 也有一大堆 Session 相关的中间件,在此就不一一介绍了。
Go
Gin
翻了下 Gin 比较常用的中间件是 gin-contrib/sessions ,同时支持 Server / Client Side Session ,也支持多种储存后端。
这个中间件默认支持在一个浏览器会话中,维护多个 Session ,对特定的需求会方便一些。
Echo
Echo 的文档中,默认使用了 gorilla/sessions 这个中间件,也是同时支持 Server / Client Side Session ,可以扩展支持其它存储后端。
这个中间件也默认支持在一个浏览器会话中,维护多个 Session多个 Session ,对特定的需求会方便一些。
Beego
按照 Beego 的英文文档来看,默认只支持 Server Side Session,但是奇怪的是中文文档上却又写着支持的后端引擎包括 Cookie ,可能是文档写错了。
Ruby
Rails
Rails 默认使用 Client Side Session 实现,Session-Data 『加密』并『签名』后存储在客户端浏览器的 Cookies 中。也支持配置为 Server Side Session 实现,可以使用多种后端存储。
在其它许多框架的 Session 实现中,都可以看到一个叫做 Flash 的特殊数据,用于实现轻量的通知反馈等,这个功能应该是 Rails 首创的。
另外,Rails 关于 Session 安全的文档写的非常非常细致,强烈建议阅读。
Session 机制的使用
不同实现方式的对比
Session-Data 存储在何处
Sever Side Session
Sever Side Session 是目前最常见的 Session 实现,以至于不少人会误以为它是唯一一种 Session 实现。
优点:
- 数据存储在服务端,相对来说安全性更强
- 请求时只需要传递 Session-ID,减小流量开销
- 可以方便的吊销 Session,管控 Session 策略
劣势:
- Session-Data 集中管理,不利于并行化,需要专门解决 Session 共享问题
- Session-Data 需要占用服务端内存 / 存储,对服务端存在压力
Client Side Session
Client Side Session 相对小众一些,但也可以看到许多框架都保留了对它的支持,Rails 甚至在文档中专门写了一大段来描述为什么自己默认这么用。
优点:
- 实现简单,无须过多考虑 Session 存储、查询
- 将存储压力转移到了客户端,这样可以减小服务端的资源消耗
- 多机并行时,不需要考虑 Session 共享问题,完美支持高并发
劣势:
- Cookies 默认有 4KB 限制,不能存储太多内容
- Cookies 会在每次请求时被默认携带,存储较多内容时,增大了流量消耗
- 存在重放攻击的风险,客户端可能会将数据替换为合法的旧数据
- 实现 Session 数据拉黑、强制失效等功能时比较复杂
- 数据需要『签名』或『加密』后才能确保安全,这需要开发者正确配置
- 部分实现只对数据进行了『签名』,客户端可以直接查看到数据内容,存在安全风险
Session-ID 如何进行传递
对于 Server Side Session 来说,绝大部分实现都会选择将 Session-ID 放在 Cookies 中,在发起请求时自动带上,这样也是很符合直觉的做法,但是凡事都有例外,还是有一些特殊的实现。
重写进 URL 中
例如,PHP 默认就支持抛开 Cookies,使用 URL 传递 Session-ID,相关的配置为 use_cookies 、 use_only_cookies 字段。
在 Tomcat 6.0 中,也支持类似的功能,但似乎是通过当前会话是否存在 Cookies 来判断的,也可以通过配置 disableURLRewriting 来关闭。
当然也存在不同的声音,比如 Django 就明确拒绝这么做,为此还在文档中专门写了原因 ,其一是make URLs ugly,其二是不安全。
除此之外,在 WAP 时代,由于早期手机浏览器很多不支持 Cookie,这种行为也非常常见。
必须要说的是,这样确实很不安全,比如可以参考这篇 9 年前的文章:浅谈WAP网站安全 - 空虚浪子心 。
放进 Header 中的其它字段
其实前面在说到 Spring 的时候就有提到,它支持将 Session-ID 放进 Header 中的X-Auth-Token 字段中。
实际上,只要前后端协调一致,其实放在任何一个字段都是没问题的。
放进隐藏的表单字段
这种处理方式我只在资料中看到过,大意就是在输出页面时,为每一个 Form 都补一个隐藏的 Session-ID 字段。
<form name="anyform" action="/xxx">
<input type="hidden" name="jsessionid" value="ByOK2vjFD43aPnrF6C2HmdnV6QZcEbzWoWiBYEnLerjQ22zWpBng!-1582381342">
<input type="text">
</form>
这种方式存在非常多的弊端,也难怪现在基本上看不到了。
Session-Data 的安全如何保障
对于 Client Side Session 来说,因为不需要索引,只需要看好 Session-Data 就好,在这件事上,大家也有不一样的思路。
大部分实现都选择只对 Session-Data 做『签名』处理,这样就算客户端知道 Session-Data 内部的数据是什么,也无法简单的篡改。
但是并不是所有的开发者都会注意这些,保不齐会有人存敏感信息在里面,所以部分框架不但做了签名,还做了『加密』,看也不让看。
需要注意的是,这样仍然存在重放攻击的风险,用户可以将其替换为旧的合法 Session-Data,此时服务端是无法鉴别出差异的。
另外,Rails 关于 Session 安全的文档写的非常非常细致,再次强烈建议阅读。
Session 相关的误解
Session 的概念并不复杂,洋洋洒洒写了这么多,其实还是因为讨论时遇到的各种误解,这里我们复盘一下。
Session ID 就是 Session 嘛?
不能等同,Session-ID 只是 Server Side Session 实现中,常用的索引信息。
Cookies 就是 Session 嘛?
Cookies 经常用于承载 Session 的信息(ID 或 Data),但并不是必然情况。
Session 是不是认证方式?Session 和 Token 的关系是什么?
这是经常被误解的一点,在此说下我个人的理解。
Token 并不是一个以技术手段定义的词汇,它的定义来源于『用途』。
但凡用于认证鉴权的 凭据,我认为都可以称作 Token ,与这个凭据的生成方式无关。
基于此,如果 Session-ID 或 Session-Data 被用作认证,那么我认为这条数据就可以被称为 Token。
但是需要注意,Session 自身是个虚拟概念,与 Token 毫不相关。
Session 和 JWT 的关系是什么?
但凡提到 Session,就不可避免的会有人搬出 JWT ,也会有很多人把 Session 和 JWT 摆到对立面来讨论,但是这两个压根就不在一个维度。
先来看看 JWT 到底是什么,它的全名是 JSON Web Token ,从名字就可以看出,它是特定的一种 Token 生成方式。
JWT 的生成方式大致是这样:将数据按特定格式进行序列化,标记过期时间,对数据进行『签名』后编码为 URL Safe 的 Base64URL 。
诶?好像有点眼熟?这怎么和 Client Side Session 那边对 Session-Data 的处理那么像呢?
除此之外,这里有一篇蛮有名的文章:Stop using JWT for sessions 也可以一起看看。
看完文章会发现,作者提到的那些将 JWT 用做 Session 实现的人,实际上说的都是** Client Side Session**啊。
那么问题变的比较清晰了:
- JWT 只是一种处理数据的手法,它通过签名保证了信息的不可篡改,特定的格式也具备其它一些小特性。
- JWT 的特性,让它具备成为 Client Side Session 的数据处理方式的潜力,也确实有人这么做了。
而那些关于 JWT 与 Session 优劣的争论,实际上大部分都是在争论 Sever Side Session 和 Client Side Session 的优劣。
总结复盘
扯了冗长的上万字,其实就是为了说明一个『定义』的问题,Session 本身并不复杂,只要不把各种简称搞混。
很多时候由于大量使用简称,或者模糊定义,导致我们对概念本身失去了掌握,也经常让讨论变的低效。
另一方面,不能把自己所熟悉的那些实现当作全部,时不时的看看其它语言或框架,也许能发现许多好玩儿的东西。
最后,如果本文有哪些地方出现了疏漏或错误,还请不吝赐教,一起交流进步。
相关推荐
- 一日一技:用Python程序将十进制转换为二进制
-
用Python程序将十进制转换为二进制通过将数字连续除以2并以相反顺序打印其余部分,将十进制数转换为二进制。在下面的程序中,我们将学习使用递归函数将十进制数转换为二进制数,代码如下:...
- 十进制转化成二进制你会吗?#数学思维
-
六年级奥赛起跑线:抽屉原理揭秘。同学们好,我是你们的奥耀老师。今天一起来学习奥赛起跑线第三讲二进制计数法。例一:把十进制五十三化成二进制数是多少?首先十进制就是满十进一,二进制就是满二进一。二进制每个...
- 二进制、十进制、八进制和十六进制,它们之间是如何转换的?
-
在学习进制时总会遇到多种进制转换的时候,学会它们之间的转换方法也是必须的,这里分享一下几种进制之间转换的方法,也分享两个好用的转换工具,使用它们能够大幅度的提升你的办公和学习效率,感兴趣的小伙伴记得点...
- c语言-2进制转10进制_c语言 二进制转十进制
-
#include<stdio.h>intmain(){charch;inta=0;...
- 二进制、八进制、十进制和十六进制数制转换
-
一、数制1、什么是数制数制是计数进位的简称。也就是由低位向高位进位计数的方法。2、常用数制计算机中常用的数制有二进制、八进制、十进制和十六进制。...
- 二进制、十进制、八进制、十六进制间的相互转换函数
-
二进制、十进制、八进制、十六进制间的相互转换函数1、输入任意一个十进制的整数,将其分别转换为二进制、八进制、十六进制。2、程序代码如下:#include<iostream>usingna...
- 二进制、八进制、十进制和十六进制等常用数制及其相互转换
-
从大学开始系统的接触计算机专业,到现在已经过去十几年了,今天整理一下基础的进制转换,希望给还在上高中的表妹一个入门的引导,早日熟悉这个行业。一、二进制、八进制、十进制和十六进制是如何定义的?二进制是B...
- 二进制如何转换成十进制?_二进制如何转换成十进制例子图解
-
随着社会的发展,电器维修由继电器时代逐渐被PLC,变频器,触摸屏等工控时代所替代,特别是plc编程,其数据逻辑往往涉及到数制二进制,那么二进制到底是什么呢?它和十进制又有什么区别和联系呢?下面和朋友们...
- 二进制与十进制的相互转换_二进制和十进制之间转换
-
很多同学在刚开始接触计算机语言的时候,都会了解计算机的世界里面大多都是二进制来表达现实世界的任何事物的。当然现实世界的事务有很多很多,就拿最简单的数字,我们经常看到的数字大多都是十进制的形式,例如:我...
- 十进制如何转换为二进制,二进制如何转换为十进制
-
用十进制除以2,除的断的,商用0表示;除不断的,商用1表示余0时结束假如十进制用X表示,用十进制除以2,即x/2除以2后为整数的(除的断的),商用0表示;除以2除不断的,商用1表示除完后的商0或1...
- 十进制数如何转换为二进制数_十进制数如何转换为二进制数举例说明
-
我们经常听到十进制数和二进制数,电脑中也经常使用二进制数来进行计算,但是很多人却不清楚十进制数和二进制数是怎样进行转换的,下面就来看看,十进制数转换为二进制数的方法。正整数转二进制...
- 二进制转化为十进制,你会做吗?一起来试试吧
-
今天孩子问把二进制表示的110101改写成十进制数怎么做呀?,“二进制”简单来说就是“满二进一”,只用0和1共两个数字表示,同理我们平常接触到的“十进制”是“满十进一”,只用0-9共十个数字表示。如果...
- Mac终于能正常打游戏了!苹果正逐渐淘汰Rosetta转译
-
Mac玩家苦转译久矣!WWDC2025苹果正式宣判Rosetta死刑,原生游戏时代终于杀到。Metal4光追和AI插帧技术直接掀桌,连Steam都连夜扛着ARM架构投诚了。看到《赛博朋克2077》...
- 怎么把视频的声音提出来转为音频?音频提取,11款工具实测搞定
-
想把视频里的声音单独保存为音频文件(MP3/AAC/WAV/FLAC)用于配音、播客、听课或二次剪辑?本文挑出10款常用工具,给出实测可复现的操作步骤、优缺点和场景推荐。1)转换猫mp3转换器(操作门...
- 6个mp4格式转换器测评:转换速度与质量并存!
-
MP4视频格式具有兼容性强、视频画质高清、文件体积较小、支持多种编码等特点,适用于网络媒体传播。如果大家想要将非MP4格式的视频转换成MP4的视频格式的话,可以使用MP4格式转换器更换格式。本文分别从...