zoukankan      html  css  js  c++  java
  • OSGI

    OSGi 规范简介

    OSGi 联盟建立于 1999 年,是一个非赢利机构,旨在建立一个开放的服务规范。OSGi 规范为网络服务定义了一个标准的、面向组件的计算环境,它最初的目的就是为各种嵌入式设备提供通用的软件运行平台,屏蔽设备操作系统与硬件区别的中间件平台,通过这个平台,可以对不同软件商提供的应用(OSGi 中称为 Bundle)进行组件的生命周期管理的能力,如应用组件可以从运行中被安装、升级或者移除而不需要中断设备的操作,应用组件可以动态的发现和使用其他库或者应用程序。由于 OSGi 技术具有服务组件模块化、动态加载应用等优点,正被越来越多的领域关注,如嵌入设备制造业、汽车制造业、企业应用等。目前,OSGi 联盟发布的最新的 OSGi 服务规范为 4.0,读者可以查阅参考资料了解详细信息。

    OSGi 体系结构

    OSGi 的体系架构是基于插件式的软件结构,包括一个 OSGi 框架和一系列插件,在 OSGi中,插件称为 Bundle,其中,OSGi 框架规范是 OSGi 规范的核心部分,它提供了一个通用的、安全可管理的 Java 框架,通过这个框架,可以支持 Bundle 服务应用的部署和扩展。Bundle 之间可以通过 Import Package 和 Require-Bundle 来共享 Java 类,在 OSGi 服务平台中,用户通过开发 Bundle 来提供需要的功能,这些 Bundle 可以动态加载和卸载,或者根据需要远程下载和升级。OSGi 体系结构图如图 1 所示:

    图示1 OSGi 体系结构

    图示1 OSGi 体系结构

    其中:

    Execution Environment:

    Bundle 应用所倚赖运行的 Java 执行环境,如 J2SE-1.4、CDC-1.0 等都是可用的执行环境。

    Modules:

    模块层定义了 Bundle 应用的加载策略。OSGi 框架是一个健壮并且严格定义的类加载模型。在大多数 Java 应用中,通常只有一个单独的 ClassPath,它包含了所有的 Java 类文件和资源文件,OSGi基于Java技术,对于每个实现了 BundleActivator 接口的 Bundle 应用,为它生成一个单独的 ClassLoader,使得 Bundle 应用的组织更加模块化。

    Life Cycle:

    生命周期层可以动态地对 Bundle 进行安装、启动、停止、升级和卸载等操作。该层基于模块层,提供了一组 API 来控制 Bundle 应用的运行时操作。

    Service Registry 和 Services:

    OSGi 服务层定义了一个集成在生命周期层中的动态协作模型,是一个发布、动态寻找、绑定的服务模型。一个服务通常是一个 Java 对象实现了特定的服务接口,并且通过服务注册,被绑定到 OSGi 的运行环境中。Bundle 应用可以注册发布服务,动态绑定服务,并且在服务注册状态改变时,可以接受到事件消息等。

    Security:

    OSGi 的安全管理是基于 Java2 安全体系的,贯穿在 OSGi 平台的所有层中,它能够对部署在 OSGi 运行环境中的 Bundle 应用进行详细的管理控制。

    Bundle 生命周期的状态

    在一个动态扩展的 OSGi 环境中,OSGi 框架管理 Bundle 的安装和更新,同时也管理 Bundle 和服务之间的依赖关系。一个 Bundle 可能处于以下六个状态,如图 2 所示:

    图示 2 Bundle 状态图

    图示 2 Bundle 状态图

    INSTALLED:安装完成,本地资源成功加载。

    RESOLVED:依赖关系满足,这个状态意味该Bundle要么已经准备好运行,要么是被停止了。

    STARTING:Bundle正在被启动,BundleActivator的start()方法已经被调用但是还没有返回。

    STOPPING:Bundle正在被停止,BundleActivator的stop()方法已经被调用但是还没有返回。

    ACTIVE:Bundle 被成功启动并且在运行。

    UNINSTALLED:bundle被卸载并且无法进入其他状态。

    Bundle接口定义了getState()方法来返回Bundle的状态。

    OSGi 标准服务

    在 OSGi 平台之上,OSGi 联盟定义了很多服务。服务是由一个 Java Interface 来定义的,Bundle 可以实现这个接口并且把服务注册到服务注册表中去,用户可以从注册表中找到需要的服务来使用,并且可以响应特定服务的状态改变,如服务注册和服务取消。下面简单介绍一下 OSGi Release 4 的一些主要服务。OSGi 框架提供了权限管理服务,包管理服务和最初加载系统服务。这些服务是 OSGi 框架的一部分并且管理着 OSGi 框架的运作。

    Permission Admin Service:权限管理是指 Bundle 是否许可其他的 Bundle 的代码。当前的或者其他的 Bundle 的权限可以通过这个服务来操作,一旦被设定权限,马上就生效。 Package Admin Service:Bundle 之间可以共享包内的 Java 类和资源,bundle 的更新可能需要 OSGi 框架重新解析 Bundle 之间的依赖关系,这个服务提供了 OSGi 服务平台中包的共享状态信息。

    Start Level Service:Start Level是指一些在特定Bundle起动之前必须运行或者初始化的一系列 bundle。Start Lever Service 可以设置当前OSGi服务框架初始的Start Level,并且可以指定和查询特定Bundle的Start Level。

    开源OSGI容器

    从企业应用开发者的角度看,OSGI容器侵入性非常小,你可以方便地将其嵌入一个企业应用。举个例子来说,假设你在开发一个复杂的web应用。你希望将这个应用分解成多个功能模块。一个View层模块,一个Model层模块,一个DAO模块。使用嵌入式OSGI容器来跨依赖地管理这些模块可以让你随时更新你的DAO模块却不需要重启你的服务器。

    只要你的应用完全符合OSGI规范,它就可以在所有符合OSGI规范的容器内运行。现在,有三种流行的开源OSGI容器:

    1. Equinox是OSGI Service Platform Release 4的一个实现。是Eclipse 模块化运行时的核心。
    2. Knopflerfish另一个选择。
    3. Apache Felix是Apache软件基金会赞助的一个OSGI容器
     

    尝试开发一个Hello World bundle

    在OSGI的领域,发布的软件是以bundle的形式出现。bundle由java class类和资源文件组成,向设备所有者提供功能,同时可以为其他的bundles提供服务。Eclipse对开发bundles提供了强大的支持。Eclipse不仅仅提供创建bundles的功能,它还集成了Equinox这个OSGI容器,你可以在其上开发和调试OSGI组件。其实所有的Eclipse插件都是使用Eclipse规范代码写的OSGI bundle。接下来,你将可以学到如何使用Eclipse IDE开发一个Hello world osgi bundle。

    开始开发bundle

    我们一步步的开始:

    1. 启动Eclipse,依次点 File --> New --> Project。
    2. 选择Plug-in Project,next。
    3. 输入Project Name项目名称,比如com.howard.sample.HelloWorld,Target Platform(目标平台)里的an OSGI framework,选择standard。
    4. 剩下的保持默认,next。
    5. 下个对话框也默认,next。
    6. 然后选择Hello OSGI Bundle作为模版。Finish。

    Eclipse会飞快的为你创建Hello world bundle的模版代码。主要包含两个文件:Activator.java和MANIFEST.MF。

    Activator.java的代码如下所示:

    Java代码  收藏代码
    1. import org.osgi.framework.BundleActivator;  
    2. import org.osgi.framework.BundleContext;  
    3. public class Activator implements BundleActivator {  
    4.     public void start(BundleContext context) throws Exception {  
    5.         System.out.println("Hello world");  
    6.     }  
    7.     public void stop(BundleContext context) throws Exception {  
    8.         System.out.println("Goodbye World");  
    9.     }  
    10. }  

    如果你的bundle在启动和关闭的时候需要被通知,你可以考虑实现BundleActivator接口。以下是定义Activator的一些注意点:

    1. 你的Activator类需要一个公有的无参数构造函数。OSGI框架会通过类反射的方式来实例化一个Activator类。
    2. 容器启动bundle过程中负责调用你的Activator类的start方法。bundle可以在此初始化资源比如说初始化数据库连接。start方法需要一个参数,BundleContext对象。这个对象允许bundles以取得OSGI容器相关信息的方式和框架交互。如果某一个bundle有异常抛出,容器将对该bundle标记为stopped并不将其纳入service列表。
    3. 容器关闭的时候会调用你的Activator类方法stop(),你可以利用这个机会做一些清理的操作。

    MANIFEST.MF

    这个文件是你的bundle的部署描述文件。格式和Jar里的MANIFEST.MF是一样的。包含的不少名值对,就像如下:

    Xml代码  收藏代码
    1. Manifest-Version: 1.0  
    2. Bundle-ManifestVersion: 2  
    3. Bundle-Name: HelloWorld Plug-in  
    4. Bundle-SymbolicName: com.howard.sample.HelloWorld  
    5. Bundle-Version: 1.0.0  
    6. Bundle-Activator: com.howard.sample.helloworld.Activator  
    7. Bundle-Vendor: HOWARD  
    8. Bundle-RequiredExecutionEnvironment: JavaSE-1.6  
    9. Import-Package: org.osgi.framework;version="1.3.0"  

    分别来看下:

    Bundle-ManifestVersion

     数值为2意味着本bundle支持OSGI规范第四版;如果是1那就是支持OSGI规范第三版。

    Bundle-Name

     给bundle定义一个短名,方便人员阅读

    Bundle-SymbolicName

     给bundle定义一个唯一的非局部名。方便分辨。

    Bundle-Activator

     声明在start和stop事件发生时会被通知的监听类的名字。

    Import-Package

     定义bundle的导入包。

    Hello World bundle完成了,接下来我们运行一下。

    执行bundle

    1. 点击Run --> Run Configuration
    2. 在左边的OSGI Framework选项里右键 new ,创建一个新的OSGI Run Configuration
    3. 名字随便取好了,我们取个OSGi hello world。
    4. 你会注意到中间的窗口里Workspace项目里有一子项 com.howard.sample.HelloWorld,将其勾选上,其他的不用管。这时的状态应该如下图。
    5. 点击Run按钮。在控制台你应该可以看见点东西了。那是叫做OSGI控制台的东东。与子相伴,还有一个"Hello world"。


    OSGI控制台

    OSGI控制台是一个OSGI容器的命令行界面。你可以利用它做些诸如启动,关闭,安装bundles,更新和删除bundles等操作。现在,点击OSGI控制台所在的位置,回车,你就会发现可以输入命令了。这时的OSGI控制台应该如下图:


    下面列出一些常用的OSGI命令,你可以试着和OSGI容器交互。

    ss 显示已安装的bundles的状态信息,信息包括bundle ID,短名,状态等等。

    start 启动一个bundle

    stop  关闭一个bundle

    update  载入一个新的JAR文件更新一个bundle

    install  安装一个新的bundle到容器中

    uninstall  卸载一个已在容器中的bundle

    依赖管理

    OSGI规范允许你把你的应用分解成多个模块然后管理各个模块间的依赖关系。

    这需要通过bundle scope来完成。默认情况下,一个bundle内的class对其他bundle来说是不可见的。那么,如果要让一个bundle访问另一个bundle里的class要怎么做?解决的方案就是从源bundle导出包,然后在目标bundle里导入。

    接下来我们对此做一个例子。

    首先,我们需要先创建一个com.howard.sample.HelloService bundle,我们将通过它导出一个包。

    然后,我们在com.howard.sample.HelloWorld 这个bundle里导入包。

    导出包

    1、创建名为com.howard.sample.HelloService的bundle,创建步骤和前面一样。

    2、在这个bundle内,添加一个com.howard.sample.service.HelloService.java 接口,代码如下:

    Java代码  收藏代码
    1. public interface HelloService {  
    2.     public String sayHello();  
    3. }  

     

    3、创建一个com.howard.sample.service.impl.HelloServiceImpl.java类实现刚才的接口:

    Java代码  收藏代码
    1. public class HelloServiceImpl implements HelloService{  
    2.     public String sayHello() {  
    3.         System.out.println("Inside HelloServiceImple.sayHello()");  
    4.         return "Say Hello";  
    5.     }  
    6. }  

     

    4、打开MANIFEST.MF,选择Runtime标签项,在Exported Packages选项栏,点击Add并且选择com.howard.sample.service这个包。然后MANIFEST.MF的代码应该如下:

    Xml代码  收藏代码
    1. Manifest-Version: 1.0  
    2. Bundle-ManifestVersion: 2  
    3. Bundle-Name: HelloService Plug-in  
    4. Bundle-SymbolicName: com.howard.sample.HelloService  
    5. Bundle-Version: 1.0.0  
    6. Bundle-Activator: com.howard.sample.helloservice.Activator  
    7. Bundle-Vendor: HOWARD  
    8. Bundle-RequiredExecutionEnvironment: JavaSE-1.6  
    9. Import-Package: org.osgi.framework;version="1.3.0"  
    10. Export-Package: com.howard.sample.service  

     

    你可以看到,MANIFEST.MF文件和刚才的HelloWorld的那份很类似。唯一的区别就是这个多了Export-Package这个标记,对应的值就是我们刚才选择的com.howard.sample.service。

    Export-Package标记告诉OSGI容器在com.howard.sample.service包内的classes可以被外部访问。

    注意,我们仅仅暴露了HelloService接口,而不是直接暴露HelloServiceImpl实现。

    导入包

    接下来我们要更新原来的HelloWorld bundle以导入com.howard.sample.service包。步骤如下:

    1、进入HelloWorld bundle,打开MANIFEST.MF,进入Dependencies标签页,在Imported Packages里添加com.howard.sample.service。MANIFEST.MF文件应该如下所示:

    Xml代码  收藏代码
    1. Manifest-Version: 1.0  
    2. Bundle-ManifestVersion: 2  
    3. Bundle-Name: HelloWorld Plug-in  
    4. Bundle-SymbolicName: com.howard.sample.HelloWorld  
    5. Bundle-Version: 1.0.0  
    6. Bundle-Activator: com.howard.sample.helloworld.Activator  
    7. Bundle-Vendor: HOWARD  
    8. Bundle-RequiredExecutionEnvironment: JavaSE-1.6  
    9. Import-Package: com.howard.sample.service,  
    10.  org.osgi.framework;version="1.3.0"  

     

    没错,Import-package标记的值也就是导入的包名之间是用逗号隔开的。在这里导入了两个包om.howard.sample.service和org.osgi.framework。后者是使用Activator类时必须导入的包。

    2、接下来,打开HelloWorld项目下的Activator.java文件,这时候你会发现可以使用HelloService这个接口了。但还是不能使用HelloServiceImpl实现类。Eclipse会告诉你:Access restriction(立入禁止)。

    Class级别可见域

    为什么OSGI容器可以做到让jar包中的一些classes可见而另一些又不可见呢。

    答案其实就是OSGI容器自定义了java class loader来有选择的加载类。OSGI容器为每一个bundle都创建了不同的class loader。因此,bundle可以访问的classes包括

    • Boot classpath:所有的java基础类。
    • Framework classpath:OSGI框架级别的classloader加载的类
    • Bundle classpath:Bundle本身引用的关系紧密的JAR的路径
    • Imported packages:就是在MANIFEST.MF里声明的导入包,一旦声明,在bundle内就可见了。

    bundle级别的可见域允许你可以随时放心的更改HelloServiceImpl实现类而不需要去担心依赖关系会被破坏。

    OSGI服务

    OSGI框架是实现SOA的绝佳土壤。通过它可以实现bundles暴露服务接口给其他bundles消费而不需要让细节暴露。消费bundles甚至可以完全不知道提供服务的bundles。凭着可以良好的隐藏具体实现的能力,OSGI当之无愧是SOA的一种较完美的实现方案。

    OSGI中,提供服务的bundle在OSGI容器上将一个POJO注册成一个service。消费者bundle请求OSGI容器中基于某个特殊接口的注册service。一旦找到,消费者bundle就会绑定它,然后就可以调用service中的方法了。举个例子会更容易说明。

    导出services

    1、确保com.howard.sample.HelloService里的MANIFEST.MF导入org.osgi.framework包

    2、创建com.howard.sample.service.impl.HelloServiceActivator.java,代码如下:

    Java代码  收藏代码
    1. public class HelloServiceActivator implements BundleActivator {  
    2.     ServiceRegistration helloServiceRegistration;  
    3.     @Override  
    4.     public void start(BundleContext context) throws Exception {  
    5.         HelloService helloService = new HelloServiceImpl();  
    6.         helloServiceRegistration = context.registerService(HelloService.class  
    7.                 .getName(), helloService, null);  
    8.     }  
    9.   
    10.     @Override  
    11.     public void stop(BundleContext context) throws Exception {  
    12.         helloServiceRegistration.unregister();  
    13.     }  
    14.   
    15. }  

     

    OK,我们就是用BundleContext的registerService方法注册service的。这个方法需要三个参数。

    • service的接口名。如果service实现了多个接口,那样你需要传入一个包含所有接口名的String数组。在这里我们传入的是HelloService这个接口。
    • 真正的service实现。在例子中我们传了一个HelloServiceImpl实现。
    • service属性。这个参数可以在有多个service实现同一个接口的情况下,消费者用来区分真正感兴趣的service。

    3、最后一步就是修改HelloService的MANIFEST.MF文件,将Bundle-Activator改成com.howard.sample.service.impl.HelloServiceActivator

    现在HelloService bundle已经随时准备将HelloServiceImpl服务发布了。OSGI容器启动HelloServie bundle的时候会让HelloServiceActivator运作,在那个时候将HelloServiceImpl注册到容器中,接下来就是创建消费者的问题了。

    导入service

    我们的消费者就是HelloWorld bundle,主要修改的就是其中的Activator.java,修改代码如下:

    Java代码  收藏代码
    1. public class Activator implements BundleActivator {  
    2. ServiceReference helloServiceReference;  
    3.     public void start(BundleContext context) throws Exception {  
    4.         System.out.println("Hello World!!");  
    5.         helloServiceReference=context.getServiceReference(HelloService.class.getName());  
    6.         HelloService helloService=(HelloService)context.getService(helloServiceReference);  
    7.         System.out.println(helloService.sayHello());  
    8.     }  
    9.     public void stop(BundleContext context) throws Exception {  
    10.         System.out.println("Goodbye World!!");  
    11.         context.ungetService(helloServiceReference);  
    12.     }  
    13. }  

     

    代码很简单,就不多说了。

    在运行之前我们在Run-->Run Configurations对话框里,把HelloWorld和HelloService这两个bundle前面的钩都打上。然后运行时你会发现HelloServiceImpl.sayHello()方法已经被调用了。

    在OSGI控制台输入ss并回车,所有容器内的bundle状态一目了然。其中id为0的bundle是OSGI框架基础bundle,另两个就是HelloService和HelloWorld了,它俩的id是随机的,状态是ACTIVE也就是已启动状态。假设HelloService的id为7,HelloWorld为8。

    输入stop 8就可以暂停bundle的运行,容器内这个bundle还是存在的,只是状态变成了RESOLVED。再次启动使用start 8,然后就会看见HelloWorld bundle消费了HelloService的服务。

    创建服务工厂

    刚才例子所示,我们会在HelloService bundle启动时初始化并注册service。然后不管存不存在消费端,这个service都会存在,而且消费端取得的service 实例其实都是同一个。OK,某些servie是比较耗费资源的主,我们不希望它一直占用资源,最好是在真正用它的时候创建不用的时候销毁就最好了。

    解决如上问题的方案就是使用ServiceFactory接口的实现来代替原先service具体的实现到OSGI容器去注册。这样,以后只有当其他bundle请求该服务时,才会由ServiceFactory实现类来处理请求并返回一个新的service实例。

    实例步骤如下:

    1、在HelloService bundle创建一个实现ServiceFactory接口的类HelloServiceFactory类,代码如下:

    Java代码  收藏代码
    1. public class HelloServiceFactory implements ServiceFactory {  
    2.     private int usageCounter = 0;  
    3.     @Override  
    4.     public Object getService(Bundle bundle, ServiceRegistration registration) {  
    5.         System.out.println("Create object of HelloService for " + bundle.getSymbolicName());  
    6.         usageCounter++;  
    7.         System.out.println("Number of bundles using service " + usageCounter);  
    8.         HelloService helloService = new HelloServiceImpl();  
    9.         return helloService;  
    10.     }  
    11.     @Override  
    12.     public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {  
    13.         System.out.println("Release object of HelloService for " + bundle.getSymbolicName());  
    14.         usageCounter--;  
    15.         System.out.println("Number of bundles using service " + usageCounter);  
    16.     }  
    17. }  

     

    ServiceFactory接口定义了两个方法:

    • getService方法:特定的bundle在第一次调用BundleContext的getService方法时由OSGI框架调用,在实例代码中,我们用这个方法来返回一个新的HelloService的实现。OSGI框架会缓存这个返回的对象,如果同一个bundle在未来再次调用BundleContext的getService方法的话,会直接返回这个缓存中的对象。
    • ungetService方法:bundle释放service的时候由OSGI容器调用。

    2、修改HelloServiceActivator.java的start方法,将ServiceFactory作为服务注册,代码如下:

    Java代码  收藏代码
    1. public class HelloServiceActivator implements BundleActivator {  
    2.     ServiceRegistration helloServiceRegistration;  
    3.     @Override  
    4.     public void start(BundleContext context) throws Exception {  
    5.         HelloServiceFactory helloServiceFactory = new HelloServiceFactory();  
    6.         helloServiceRegistration = context.registerService(HelloService.class  
    7.                 .getName(), helloServiceFactory, null);  
    8.     }  
    9.   
    10.     @Override  
    11.     public void stop(BundleContext context) throws Exception {  
    12.         helloServiceRegistration.unregister();  
    13.     }  
    14. }  

     

    现在运行下试试看,你会发现HelloWorld bundle启动时才会初始化HelloService,控制台会打印出"Number of bundles using service 1",当HelloWorld bundle暂停时会打印出"Number of bundles using service 0"。

    services跟踪

    某种情形下,我们可能需要在某个特殊的接口有新的服务注册或取消注册时通知消费端。这时我们可以使用ServiceTracker类。如下步骤所示:

    1、在HelloWorld bundle里的MANIFEST.MF导入org.util.tracker包。

    2、创建HelloServiceTracker类,代码如下:

    Java代码  收藏代码
    1. public class HelloServiceTracker extends ServiceTracker {  
    2.     public HelloServiceTracker(BundleContext context) {  
    3.         super(context, HelloService.class.getName(),null);  
    4.     }  
    5.     public Object addingService(ServiceReference reference) {  
    6.         System.out.println("Inside HelloServiceTracker.addingService " + reference.getBundle());  
    7.         return super.addingService(reference);  
    8.     }  
    9.     public void removedService(ServiceReference reference, Object service) {  
    10.         System.out.println("Inside HelloServiceTracker.removedService " + reference.getBundle());  
    11.         super.removedService(reference, service);  
    12.     }  
    13. }  

     

    我们在HelloServiceTracker的构造函数里将HelloService接口名传进去,ServiceTracker会跟踪实现这个接口的所有的注册services。ServiceTracker主要有两个重要方法:

    • addingService方法:bundle注册一个基于给定接口的service时调用。
    • removeService方法:bundle取消注册一个基于给定接口的service时调用。

    3、修改Activator类,使用刚刚创建的HelloServiceTracker来获取service:

    Java代码  收藏代码
    1. public class Activator implements BundleActivator {  
    2.     HelloServiceTracker helloServiceTracker;  
    3.     public void start(BundleContext context) throws Exception {  
    4.         System.out.println("Hello World!!");  
    5.         helloServiceTracker= new HelloServiceTracker(context);  
    6.         helloServiceTracker.open();  
    7.         HelloService helloService=(HelloService)helloServiceTracker.getService();  
    8.         System.out.println(helloService.sayHello());  
    9.     }  
    10.       
    11.     public void stop(BundleContext context) throws Exception {  
    12.         System.out.println("Goodbye World!!");  
    13.         helloServiceTracker.close();  
    14.     }  
    15. }  

     

    现在运行一下,可以发现只要HelloService bundle启动或是暂停都会导致HelloServiceTracker的对addingService或removedService方法的调用。

    ServiceTracker不仅仅能跟踪Service的动向,它还能通过getService方法取得Service实例并返回。但是如果同一个接口下有多个service注册,这时返回哪个service呢?这时候就需要看service的等级哪个高了。这个等级是service注册时的property属性里的一项:SERVICE_RANKING。谁的SERVICE_RANKING高,就返回谁。

    如果有两个一样高的呢?这时再看这两个service谁的PID更低了。

    如果对OSGI的ClassLoader机制有疑问,可以看看这篇解释ClassLoader机制和自定义ClassLoader相关的文章:

    http://longdick.iteye.com/blog/442213

    OSGI的基本原理和入门开发就到这里了。希望同学们能对OSGI开发有个简单而清晰的认识。

  • 相关阅读:
    学习asp.net比较完整的流程
    [图像思考法]我用图像故事法来理解与记忆冒泡排序
    MVC4与JSON交互的知识总结
    vulhub漏洞环境库内容整理
    kaliapt update时报The following signatures couldn't be verified because the public key is not available: NO_PUBKEY ED444FF07D8D0BF6解决方案
    sqlilabs靶机测试笔记
    Lord_Of_The_Root1.0.1靶机测试笔记
    utf16的单引号乱码,用于post型宽字节注入
    ubuntu16.04报错AttributeError: module 'platform' has no attribute 'linux_distribution'
    一文搞定OSCP的缓冲区溢出
  • 原文地址:https://www.cnblogs.com/FZ1314/p/6804417.html
Copyright © 2011-2022 走看看