Retrofit源码分析:动态代理获取Api接口实例,解析注解生成request,线程切换
目录
一,Retrofit的基本使用
1.定义api接口
2.创建Retrofit实例
3.获取api接口实例发起请求
二,静态代理和动态代理
1,静态代理
2,动态代理
三,动态代理获取Api接口实例
四,解析接口方法注解,生成请求方法
五,代理发起网络请求
六,Retrofit如何实现线程切换?
一,Retrofit的基本使用
1.定义api接口
public interface ApiService {// GET 请求示例@GET("users/{id}")Call<User> getUser(@Path("id") int userId);// POST 请求示例@POST("users")Call<User> createUser(@Body User user);// 带查询参数的 GET 请求示例@GET("users")Call<List<User>> getUsers(@Query("page") int page, @Query("size") int size);
}
2.创建Retrofit实例
public class ApiClient {private static final String BASE_URL = "https://api.github.com/";private static Retrofit retrofit;public static Retrofit getClient() {if (retrofit == null) {retrofit = new Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create()) // 使用 Gson 解析器.build();}return retrofit;}
}
3.获取api接口实例发起请求
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 获取 apiService 实例ApiService apiService = ApiClient.getClient().create(ApiService.class);// 创建请求Call<User> call = apiService.getUser("octocat");// 异步请求call.enqueue(new Callback<User>() {@Overridepublic void onResponse(Call<User> call, Response<User> response) {if (response.isSuccessful()) {// 请求成功,处理数据User user = response.body();Log.d("Retrofit", "User: " + user.getName());} else {// 请求失败Log.e("Retrofit", "Request failed");}}@Overridepublic void onFailure(Call<User> call, Throwable t) {// 请求失败Log.e("Retrofit", "Request error", t);}});}
}
从Retrofit的使用入手,Retrofit的核心在于:
- 通过动态代理获取apiService实例,对api请求方法进行拓展。
- 在代理对象的invoke方法中对接口方法进行解析,解析注解,参数,参数注解,返回类型等。
- 代理用于发送请求的Call对象执行网络请求,并拦截响应。
- 底层使用Handler机制切换到主线程,并将响应结果返回。
二,静态代理和动态代理
首先,我们先了解一下代理模式:
代理模式:通过代理对象来代替真实对象的访问,从而在不修改原对象的情况下,对原对象的方法进行拓展
代理模式一般包含这几个元素:
- 委托类(被代理的类)
- 接口(委托类实现的方法)
- 代理类
1,静态代理
静态代理分为以下几个步骤:
- 创建一个接口,定义方法
- 创建一个委托类实现接口
- 创建一个代理类,持有委托类的引用,实现与委托类相同的接口
(1)创建接口
/*
委托接口*/
interface ClientInterface {fun call()
}
(2)创建委托类实现接口
/*
委托类*/
class Client : ClientInterface {override fun call() {println("Client Call")}
}
(3)创建代理类,实现与委托类相同的接口
/*
代理类*/
class Proxy : ClientInterface {var client : Client? = nullfun setInstance(client: Client){this.client = client;}override fun call() {println("proxy pre call")client?.call()println("proxy aft call")}
}
这样我们就可以通过使用代理类的call方法,从而对委托类的方法进行拓展;
2,动态代理
动态代理与静态代理的区别就是:动态代理的代理对象由Java中的Proxy.newProxyInstance()在运行时动态生成。
@CallerSensitivepublic static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException
这个方法需要的三个参数为:
- loader:委托类的类加载器
- interfaces:委托类实现的接口
- h:一个实现了InvocationHandler的类,在这个类的invoke方法中对委托类的方法进行拓展
(1)同样创建委托类和接口
/*
委托类,也就是代理对象
*/
class Client : ClientInterface {override fun call() {println("client call")}
}/*
委托接口,代理类实现的接口*/
interface ClientInterface {fun call()fun call1()
}
(2)创建实现InvocationHandler的类
/*
实现了InvocationHandler的类*/
class CallInvocationHandler(private var target: Any) : InvocationHandler {//代理类调用的方法会被转发到这里运行override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {println("before client: ${args.toString()} call: ${method.name}")return method.invoke(target, args)}
}
(3)使用Proxy.newProxyInstance()生成代理对象
class Application3 {fun main(){val client : ClientInterface = Client()val proxy = Proxy.newProxyInstance(client::class.java.classLoader, //代理对象的类加载器,用于加载代理对象arrayOf(client::class.java), //代理对象实现的接口,也就是代理对象要进行的业务CallInvocationHandler(client) //实现了 InvocationHandler 接口的对象,通过代理类调用代理对象的方法时,就会转发到invoke方法中被调用) as Clientproxy.call();}
}
动态代理(JDK动态代理)只能代理实现了接口的类,所以与其说代理类代理了委托类,不如说它是代理了接口
三,动态代理获取Api接口实例
当我们明白了动态代理之后,我们再来看Retrofit的create方法:
private val apiService : ApiService = RetrofitClient.getInstance().create(ApiService::class.java)public <T> T create(final Class<T> service) {validateServiceInterface(service);//动态代理,获取ApiService的代理对象return (T)Proxy.newProxyInstance(service.getClassLoader(),new Class<?>[] {service},new InvocationHandler() {private final Platform platform = Platform.get();private final Object[] emptyArgs = new Object[0];@Overridepublic @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)throws Throwable {// object类中的方法直接调用if (method.getDeclaringClass() == Object.class) {return method.invoke(this, args);}args = args != null ? args : emptyArgs;//检查是否含有默认实现,如果有直接按默认实现调用,//没有则进入loadServiceMethod方法return platform.isDefaultMethod(method)? platform.invokeDefaultMethod(method, service, proxy, args): loadServiceMethod(method).invoke(args);}});
}//在这里对接口中的方法进行解析
ServiceMethod<?> loadServiceMethod(Method method) {//请求方法缓存,避免重复加载ServiceMethod<?> result = serviceMethodCache.get(method);if (result != null) return result;synchronized (serviceMethodCache) {result = serviceMethodCache.get(method);if (result == null) {//没有缓存,解析方法result = ServiceMethod.parseAnnotations(this, method);serviceMethodCache.put(method, result);}}return result;
}
可见Retrofit通过create方法创建了一个ApiService的代理类。通过ApiService代理类调用请求方法时,就会被转发到InvocationHandler的invoke方法中进行解析(loadServiceMethod)和调用(invoke)。
四,解析接口方法注解,生成请求方法
接着,Retrofit通过ServiceMethod类对方法及方法注解进行解析:
abstract class ServiceMethod<T> {static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {//构建请求工厂RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);...//封装,形成完整的可执行的请求return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);}abstract @Nullable T invoke(Object[] args);
}
ServiceMethod是一个抽象类,实现类为HttpServiceMethod。
ServiceMethod首先构建了一个RequestFactory,RequestFactory中封装了从接口方法解析到的信息,包括:
- 请求方法(GET, POST等)
- baseUrl和请求的相对路径
- 请求头,请求参数,请求体等
final class RequestFactory {static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {return new Builder(retrofit, method).build();}private final Method method; //接口方法private final HttpUrl baseUrl; //baseUrlfinal String httpMethod; //请求方法 get,post等private final @Nullable String relativeUrl; //请求的相对路径private final @Nullable Headers headers; //请求头private final @Nullable MediaType contentType; //请求体MIME类型private final boolean hasBody; //是否包含请求体private final boolean isFormEncoded; //是否是表单编码请求private final boolean isMultipart; //是否是多部分请求private final ParameterHandler<?>[] parameterHandlers; //参数处理数组,根据参数注解决定如何处理参数final boolean isKotlinSuspendFunction; //是否为kotlin挂起函数}
接着在HttpServiceMethod中,使用适配器,格式转换器,结合RequestFactory将请求信息封装为可执行的完整的请求
核心代码:
static <T> HttpServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method, RequestFactory requestFactory) {CallAdapter<T, ?> callAdapter = createCallAdapter(retrofit, method);Type responseType = callAdapter.responseType();Converter<ResponseBody, T> responseConverter = createResponseConverter(retrofit, method, responseType);return new HttpServiceMethod<>(requestFactory, callAdapter, responseConverter);
}
核心组件:
-
callAdapter适配器:将底层的call对象适配为用户接口返回的类型(Call<T>, Flow<T>, LiveData<T>等);
-
converter格式转化器:例如转化Json格式数据
-
RequestFactory:之前封装好的请求信息
五,代理发起网络请求
在请求构建完毕后,就会调用invoke方法,具体调用的是HttpServiceMethod类中的invoke方法
invoke方法中又返回了adapt方法的调用,adapt方法的具体实现交给三个子类:
-
CallAdapted<ResponseT, ReturnT>:适配返回类型为标准Java类型,如Call<T>,或者RxJava的Single<T>, Observer<T>类型。
-
SuspendForResponse<ResponseT>:适配 Kotlin 协程场景,当接口方法的返回类型为 suspend fun 且需要返回一个 Respond<T>(完整的 HTTP 响应对象)时使用。
-
SuspendForBody<ResponseT>:适配 Kotlin 协程场景,当接口方法的返回类型为 suspend fun 且只需要返回响应体对象 T(不需要完整的 HTTP 响应对象)时使用。
@Overridefinal @Nullable ReturnT invoke(Object[] args) {Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);return adapt(call, args);}protected abstract @Nullable ReturnT adapt(Call<ResponseT> call, Object[] args);static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {}static final class SuspendForResponse<ResponseT> extends HttpServiceMethod<ResponseT, Object> {}static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT, Object> {}
一般情况,使用的都是CallAdapted子类,CallAdapted子类中的adapt方法返回callAdapter的adapt方法调用:
static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {private final CallAdapter<ResponseT, ReturnT> callAdapter;CallAdapted(RequestFactory requestFactory,okhttp3.Call.Factory callFactory,Converter<ResponseBody, ResponseT> responseConverter,CallAdapter<ResponseT, ReturnT> callAdapter) {super(requestFactory, callFactory, responseConverter);this.callAdapter = callAdapter;}@Overrideprotected ReturnT adapt(Call<ResponseT> call, Object[] args) {return callAdapter.adapt(call);}}
返回类型为Call<T>,对应的callAdapter由Retrofit默认添加的DefaultCallAdapterFactory生成:
@Overridepublic @Nullable CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {if (getRawType(returnType) != Call.class) {return null;}if (!(returnType instanceof ParameterizedType)) {throw new IllegalArgumentException("Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");}final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);final Executor executor =Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)? null: callbackExecutor;return new CallAdapter<Object, Call<?>>() {@Overridepublic Type responseType() {return responseType;}@Overridepublic Call<Object> adapt(Call<Object> call) {//这里使用代理模式,将okHttpCall的功能代理到ExecutorCallbackCall中return executor == null ? call : new ExecutorCallbackCall<>(executor, call);}};}
adapt方法最后返回ExecutorCallbackCall对象,所以调用ApiService代理类的请求方法,最终返回的是call对象为ExecutorCallbackCall对象。
或者说是使用了代理模式,将call的功能代理到了ExecutorCallbackCall中,当我们调用call的异步方法enquque时,最终调用到的是ExecutorCallbackCall的enquque方法。
在ExecutorCallbackCall的enquque方法调用了原call对象的异步请求方法,并拦截了onResponse 和 onFailure 回调,切换到指定的线程(通常是主线程),也就是Retrofit中的onResponse 和 onFailure 回调最终是在主线程中获取到的。
至此,Retrofit的整个流程就结束了。
static final class ExecutorCallbackCall<T> implements Call<T> {final Executor callbackExecutor;final Call<T> delegate; //被代理的call对象ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {this.callbackExecutor = callbackExecutor;this.delegate = delegate;}@Overridepublic void enqueue(final Callback<T> callback) {Objects.requireNonNull(callback, "callback == null");//调用原call对象的异步请求方法delegate.enqueue(new Callback<T>() {//拦截底层的 onResponse 和 onFailure 回调,切换到指定的线程(通常是主线程)。@Overridepublic void onResponse(Call<T> call, final Response<T> response) {//这里切换到主线程callbackExecutor.execute(() -> {if (delegate.isCanceled()) {callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));} else {callback.onResponse(ExecutorCallbackCall.this, response);}});}@Overridepublic void onFailure(Call<T> call, final Throwable t) {callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t));}});}@Overridepublic boolean isExecuted() {return delegate.isExecuted();}@Overridepublic Response<T> execute() throws IOException {return delegate.execute();}@Overridepublic void cancel() {delegate.cancel();}@Overridepublic boolean isCanceled() {return delegate.isCanceled();}@SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone.@Overridepublic Call<T> clone() {return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone());}@Overridepublic Request request() {return delegate.request();}@Overridepublic Timeout timeout() {return delegate.timeout();}}
六,Retrofit如何实现线程切换?
ExecutorCallbackCall代理类发起请求后,在响应回调中,通过callbackExecutor.execute切换到主线程。
这里的callbackExecutor是Retrofit在构建时配置的:
public Retrofit build() {Executor callbackExecutor = this.callbackExecutor;if (callbackExecutor == null) {callbackExecutor = platform.defaultCallbackExecutor();}}
在Retrofit的Platform中,Android平台默认返回一个MainThreadExecutor,在MainThreadExecutor的execute方法中,线程通过Hanlder进行切换。
static class Android extends Platform {@Overridepublic Executor defaultCallbackExecutor() {return new MainThreadExecutor();}static class MainThreadExecutor implements Executor {private final Handler handler = new Handler(Looper.getMainLooper());@Overridepublic void execute(Runnable r) {//通过Handler进行线程切换handler.post(r);}}
}
相关文章:
Retrofit源码分析:动态代理获取Api接口实例,解析注解生成request,线程切换
目录 一,Retrofit的基本使用 1.定义api接口 2.创建Retrofit实例 3.获取api接口实例发起请求 二,静态代理和动态代理 1,静态代理 2,动态代理 三,动态代理获取Api接口实例 四,解析接口方法注解&…...

范德蒙矩阵(Vandermonde 矩阵)简介:意义、用途及编程应用
参考: Introduction to Applied Linear Algebra – Vectors, Matrices, and Least Squares Stephen Boyd and Lieven Vandenberghe 书的网站: https://web.stanford.edu/~boyd/vmls/ Vandermonde 矩阵简介:意义、用途及编程应用 在数学和计算科学中&a…...

【中标麒麟服务器操作系统实例分享】java应用DNS解析异常分析及处理
了解更多银河麒麟操作系统全新产品,请点击访问 麒麟软件产品专区:https://product.kylinos.cn 开发者专区:https://developer.kylinos.cn 文档中心:https://document.kylinos.cn 情况描述 中标麒麟服务器操作系统V7运行在 ARM…...

网安瞭望台第17期:Rockstar 2FA 故障催生 FlowerStorm 钓鱼即服务扩张现象剖析
国内外要闻 Rockstar 2FA 故障催生 FlowerStorm 钓鱼即服务扩张现象剖析 在网络安全的复杂战场中,近期出现了一个值得关注的动态:名为 Rockstar 2FA 的钓鱼即服务(PhaaS)工具包遭遇变故,意外推动了另一个新生服务 Flo…...

玩转OCR | 探索腾讯云智能结构化识别新境界
📝个人主页🌹:Eternity._ 🌹🌹期待您的关注 🌹🌹 ❀ 玩转OCR 腾讯云智能结构化识别产品介绍服务应用产品特征行业案例总结 腾讯云智能结构化识别 腾讯云智能结构化OCR产品分为基础版与高级版&am…...

idea2024创建JavaWeb项目以及配置Tomcat详解
今天呢,博主的学习进度也是步入了JavaWeb,目前正在逐步杨帆旗航,迎接全新的狂潮海浪。 那么接下来就给大家出一期有关JavaWeb的配置教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正…...

外连接转AntiJoin的应用场景与限制条件 | OceanBase SQL 查询改写系列
在《SQL 改写系列:外连接转内连接的常见场景与错误》一文中,我们了解到谓词条件可以过滤掉连接结果中的 null 情形的,将外连接转化为内连接的做法是可行的,正如图1中路径(a)所示。此时,敏锐的你或许会进一步思考&#…...

华为实训课笔记 2024 1223-1224
华为实训 12/2312/24 12/23 [Huawei]stp enable --开启STP display stp brief --查询STP MSTID Port Role STP State Protection 实例ID 端口 端口角色 端口状态 是否开启保护[Huawei]display stp vlan xxxx --查询制定vlan的生成树计算结…...

MySQL超详细安装配置教程(亲测有效)
目录 1.下载mysql 2.环境配置 3.安装mysql 4.navicat工具下载与连接 5总结 1.下载mysql mysql下载--MySQL :: 下载 MySQL 社区服务器 下载的时候这里直接逃过就行 我这里的版本是最新的mysql8.0.37 下载完成之后,将压缩包进行解压 这里我建议大…...

MySQL 8.0:explain analyze 分析 SQL 执行过程
介绍 MySQL 8.0.16 引入一个实验特性:explain formattree ,树状的输出执行过程,以及预估成本和预估返 回行数。在 MySQL 8.0.18 又引入了 EXPLAIN ANALYZE,在 formattree 基础上,使用时,会执行 SQL &#…...
信管通低代码信息管理系统应用平台
目前,国家统一要求事业单位的电脑都要进行国产化替代,替代后使用的操作系统都是基于linux的,所有以前在WINDOWS下运行的系统都不能使用了,再者,各单位的软件都很零散,没有统一起来。需要把日常办公相关的软…...

git推送本地仓库到远程(Gitee)
目录 一、注册创建库 二、创建仓库 三、推送本地仓库到远程 1.修改本地仓库用户名和邮箱 2.本地库关联远程仓库 3.拉取远程仓库的文件 4.推送本地库的文件 5.查看远程仓库 四、远程分支查看 1.查看远程分支 2.修改test.txt文件 一、注册创建库 Gitee官网࿱…...

【C++语言】多态
一、多态的概念 多态的概念:通俗来说,就是多种形态,具体点就是去完成某种行为,当不同的对象去完成时会产生出不同的状态。 我们可以举一个例子: 比如买票这种行为,当普通人买票时,是全价买票&am…...
ThinkPHP 吸收了Java Spring框架一些特性
ThinkPHP 吸收了Java Spring框架一些特性,下面介绍如下: 1、controller 控制器层 存放控制器层的文件,用于处理请求和响应 2、model 实体类 存放实体类的文件,用于定义数据模型 3、dao DAO层 存放DAO(数据访问…...

自动控制系统综合与LabVIEW实现
自动控制系统综合是为了优化系统性能,确保其可靠性、稳定性和灵活性。常用方法包括动态性能优化、稳态误差分析、鲁棒性设计等。结合LabVIEW,可以通过图形化编程、高效数据采集与处理来实现系统综合。本文将阐述具体方法,并结合硬件选型提供实…...

记录一个SVR学习
1、为什么使用jupter来做数据预测?而不是传统pycharm编辑器 1、Jupyter Notebook 通过anaconda统一管理环境,可以运行python、R、Sql等数据分析常用语言。 2、做到交互式运行,可以逐步运行代码块,实时查看结果,便于调…...
Java内存区域进一步详解
方法区 方法区属于是 JVM 运行时数据区域的一块逻辑区域,是各个线程共享的内存区域。 《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,方法区到底要如何实现那就是虚拟机自己要考虑的事情了。也就是说,在不同的虚拟机实现上&am…...

SpiderFlow平台v0.5.0流程的执行过程
流程执行过程: 1. 流程启动 流程的执行通常从一个 开始节点 开始,该节点是整个爬虫任务的起点。开始节点没有实际的功能作用,主要作用是标记流程的起始。 执行顺序:在执行过程中,系统按照流程中的连接线顺序依次执行…...

利用.NET Upgrade Assitant对项目进行升级
本教程演示如何把WPF程序从 <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>升级到<TargetFramework>net8.0-windows</TargetFramework>. 下载并安装.NET Upgrade Assistant - Visual Studio Marketplace Supported .NET upgrades: .NET Frame…...
JAVA开发Erp时日志报错:SQL 当 IDENTITY_INSERT 设置为 OFF 时,不能为表 ‘***‘ 中的标识列插入显式值
错误提示 ### SQL: INSERT INTO sys_user ( user_id, username, password, status, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ? ) ### Cause: com.microsoft.sqlserver.jdbc.SQLServerException: 当 IDENTITY_INSERT 设置为 OFF 时&…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...

Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...

解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...

淘宝扭蛋机小程序系统开发:打造互动性强的购物平台
淘宝扭蛋机小程序系统的开发,旨在打造一个互动性强的购物平台,让用户在购物的同时,能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机,实现旋转、抽拉等动作,增…...