NDK开发

news2026/2/11 12:44:10

NDK介绍

  1. app为什么会把代码放到so中

a) C语言历史悠久,有很多现成的代码可用

b) C代码执行效率比Java高

c) Java代码很容易被反编译,而且反编译以后的逻辑很清晰

  1. 为什么要学习NDK开发

在安卓的so开发中,其他基本与C/C++开发一致,而与Java交互需要用到jni

在本部分的NDK开发讲解中,主要就是介绍jni相关内容

so中会接触的:系统库函数、jni调用、加密算法、魔改算法、系统调用、自定义算法

  1. 什么是JNI

jni是Java Native Interface的缩写。从Java1.1开始,jni标准成为Java平台的一部分,允许Java代码和其他语言写的代码进行交互

  1. 什么是NDK

交叉编译工具链

NDK的配置

NDK、CMake、LLDB的作用 https://developer.android.com/ndk/guides

  1. ABI与指令集

https://developer.android.com/ndk/guides/abis

NDK与Java工程的区别

  1. Java代码中加载so和声明所需使用的so中的函数

  2. 编写CMakeLists.txt和C文件

  3. build.gradle中添加一些代码

defaultConfig {
......
externalNativeBuild {
	cmake {
	    cppFlags "-std=c++11"
	}
}
ndk {
	abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
externalNativeBuild {
cmake {
	path "src/main/cpp/CMakeLists.txt"
	version "3.10.2"
}
}

第一个NDK工程

  1. CMakeLists介绍

指明文件源代码编译成哪个so、指明依赖库

2.so的加载

static {
        System.loadLibrary("native-lib");
    }

3.native函数的声明

public native String stringFromJNI(); 
  1. JNI函数的静态注册规则
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

5.JNIEnv、jobject/jclass

so层对接java层需要的参数

  1. NewstringUTF

将c字符串转换为java字符串

关键代码定位点、系统函数

  1. 在NDK开发中,一定要注意哪些是Java的类型,哪些是C/C++的类型,在适当的时候需要转换

  2. extern “C” JNIEXPORT jstring JNICALL

使用c语言方式编译、JNIEXPORT是否出现在导出表、jstring返回值

  1. 指定只编译arm64的so
ndk(){
    adiFilters 'arm64-v8a'
}
  1. 指定编译后的so名字

CMakeLists.txt中修改

so中常用的Log输出

#include <android/log.h>

#define TAG "xxxxxx"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);



#include <jni.h>
#include <string>
#include <android/log.h>

#define TAG "xxxxxx"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);



extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    LOGD("xxxxxx%d",100);
    return env->NewStringUTF(hello.c_str());
}

#define中的…和__VA_ARGS__

可变参数

NDK多线程

JavaVM每个进程中只有一份
JNIEnv每个线程中都有一份
为了更好的演示,所以先简单介绍一下多线程

//线程id,其实就是long
pthread_t thread;
//线程id 线程属性 函数 传给函数的参数
pthread_create(&thread, nullptr, myThread, nullptr);
//等待线程执行完毕
//默认的线程属性是joinable 随着主线程结束而结束
//线程属性是dettach,可以分离执行 
pthread_join(thread, nullptr);
//子线程中使用它来退出线程
pthread_exit(0);

传递int参数
传递多个参数 结构体 数组
接收返回值

JNI_OnLoad

  1. so中各种函数的执行时机

init、init_array、JNI_OnLoad

  1. JNI_OnLoad的定义
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = nullptr;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        LOGD("GetEnv failed");
        return -1;
    }
    return JNI_VERSION_1_6;
}
  1. 注意事项

一个so中可以不定义JNI_OnLoad

一旦定义了JNI_OnLoad,在so被加载的时候会自动执行

必须返回JNI版本 JNI_VERSION_1_6

JavaVM

  1. JavaVM是什么
    JavaVM结构体介绍
    C和C++中的区别
    JavaVM中的常用方法
    GetEnv
    AttachCurrentThread
  2. JavaVM的获取方式
    JNI_OnLoad的第一个参数
    JNI_OnUnload的第一个参数
    env->GetJavaVM
    对比各种方式获取的JavaVM指针是否一致
    %p打印地址

JNIEnv

  1. JNIEnv是什么
    JNIEnv结构体介绍
    C和C++中的区别
    JNIEnv中的常用方法后续详细介绍
  2. JNIEnv的获取方式
    函数静态/动态注册,传的第一个参数
    vm->GetEnv
    globalVM->AttachCurrentThread
    对比各种方式获取的JNIEnv指针是否一致
    %p打印地址
#include <jni.h>
#include <string>
#include <android/log.h>
#include <pthread.h>


#define TAG "xxxx"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "xiaojianbang", __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);


JavaVM* globalVM;


void myThread(){

//    JNIEnv *env = nullptr;
//    if (globalVM->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
//        LOGD("GetEnv failed");
//    }

    JNIEnv *env = nullptr;
    if (globalVM->AttachCurrentThread((JNIEnv **) &env, nullptr) != JNI_OK) {
        LOGD("GetEnv failed");
    }
    LOGD("myThread JNIEnv %p", env);


    LOGD("this is from myThread");
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_javaandso_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";

    LOGD("stringFromJNI JNIEnv %p", env);

    //__android_log_print(ANDROID_LOG_DEBUG, "xiaojianbang", "xxxxxx jni native %d %d", 100, 200);
    LOGD("xxxxxx jni native %d %d", 100, 200);

    pthread_t thread;
    // int pthread_create(pthread_t* __pthread_ptr, pthread_attr_t const* __attr, void* (*__start_routine)(void*), void*);
    pthread_create(&thread, nullptr, reinterpret_cast<void *(*)(void *)>(myThread), nullptr);
    pthread_join(thread, nullptr);

    return env->NewStringUTF(hello.c_str());
}

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    LOGD("this is form JNI_OnLoad");

    JNIEnv *env = nullptr;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        LOGD("GetEnv failed");
        return -1;
    }

    LOGD("JavaVM %p", vm);
    env->GetJavaVM(&globalVM);
    LOGD("JavaVM %p", globalVM);

    LOGD("JNI_OnLoad JNIEnv %p", env);


    return JNI_VERSION_1_6;
}



so相关的几个概念

  1. 导出表、导入表是什么
  2. 出现在导出表、导入表里面的函数,一般可以通过frida相关API直接得到函数地址,也可以自己计算函数地址
  3. 没有出现在导出表、导入表、符号表里面的函数,都需要自己计算函数地址
  4. 要完成so层的hook,都需要得到一个地址
  5. Java层中的native函数,被调用后会找到so中对应的函数。简单的说,就是Java调用C需要先完成函数注册,函数注册分为静态注册、动态注册

so函数注册

  1. JNI函数的静态注册
    必须遵循一定的命名规则,一般是Java_包名_类名_方法名
    系统会通过dlopen加载对应的so,通过dlsym来获取指定名字的函数地址,然后调用
    静态注册的jni函数,必然在导出表里
  2. JNI函数的动态注册
    通过env->RegisterNatives注册函数,通常在JNI_OnLoad中注册
    JNINativeMethod
    函数签名
    可以给同一个Java函数注册多个native函数,以最后一次为准
jclass MainActivityClazz = env->FindClass("com/xxxx/ndk/NDKMain");
JNINativeMethod methods[] = {
    //public native String encode(int i, String str, byte[] byt);
    {"encode", "(ILjava/lang/String;[B)Ljava/lang/String;", (void *)encodeFromC},
};
env->RegisterNatives(MainActivityClazz, methods, sizeof(methods)/sizeof(JNINativeMethod));
#include <jni.h>
#include <string>
#include <android/log.h>
#include <pthread.h>
#include <dlfcn.h>


#define TAG "xiaojianbang"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "xiaojianbang", __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);


JavaVM* globalVM;
void test();
extern "C" void fromSoB();

void myThread(){

//    JNIEnv *env = nullptr;
//    if (globalVM->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
//        LOGD("GetEnv failed");
//    }

    JNIEnv *env = nullptr;
    if (globalVM->AttachCurrentThread((JNIEnv **) &env, nullptr) != JNI_OK) {
        LOGD("GetEnv failed");
    }
    LOGD("myThread JNIEnv %p", env);


    LOGD("this is from myThread");
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_javaandso_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */, jstring path) {
    std::string hello = "Hello from C++";

    LOGD("stringFromJNI JNIEnv %p", env);

    //__android_log_print(ANDROID_LOG_DEBUG, "xiaojianbang", "xxxxxx jni native %d %d", 100, 200);
    LOGD("xxxxxx jni native %d %d", 100, 200);

    pthread_t thread;
    // int pthread_create(pthread_t* __pthread_ptr, pthread_attr_t const* __attr, void* (*__start_routine)(void*), void*);
    pthread_create(&thread, nullptr, reinterpret_cast<void *(*)(void *)>(myThread), nullptr);
    pthread_join(thread, nullptr);


    const char* cPath = env->GetStringUTFChars(path, nullptr);

    LOGD("cPath: %s", cPath);

    void *soinfo = dlopen(cPath, RTLD_NOW);
    void (*ref)();
    //ref = reinterpret_cast<void (*)()> (dlsym(soinfo, "_Z7fromSoBv"));
    ref = reinterpret_cast<void (*)()> (dlsym(soinfo, "fromSoB"));
    if (ref == nullptr) {
        LOGD("ref is null");
    }
    ref();

    fromSoB();
    dlclose(soinfo);

    return env->NewStringUTF(hello.c_str());
}

jstring xxxx(JNIEnv* env, jobject obj, jstring a, jint b, jbyteArray c) {
    return env->NewStringUTF("xxxx from jni");
}

jstring xxxx1(JNIEnv* env, jobject obj, jstring a, jint b, jbyteArray c) {

    test();

    return env->NewStringUTF("xxxx1 from jni");
}

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    LOGD("this is form JNI_OnLoad");

    JNIEnv *env = nullptr;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        LOGD("GetEnv failed");
        return -1;
    }

    LOGD("JavaVM %p", vm);
    env->GetJavaVM(&globalVM);
    LOGD("JavaVM %p", globalVM);
    LOGD("JNI_OnLoad JNIEnv %p", env);

    jclass MainActivityClazz = env->FindClass("com/example/javaandso/MainActivity");
    JNINativeMethod methods[] = {
            {"stringFromJNI2", "(Ljava/lang/String;I[B)Ljava/lang/String;", (void *)xxxx},
            {"stringFromJNI2", "(Ljava/lang/String;I[B)Ljava/lang/String;", (void *)xxxx1},
    };
//    jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
//                         jint nMethods)
    env->RegisterNatives(MainActivityClazz, methods, sizeof(methods)/sizeof(JNINativeMethod));

    return JNI_VERSION_1_6;
}



so之间的互相调用

  1. 多个cpp文件编译成一个so

CMake.txt文件中 添加cpp文件

add_library( # Sets the name of the library.
            xiaojianbang
            
            # Sets the library as a shared library.
            SHARED
            
            # Provides a relative path to your source file(s).
            xiaojianbang.cpp
            xxx.cpp
        )
  1. 编译多个so
    编写多个cpp文件
    修改CMakeLists.txt
    Java静态代码块加载多个so
dd_library( # Sets the name of the library.
             xxxxA

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             xxxx.cpp
             main.cpp
        )

add_library( # Sets the name of the library.
        xxxxB

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        demo.cpp
        )


# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                       xxxxA
                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )


target_link_libraries( # Specifies the target library.
                    xxxxB

                    # Links the target library to the log library
                    # included in the NDK.
                    ${log-lib} )
  1. so之间的相互调用
    2.1 使用dlopen、dlsym、dlclose获取函数地址,然后调用。需要导入dlfcn.h
    void soinfo = dlopen(nativePath, RTLD_NOW);
    void (def)(char str) = nullptr;
    def = reinterpret_cast<void (
    )(char *)>(dlsym(soinfo, “_Z7fromSoBPc”));
    def(“”);
    2.2 extern 函数声明

通过jni创建Java对象

1. NewObject创建对象
jclass clazz = env->FindClass("com/xxxx/ndk/NDKDemo");
jmethodID methodID = env->GetMethodID(clazz, "<init>", "()V");
jobject ReflectDemoObj = env->NewObject(clazz, methodID);
LOGD("ReflectDemoObj %p", ReflectDemoObj);

2. AllocObject创建对象
jclass clazz = env->FindClass("com/xxxx/ndk/NDKDemo");
jmethodID methodID2 = env->GetMethodID(clazz, "<init>", "(Ljava/lang/String;I)V");
jobject ReflectDemoObj2 = env->AllocObject(clazz);
jstring jstr = env->NewStringUTF("from jni str");
env->CallNonvirtualVoidMethod(ReflectDemoObj2, clazz, methodID2, jstr, 100);

通过jni访问Java属性

  1. 获取静态字段
jfieldID privateStaticStringField = 
	env->GetStaticFieldID(clazz, "privateStaticStringField", "Ljava/lang/String;");
jstring privateStaticString = 
	static_cast<jstring>(env->GetStaticObjectField(clazz, privateStaticStringField));
const char* privatecstr = 
	env->GetStringUTFChars(privateStaticString, nullptr);
LOGD("privateStaticString: %s", privatecstr);
env->ReleaseStringUTFChars(privateStaticString, privatecstr);

  1. 获取对象字段
jfieldID publicStringField = 
	env->GetFieldID(clazz, "publicStringField", "Ljava/lang/String;");
jstring publicString = 
	static_cast<jstring>(env->GetObjectField(ReflectDemoObj, publicStringField));
const char* publiccstr = 
	env->GetStringUTFChars(publicString, nullptr);
LOGD("publicStringField: %s", publiccstr);
env->ReleaseStringUTFChars(publicString, publiccstr);

  1. 设置字段
env->SetObjectField(ndkobj, privateStringFieldID, env->NewStringUTF("xxxx"));

通过jni访问Java数组

  1. 获取数组字段ID
jfieldID byteArrayID = env->GetFieldID(clazz, "byteArray", "[B");
jbyteArray byteArray = 
	static_cast<jbyteArray>(env->GetObjectField(ReflectDemoObj, byteArrayID));
int _byteArrayLength = env->GetArrayLength(byteArray);
jbyte* CBytes = env->GetByteArrayElements(byteArray,nullptr);
for(int i=0;i< _byteArrayLength;i++)
{
    LOGD(CBytes[i]);
}
  1. 修改数组字段
char javaByte[10];
for(int i = 0; i < 10; i++){
	javaByte[i] = static_cast<char>(100 - i);
}
const jbyte *java_array = reinterpret_cast<const jbyte *>(javaByte);
env->SetByteArrayRegion(byteArray, 0, _byteArrayLength, java_array);

  1. 获取数组字段
byteArray = static_cast<jbyteArray>(env->GetObjectField(ReflectDemoObj, byteArrayID));
_byteArrayLength = env->GetArrayLength(byteArray);
char* str = reinterpret_cast<char *>(env->GetByteArrayElements(byteArray, nullptr));
for(int i = 0; i< _byteArrayLength; i++){
	LOGD("str[%d]=%d", i, str[i]);
}
env->ReleaseByteArrayElements(jbyteArray, reinterpret_cast<jbyte *>(cbyteArray), 0);

通过jni访问Java方法

  1. 调用静态函数
jclass ReflectDemoClazz = env->FindClass("com/xiaojianbang/ndk/NDKDemo");
jmethodID publicStaticFuncID = env->GetStaticMethodID(ReflectDemoClazz, "publicStaticFunc", "()V");
env->CallStaticVoidMethod(ReflectDemoClazz, publicStaticFuncID);

  1. 调用对象函数
jmethodID privateFuncID = env->GetMethodID(ReflectDemoClazz, "privateFunc", "(Ljava/lang/String;I)Ljava/lang/String;");
jmethodID ReflectDemoInit = env->GetMethodID(ReflectDemoClazz, "<init>", "(Ljava/lang/String;)V");
jstring str1 = env->NewStringUTF("this is from NDK");
jobject ReflectDemoObj = env->NewObject(ReflectDemoClazz, ReflectDemoInit, str1);
jstring str2 = env->NewStringUTF("this is from JNI");
jstring retval = static_cast<jstring>(env->CallObjectMethod(ReflectDemoObj, privateFuncID, str2, 1000));

  1. CallObjectMethodA的使用
jvalue args[2];
args[0].l = str2;
args[1].i = 1000;
jstring retval = static_cast<jstring>(env->CallObjectMethodA(ReflectDemoObj, privateFuncID, args));
const char* cpp_retval = env->GetStringUTFChars(retval, nullptr);
LOGD("cpp_retval: %s", cpp_retval);
env->ReleaseStringUTFChars(retval, cpp_retval);

通过jni访问Java父类方法

//super.onCreate(savedInstanceState);
jclass AppCompatActivityClazz = env->FindClass("androidx/appcompat/app/AppCompatActivity");
jmethodID onCreateID = env->GetMethodID(AppCompatActivityClazz, "onCreate", "(Landroid/os/Bundle;)V");
env->CallNonvirtualVoidMethod(thiz, AppCompatActivityClazz, onCreateID, saved_instance_state);

内存管理

  1. 局部引用
    大多数的jni函数,调用以后返回的结果都是局部引用
    因此,env->NewLocalRef 基本不用
    一个函数内的局部引用数量是有限制的,在早期的安卓系统中,体现的更为明显
    当函数体内需要大量使用局部引用时,比如大循环中,最好及时删除不用的局部引用
    可以使用 env->DeleteLocalRef 来删除局部引用
  2. 局部引用相关的其他函数
    env->EnsureLocalCapacity(num) 判断是否有足够的局部引用可以使用,足够则返回0
    需大量使用局部引用时,手动删除太过麻烦,可使用以下两个函数来批量管理局部引用
    env->PushLocalFrame(num)
    env->PopLocalFrame(nullptr)
  3. 全局引用
    在jni开发中,需要跨函数使用变量时,直接定义全局变量是没用的
    需要使用以下两个方法,来创建和删除全局引用
    env->NewGlobalRef
    env->DeleteGlobalRef
  4. 弱全局引用
    与全局引用基本相同,区别是弱全局引用有可能会被回收
    env->NewWeakGlobalRef
    env->DeleteWeakGlobalRef

子线程中获取Java类

  1. 在子线程中,findClass可以直接获取系统类
  2. 在主线程中获取类,使用全局引用来传递到子线程中
  3. 在主线程中获取正确的ClassLoader,在子线程中去加载类
    3.1 在Java中,可以先获取类字节码,然后使用getClassLoader()来获取
    Demo.class.getClassLoader()
    new Demo().getClass().getClassLoader()
    Class.forName(…).getClassLoader()
    3.2 在jni的主线程中获取ClassLoader
    3.3 在jni的子线程中loadClass

init与initarray

  1. so在执行JNI_Onload之前,还会执行两个构造函数init、initarray
  2. so加固、so中字符串加密等等,一般会把相关代码放到这里
  3. init的使用
    extern “C” void _init(){ //函数名必须为_init

    }
  4. initarray的使用
attribute ((constructor)) void initArrayTest1(){ ... }
attribute ((constructor(200))) void initArrayTest2(){ ... }
attribute ((constructor(101))) void initArrayTest3(){ ... }
attribute ((constructor, visibility("hidden"))) void initArrayTest4(){ ... }

constructor后面的值,较小的先执行,最好从100以后开始用
如果constructor后面没有跟值,那么按定义的顺序,从上往下执行

visibility(“hidden”)隐藏函数名

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2216360.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

免费插件集-illustrator插件-Ai插件-路径点到点连线

文章目录 1.介绍2.安装3.通过窗口>扩展>知了插件4.功能解释5.总结 1.介绍 本文介绍一款免费插件&#xff0c;加强illustrator使用人员工作效率&#xff0c;实现简单路径内部点到点连线功能。首先从下载网址下载这款插件 https://download.csdn.net/download/m0_67316550…

打造卓越APP体验:13款界面设计软件推荐

你知道如何选择正确的UI设计软件吗&#xff1f;你知道设计美观的用户界面&#xff0c;及带来良好用户体验的APP&#xff0c;需要什么界面设计软件吗&#xff1f;基于APP界面的功能不同&#xff0c;选择的APP界面设计软件也会有所不同。然而&#xff0c;并不是要把所有APP界面设…

1.2.3 TCP IP模型

TCP/IP模型&#xff08;接网叔用&#xff09; 网络接口层 网络层 传输层 应用层 理念&#xff1a;如果某些应用需要“数据格式转换”“会话管理功能”&#xff0c;就交给应用层的特定协议去实现 tip&#xff1a;数据 局部正确不等于全局正确 但是&#xff0c;数据的 全局正…

docker (desktopcompose) download

docker docker-compose download 百度网盘获取离线包链接release-notes 参考dockerdocker-composewlspowershell

基于Spring Boot的大创项目成本控制系统

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

Linux下ClamAV源代码安装与使用说明

Linux下ClamAV源代码安装与使用说明 ClamAV(Clam AntiVirus)是一款开源的防病毒工具,广泛应用于Linux平台上的网络安全领域。它以其高效的性能和灵活的配置选项,成为网络安全从业人员的重要工具。ClamAV支持多线程扫描,可以自动升级病毒库,并且支持多个操作系统,包括Li…

扫普通链接二维码打开小程序

1. 2.新增规则&#xff08;注意下载文件到跟目录下&#xff0c;需要建个文件夹放下载的校验文件&#xff09; 3.发布 ps&#xff1a;发布后&#xff0c;只能访问正式版本。体验版本如果加了 测试链接http://xxx/xsc/10 那么http://xxx/xsc/aa.....应该都能访问 例如aa101 aa…

5 -《本地部署开源大模型》在Ubuntu 22.04系统下ChatGLM3-6B高效微调实战

在Ubuntu 22.04系统下ChatGLM3-6B高效微调实战 无论是在单机单卡&#xff08;一台机器上只有一块GPU&#xff09;还是单机多卡&#xff08;一台机器上有多块GPU&#xff09;的硬件配置上启动ChatGLM3-6B模型&#xff0c;其前置环境配置和项目文件是相同的。如果大家对配置过程还…

前端excel的实现方案Luckysheet

一、介绍 Luckysheet是一款纯前端类似excel的在线表格&#xff0c;功能强大、配置简单、完全开源的插件。目前已暂停维护&#xff0c;但是其已有功能大概能满足常见需求的使用。 二、引入 ①cdn引入&#xff08;目前应该已经不支持&#xff0c;可自行尝试&#xff09; <l…

第二十七篇:传输层讲解,TCP系列一

一、传输层的功能 ① 分割与重组数据 传输层也要做数据分割&#xff0c;所以必然也需要做数据重组。 ② 按端口号寻址 IP只能定位数据哪台主机&#xff0c;无法判断数据报文应该交给哪个应用&#xff0c;传输层给每个应用都设置了一个编号&#xff0c;这个编号就是端口&…

大数据毕业设计选题推荐-电影数据分析系统-电影推荐系统-Python数据可视化-Hive-Hadoop-Spark

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

大模型应用开发:如何在网页中嵌入3D人物

要实现的效果如图所示&#xff1a; 左侧是插入的3D人物&#xff0c;类似AI智能助手的角色。 我们这里是通过React做的。需要用到以下工具或者网站&#xff1a; readyplayer.me/ 自定义3D人物Blender 3维设计软件&#xff0c;3D文件格式转化&#xff0c;主要是fbx和glb的互转w…

【Docker】安装部署项目流程(Pycharm版)

安装部署步骤 1.准备项目 第一步要准备好你所需要部署的项目&#xff0c;确保在工作目录下所以程序.py文件正常调用并能正确运行 如上&#xff0c;main要在工作目录中能跑通&#xff0c;这里有一点需要注意 在IDE src不要标记为源代码根目录&#xff0c;观察一下是否能跑通代…

React国际化中英文切换实现

目录 概况 安装 文件结构 引入 使用 正常使用 传参使用 概况 react-intl-universal 是一个国际化库&#xff0c;专门为 React 应用提供多语言支持。与 React 原生的 react-intl 相比&#xff0c;react-intl-universal 支持从远程服务器加载语言包&#xff0c;动态切换语…

【途牛旅游网-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

RabbitMQ service is already present - only updating service parameters

Windows下卸载RabbitMQ之后,然后重新注册RabbitMQ服务的时候,报错以下信息: D:\software\rabbitmq-server-4.0.2\rabbitmq_server-4.0.2\sbin>D:\software\rabbitmq-server-4.0.2\rabbitmq_server-4.0.2\sbin\rabbitmq-service.bat install RabbitMQ service is already …

智能时代摩托车一键启动无钥匙进入感受科技前线

向智能化与高性能迈进,技术创新与绿色转型引领摩托车行业智能化出行。 摩托车一键启动无钥匙进入功能是一种先进的车辆控制系统&#xff0c;它允许驾驶员在不使用传统机械钥匙的情况下&#xff0c;通过智能感应技术自动解锁和启动摩托车。这种系统通常包括一个智能钥匙&#x…

第二十八篇:TCP协议概述,TCP系列二

传输控制协议&#xff08;Transmission Control Protocol&#xff0c;TCP&#xff09;是一种面向连接的、可靠的、基于字节流的传输层通信协议。在 TCP 协议中&#xff0c;通过三次握手建立连接。通信结束后&#xff0c;还需要断开连接。如果在发送数据包时&#xff0c;没有正确…

哪款宠物空气净化器性价比高?希喂、米家和范罗士哪款更好?

这次我真的不是很想抱怨&#xff0c;是我男朋友真的很过分&#xff01;真的很过分&#xff0c;差点让我们两个分道扬镳。先听我说&#xff0c;这不是我和他都嫌家里太安静了吗&#xff0c;每天下班后两个人吃完饭就各玩各的手机&#xff0c;生活太无趣了&#xff0c;加上这几年…

前端开发设计模式——状态模式

目录 一、状态模式的定义和特点 二、状态模式的结构与原理 1.结构&#xff1a; 2.原理&#xff1a; 三、状态模式的实现方式 四、状态模式的使用场景 1.按钮的不同状态&#xff1a; 2.页面加载状态&#xff1a; 3.用户登录状态&#xff1a; 五、状态模式的优点 1.提…