开源库源码分析:Okhttp源码分析(一)
开源库源码分析:OkHttp源码分析
导言
接下来就要开始分析一些常用开源库的源码了,作为最常用的网络请求库,OkHttp以其强大的功能深受Android开发者的喜爱(比如说我),还有对该库进行二次封装而成的热门库,比如说Retrofit。本文我们将从源码入手看看OkHttp是如何运作的。注意本文解析的是OkHttp3库,该库是用Kotlin写的,需要大家有一些Kotlin基础。
OkHttp的最佳使用
这个问题是在OkHttp3的OkHttpClient
中的注释中发现的:
这段注释中提到了OkHttp最好是用单例的OkHttpClient来实现请求,我们可以对该单例进行复用。这是因为每一个OkHttpClient都会持有一个连接池和线程池,都称之为池了,那么其作用肯定就是为了复用。通过复用这些连接和线程我们可以显著地降低延迟和节约内存使用。
实际上这段注释中已经提到了OkHttp中的一些复用机制了,连接复用和线程复用。
调度类Dispatcher
调度类的线程池
OkHttp中核心类之一就是这个调度类Dispatcher
,所有的网络请求类Call
都需要通过这个调度类来执行任务。这个调度类是OkHttpClient类的成员变量,我们是在调度类中执行请求的,那我们就顺便再来介绍一下客户端类中的成员变量:
//调度类
@get:JvmName("dispatcher") val dispatcher: Dispatcher = builder.dispatcher
//连接池
@get:JvmName("connectionPool") val connectionPool: ConnectionPool = builder.connectionPool
//应用拦截器
@get:JvmName("interceptors") val interceptors: List<Interceptor> = builder.interceptors.toImmutableList()
//网络拦截器
@get:JvmName("networkInterceptors") val networkInterceptors: List<Interceptor> =builder.networkInterceptors.toImmutableList()
//事件监听工厂
@get:JvmName("eventListenerFactory") val eventListenerFactory: EventListener.Factory =builder.eventListenerFactory
这几个应当是最核心的几个成员变量,我们将在后面再接触它们。回归正题,我们继续看调度类,既然要执行Call,那么这个调度器类就一定会有线程池,这个线程池有两个手段可以设置:
private var executorServiceOrNull: ExecutorService? = null//线程池@get:Synchronized //在调用get时设置线程池为一个默认线程池,类似于一个CachedPool@get:JvmName("executorService") val executorService: ExecutorServiceget() {if (executorServiceOrNull == null) {executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))}return executorServiceOrNull!!}constructor(executorService: ExecutorService) : this() { //通过构造方法设置线程池this.executorServiceOrNull = executorService}
可以看到,方法一是调用构造函数来设置线程池,第二个方法就是在调用get方法时会自动设置一个效果类似于CachedPool
的线程池。
调度类执行请求
我们使用OkHttp时一般是两种方式,同步请求execute
和异步请求enqueue
,我们以异步请求为例来分析调度类是如何执行网络请求的。一般我们发送异步请求是这样的形式:
public static void main(String[] args) {OkHttpClient client = new OkHttpClient();Request r = new Request.Builder().build();Call call = client.newCall(r);call.enqueue(new Callback() {@Overridepublic void onFailure(@NonNull Call call, @NonNull IOException e) {}@Overridepublic void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {}});}
首先通过RequestBuilder
类来构建了一个请求类,然后将该请求传入newCall
方法中新创建出了一个Call
:
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
可以看到返回的是RealCall
类,该类实现了Call接口。接下来就从Call的enqueue
方法入手,这里实际上是RealCall
类中:
override fun enqueue(responseCallback: Callback) {check(executed.compareAndSet(false, true)) { "Already Executed" }callStart() //开启事件监听client.dispatcher.enqueue(AsyncCall(responseCallback)) //通过调度类入队}
这其中callStart
方法会开启事件监听,我们先不管这个事件监听,看最后一句通过调度器来入队,这个AsyncCall()
类是异步请求类,实现了Runnable
接口,这里实际上就是将我们传入的实现了Callback
的回调给包装成了AsyncCall。我们继续看Dispatcher中是如何入队的:
internal fun enqueue(call: AsyncCall) {synchronized(this) {readyAsyncCalls.add(call)// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to// the same host.if (!call.call.forWebSocket) {val existingCall = findExistingCallWithHost(call.host)if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)}}promoteAndExecute()}
首先为了确保线程安全,调度器先将其上锁然后将之前包装成的AsyncCall
加入到了readyAsyncCall
就绪队列中。中间这一段判断是用于处理与主机(host)相关的异步调用复用。如果 call 不是用于 WebSocket 调用,它会尝试查找是否已经存在一个与相同主机(host)相关的异步调用任务。如果找到了已经存在的异步调用,它会尝试共享这两个异步调用任务之间的状态。这是为了复用已经建立的连接,以提高性能和减少资源消耗。
最后会调用promoteAndExecute
方法执行,这个方法是执行请求的核心之一:
private fun promoteAndExecute(): Boolean {this.assertThreadDoesntHoldLock()val executableCalls = mutableListOf<AsyncCall>()//可执行的Callsval isRunning: Booleansynchronized(this) {val i = readyAsyncCalls.iterator()//获得一个迭代器while (i.hasNext()) { //开始迭代val asyncCall = i.next()if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.i.remove()asyncCall.callsPerHost.incrementAndGet()executableCalls.add(asyncCall)runningAsyncCalls.add(asyncCall)}isRunning = runningCallsCount() > 0}for (i in 0 until executableCalls.size) {val asyncCall = executableCalls[i]asyncCall.executeOn(executorService)}return isRunning}
这段代码的核心逻辑就是while循环之中,这很显然是一个通过迭代器开始遍历就绪队列readyAsyncCall
的过程,它会将就绪队列中的call移入到可执行队列executableCalls
和正在执行异步队列runningAsyncCalls
中。其中还有两句判断条件:
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
一句是判断正在运行队列没有超过最大Size,一句是判断每一句的最大执行数不超过最大值,这两个值在Dispatcher中都有定义。最后会跳入到for循环中并调用AsyncCall的executeOn
方法,并将线程池传入,也就是接下来就是在线程池中执行该异步请求了。我们最后来看这个executeOn
方法:
fun executeOn(executorService: ExecutorService) {client.dispatcher.assertThreadDoesntHoldLock()var success = falsetry {executorService.execute(this)success = true} catch (e: RejectedExecutionException) {val ioException = InterruptedIOException("executor rejected")ioException.initCause(e)noMoreExchanges(ioException)responseCallback.onFailure(this@RealCall, ioException)} finally {if (!success) {client.dispatcher.finished(this) // This call is no longer running!}}}
这个方法真是开门见山。在try语句的第一句处就通过线程池执行了该任务,还记得这个AsyncCall是由我们传入的Call回调包装而成吗?接下来就会回调到我们的函数之中去了。众所周知,由于executorService.execute(this)
这一句,线程池接下来会执行AsyncCall的run
方法了:
override fun run() {threadName("OkHttp ${redactedUrl()}") {var signalledCallback = falsetimeout.enter()try {val response = getResponseWithInterceptorChain() //1--通过拦截器链来获得响应signalledCallback = trueresponseCallback.onResponse(this@RealCall, response)//2--请求成功,回调到我们传入的onResponse方法中} catch (e: IOException) {if (signalledCallback) {// Do not signal the callback twice!Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)} else {responseCallback.onFailure(this@RealCall, e) //3--请求失败,回调到我们传入的onFailure方法之中去}} catch (t: Throwable) {cancel()if (!signalledCallback) {val canceledException = IOException("canceled due to $t")canceledException.addSuppressed(t)responseCallback.onFailure(this@RealCall, canceledException)}throw t} finally {client.dispatcher.finished(this)//结束本次请求}}}
我已经在代码之中加入一些注释了,在注释一处会通过拦截器链来获得响应,这个拦截器机制我们之后再将,这里只要知道它在这里获得了响应即可。在注释二处我们就可以看到这里显然是调用了我们传入的Call回调了,请求成功调用onResponse
回调,要是请求失败的话则回调到onFailure
方法之中去。最后在finally块中将本次请求结束,实际上这个finished
方法还会开启下一次循环:
internal fun finished(call: AsyncCall) {call.callsPerHost.decrementAndGet()finished(runningAsyncCalls, call)}private fun <T> finished(calls: Deque<T>, call: T) {val idleCallback: Runnable?synchronized(this) {if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")idleCallback = this.idleCallback}val isRunning = promoteAndExecute()if (!isRunning && idleCallback != null) {idleCallback.run()}}
由第一个函数会调转到第二个函数,从第二个函数来看,它首先会将本次的call从runningAsyncCalls
正在运行队列中移除,然后会继续调用我们之前提到过的promoteAndExecute
方法开启下一次循环,如果没有任务了的话并且空闲处理程序不为空的话,还会执行空闲处理程序。
拦截器链
之前提到网络请求的相应是通过拦截器链获得的:
val response = getResponseWithInterceptorChain()
首先,什么是拦截器呢?
在计算机编程领域,拦截器(Interceptor)是一种常见的设计模式,用于在软件系统的不同组件之间添加或修改功能。拦截器的主要目的是拦截请求或操作,允许在请求进入目标组件之前或之后执行自定义逻辑。
简单来说就是有许多个拦截器,一个请求的执行需要经过这一整条拦截器链,在一个请求到达一个拦截器的时候我们就可以判断是否要拦截这个请求并进行处理,简单来说就是类似于旅行途中的设置关口。Android中这种模式也运用,比如说广播
和View的事件分发和处理
,这都是拦截器模式的运用。
好了,言归正传,我们继续回到上面的方法上:
internal fun getResponseWithInterceptorChain(): Response {// Build a full stack of interceptors.val interceptors = mutableListOf<Interceptor>() //创建拦截器集合interceptors += client.interceptors //添加用户设置的应用拦截器interceptors += RetryAndFollowUpInterceptor(client) //负责重试和重定向 interceptors += BridgeInterceptor(client.cookieJar) //用于桥接应用层和网络层的请求数据interceptors += CacheInterceptor(client.cache) //用于处理缓存interceptors += ConnectInterceptor //网络连接拦截器,用于获取一个链接if (!forWebSocket) {//添加用户设置的网络拦截器interceptors += client.networkInterceptors}interceptors += CallServerInterceptor(forWebSocket)//用于请求网络并获取网络响应//创建责任链val chain = RealInterceptorChain(call = this,interceptors = interceptors,index = 0,exchange = null,request = originalRequest,connectTimeoutMillis = client.connectTimeoutMillis,readTimeoutMillis = client.readTimeoutMillis,writeTimeoutMillis = client.writeTimeoutMillis)var calledNoMoreExchanges = falsetry { //启动责任链val response = chain.proceed(originalRequest)if (isCanceled()) {response.closeQuietly()throw IOException("Canceled")}return response} catch (e: IOException) {calledNoMoreExchanges = truethrow noMoreExchanges(e) as Throwable} finally {if (!calledNoMoreExchanges) {noMoreExchanges(null)}}}
具体代码的含义已经写在注释中了,首先要提醒大家上面的+=
号是Kotlin的重载,就是相当于添加集合。其实把上面的方法切分一下的话主要就是做了两件事:创建和整理拦截器集合 和 创建并启动责任链。下面是一张从官方文档中截取的图片
我们看右下部分就是说明网络响应是通过网络拦截器来层层向上传递的,并且拦截器是HTTP工作真正发生的地方。
下面再来介绍各个拦截器的作用:
- interceptor:应用拦截器,通过Client设置
- RetryAndFollowUpInterceptor:重试拦截器,负责网络请求中的重试和重定向。比如网络请求过程中出现异常的时候就需要进行重试。
- BridgeInterceptor:桥接拦截器,用于桥接应用层和网络层的数据。请求时将应用层的数据类型转化为网络层的数据类型,响应时则将网络层的数据类型转化为应用层的数据类型。
- CacheInterceptor:缓存拦截器,负责读取和更新缓存。可以配置自定义的缓存拦截器。
- ConnectInterceptor:网络连接拦截器,其内部会获取一个连接。
- networkInterceptor:网络拦截器,通过Client设置。
- CallServerInterceptor:请求服务拦截器。它是拦截器中处于末尾的拦截器,用于向服务端发送数据并获取响应。
我们回到上面的方法中,在try块中的第一句就是启动责任链chain.proceed()
,这个方法将会启动责任链并获取响应:
override fun proceed(request: Request): Response {check(index < interceptors.size)......// Call the next interceptor in the chain.val next = copy(index = index + 1, request = request)val interceptor = interceptors[index]@Suppress("USELESS_ELVIS")val response = interceptor.intercept(next) ?: throw NullPointerException("interceptor $interceptor returned null").......return response}
这里面的核心就是上面的这一小段,首先通过copy
方法获得下一个拦截器:
internal fun copy(index: Int = this.index,exchange: Exchange? = this.exchange,request: Request = this.request,connectTimeoutMillis: Int = this.connectTimeoutMillis,readTimeoutMillis: Int = this.readTimeoutMillis,writeTimeoutMillis: Int = this.writeTimeoutMillis) = RealInterceptorChain(call, interceptors, index, exchange, request, connectTimeoutMillis,readTimeoutMillis, writeTimeoutMillis)
这个copy本质还是调用了构造函数,这这里它调用更改的唯一参数就是index
参数,它将原来的index+1,显然是想要达到遍历的效果,那么这个index
参数到底会影响什么呢?我们再将其与一开始的构造相比较:
//创建责任链val chain = RealInterceptorChain(call = this,interceptors = interceptors,index = 0,exchange = null,request = originalRequest,connectTimeoutMillis = client.connectTimeoutMillis,readTimeoutMillis = client.readTimeoutMillis,writeTimeoutMillis = client.writeTimeoutMillis)
一开始这个index的值是0,通过查找整个类可以发现唯一有意义的就是我们上面提到的proceed
方法:
val interceptor = interceptors[index]
val response = interceptor.intercept(next) ?: throw NullPointerException("interceptor $interceptor returned null")
就是用来从拦截器集合中获取拦截器的。之后又会用我们获取到的拦截器调用其intercept
方法,并将之前copy出来的对象传进去(也就是新的责任链,只不过index向后指了一格)。这个intercept
方法是一个接口方法,实现该接口的类有很多,我们以比较简单的ConnectInterceptor
类为例:
override fun intercept(chain: Interceptor.Chain): Response {val realChain = chain as RealInterceptorChainval exchange = realChain.call.initExchange(chain)val connectedChain = realChain.copy(exchange = exchange)return connectedChain.proceed(realChain.request)}
这里它做的唯一就是初始化了一个exchange
参数然后将其设置进了责任链中,最后又会调用新设置好的责任链的proceed
方法中。这就又回到了之前的方法中。这样看来,这个责任链的大体行为模式还是很好懂的,首先启动责任链,责任链调用proceed
方法启动,proceed
方法中会获得下一个拦截器并且调用下一个拦截器中的intercept
拦截方法,在这个拦截方法中首先会进行该拦截器的一些拦截逻辑,拦截逻辑完成之后会再次调用proceed
方法继续获得下一个拦截器,然后再调用它的拦截器方法,以此类推直到整个拦截器链上的拦截器方法都执行一遍,最后返回出一个Response响应。整个过程差不多如下所示:
相关文章:

开源库源码分析:Okhttp源码分析(一)
开源库源码分析:OkHttp源码分析 导言 接下来就要开始分析一些常用开源库的源码了,作为最常用的网络请求库,OkHttp以其强大的功能深受Android开发者的喜爱(比如说我),还有对该库进行二次封装而成的热门库&a…...

无涯教程-JavaScript - LOOKUP函数
描述 需要查看单个行或一列并从第二行或第二列的同一位置查找值时,请使用LOOKUP函数。使用"查找"功能搜索一行或一列。 使用VLOOKUP函数可搜索一行或一列,或搜索多行和多列(如表)。它是LOOKUP的改进版本。 有两种使用LOOKUP的方法- 矢量形式 − Use this form of…...

这所院校太好考了!地处魔都!不要错过!
一、学校及专业介绍 上海电力大学(Shanghai University of Electric Power),位于上海市,是中央与上海市共建、以上海市管理为主的全日制普通高等院校,是教育部首批“卓越工程师教育培养计划”试点院校、上海高水平地方…...

Python - PyQt6、QDesigner、pyuic5-tool 安装使用
Python 开发可视化界面可以使用原生的 tkinter,但是原生框架使用起来颇为不方便,所以最流行的还是QT UI框架,QT是使用C语言开发,Python 想使用需要对其进行封装,所以就出现了PyQt框架,这个框架使用极其方便…...

C语言——指针进阶(三)
目录 一.前言摘要 二.排序函数qsort的模拟实现 三.指针和数组笔试题解析 一.前言摘要 讲述关于strlen和sizeof对于各种数组与指针的计算规则与用法。另外还有qsort函数的模拟实现(可以排序任意类型变量) 二.排序函数qsort的模拟实现 目标:…...

三勾商城(java+vue3)微信小程序商城+SAAS+前后端源码
项目介绍 本系统功能包括: 前台展示后台管理SAAS管理端,包括最基本的用户登录注册,下单, 购物车,购买,结算,订单查询,收货地址,后台商品管 理,订单管理&…...

【洁洁送书第七期】现在学 Java 找工作还有优势吗
java 现在学 Java 找工作还有优势吗?活力四射的 JavaTIOBE 编程语言排行榜从零开始学会 JavaJava 语言运行过程基础知识进阶知识高级知识talk is cheap, show me the code结语 文末赠书 现在学 Java 找工作还有优势吗? 在某乎上可以看到大家对此问题的…...

npm发布自定义vue组件库
npm发布自定义vue组件库 创建项目 vue create test-ui自定义组件 创建自定义组件,组件名称根据你的需求来,最好一个组件一个文件夹,下图是我的示例。 src/components 组件和你写页面一样,所谓组件就是方便实用,不用…...

9.12 C++作业
实现一个图形类(Shape),包含受保护成员属性:周长、面积, 公共成员函数:特殊成员函数书写 定义一个圆形类(Circle),继承自图形类,包含私有属性:半…...

利用LinuxPTP进行时间同步(软/硬件时间戳) - 研一
转自:https://blog.csdn.net/BUPTOctopus/article/details/86246335 官方文档:https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/s1-using_ptp 查看网卡是否支持软硬件时间戳: sudo ethtoo…...
《极客时间:左耳听风——程序员练级攻略》【文章笔记个人思考】
原文链接:https://time.geekbang.org/column/intro/100002201 69 | 程序员练级攻略:开篇词70 | 程序员练级攻略:零基础启蒙编程入门入门语言 Python入门语言 JavaScript操作系统入门 Linux编程工具 Visual Studio CodeWeb 编程入门实践项目小…...

Springboot 实践(15)spring config 配置与运用—自动刷新
目前,网络上讲解spring config的自动刷新,都是通过git服务站的webhook功能执行“actuator/bus-refresh”服务实现的自动刷新。我们的前文讲解的配置中心,配置中心仓库使用的时本地地址,如下图所示: 那么,配…...

FirmAFL
FirmAFL使用并改进了Firmdyne模拟方式,并利用AFL对IoT固件实施高通量灰盒Fuzzing。 一、项目简介 FIRM-AFL 是 第一个针对物联网固件的高吞吐量灰盒模糊测试器。 支持mipsel、mipseb和armel三种CPU架构 ,涵盖Firmadyne数据库中90.2%的固件。 FIRM-AFL 解…...

SpringMVC的整合完成CRUD(增删改查)
SpringMVC是一种基于Java的Web框架,它是Spring框架的一部分。SpringMVC通过使用MVC(Model-View-Controller)设计模式来组织和管理Web应用程序的开发。 在SpringMVC中,Model代表数据模型,View代表用户界面,C…...

Postman使用_Tests Script(断言测试)
断言测试可以在Collection、Folder和Request的 pre-request script 和 test script中编写,测试脚本可以检测请求响应的各个方面,包括正文、状态代码、头、cookie、响应时间等,只有测试符合自定义的要求后才能通过。 pm对象提供了测试相关功能…...

问道管理:华为概念股捷荣技术13天10板,监管质疑迎合热点炒作
捷荣技能(002855.SZ)一口气将气氛拉满。 盘面看,自8月29日启动到9月14日,捷荣技能用了13天时间,将累计涨幅推到了188%,期间有10个涨停板,如此冷艳之举,还在于其“华为概念”。 …...

VR云游:让游客足不出户享受旅行的乐趣
随着人们的生活水平不断提高,对于旅行的需求也在日益增长,既要玩的舒心,也要享受的舒服,因此VR全景云游就成为了一种新型的旅行方式,人们足不出户就可以沉浸式游览各地自然风光与名胜古迹。 VR云游景区是一种全新的旅游…...

vue3学习源码笔记(小白入门系列)------ 重点!响应式原理 代码逐行分析
目录 备注响应式数据创建ref 和 reactive 核心 作用第一轮的 依赖收集 发生时机setup 阶段 去更改了 响应式数据 会发生依赖收集吗 派发更新派发更新是什么时候 触发的?扩展: setup阶段 响应式数据被修改 会触发组件更新吗 vue 是如何根据派发更新来触发…...

62、SpringBoot 使用RestTemplate 整合第三方 RESTful 服务
这节的要点: 就是弄两个项目 , 从 端口9090 这个项目,通过 restTemplate, 去访问 端口8080 的项目,并获取8080项目的数据。 ★ RESTful服务包含两方面的含义 1. 自己的应用要暴露一些功能供别人来调用。此时我们是服…...

Linux基本认识
一、Linux基本概念 Linux 内核最初只是由芬兰人林纳斯托瓦兹(Linus Torvalds)在赫尔辛基大学上学时出于个人爱好而编写的。 Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 UNIX 的多用户、多任务、支持多线程和多…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

LLMs 系列实操科普(1)
写在前面: 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容,原视频时长 ~130 分钟,以实操演示主流的一些 LLMs 的使用,由于涉及到实操,实际上并不适合以文字整理,但还是决定尽量整理一份笔…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...

day36-多路IO复用
一、基本概念 (服务器多客户端模型) 定义:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用:应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...