`
liuzhaomin
  • 浏览: 198607 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Apache中多任务并发处理机制研究(1)

阅读更多

//转载请注明来源:http://blog.csdn.net/tingya
//版权声明:
//本书是《Apache源代码全景分析》的草稿部分,
//读者可以自由浏览和打印
//未经本文允许,不得以任何形式出现在盈利印刷品中,否则将追究法律责任!!!

6.1 多进程并发处理概述

<chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on"><span lang="EN-US">6.1.1</span></chsdate> 概述

第五章中我们讨论Apache主程序的时候,当主程序调用了函数ap_mpm_run之后,整个主程序就算结束了。那么函数进入到ap_mpm_run之后它干什么去了呢?

如果让我们来写服务器程序的话,按照正常的思路,通常主程序在进行了必要的准备工作后会调用诸如fork之类的函数产生一个新的进程或者线程,然后由子进程进行并发处理。事实上,Apache尽管是一个先进的服务器,但是它也不能脱离窠臼。

主进程一旦调用ap_mpm_run之后,它就进入多进程并发处理状态。为了并发处理客户端请求,Apache或者产生多个进程,或者产生多个线程,或者产生多个进程,每个进程又产生一定数目的线程等等。

Apache HTTP服务器从一开始就被设计为一个强大、灵活的能够在多种平台上及不同的环境下工作的服务器。 不同的平台和不同的环境经常产生不同的需求,或是会为了达到同样的最佳效果而采用不同的方法。当然,Apache中提供了多种多进程并发模型,比如PreforkWindow NTEventperchild等等。并且为了方便移植和替换,Apache将这些多进程并发处理模型设计成模块。Apache凭借它的模块设计很好的适应了大量不同的环境。这一设计使得网站管理员能够在编译时和运行时凭借载入不同的模块来决定服务器的不同附加功能。

Apache 2.0 将这种模块式设计延伸到web服务器的基础功能上。这个发布版本带有多道处理模块的选择以处理网络端口绑定、接受请求并指派子进程来处理这些请求。

将模块设计延伸到这一层面主要有以下两大好处:

Apache可以更简洁、更有效地支持各种操作系统。 尤其是在mpm_winnt使用本地网络特性以代替Apache 1.3中使用的POSIX层后, Windows版本的Apache现在有了更好的性能。 这个优势借助特定的MPM同样延伸到其他各种操作系统。

服务器可以为某些特定的站点进行自定义。比如, 需要更好缩扩性的站点可以选择象worker这样线程化的MPM 而需要更好的稳定性和兼容性以适应一些旧的软件的站点可以用prefork 此外,象用不同的用户号(perchild)伺服不同的站点这样的特性也能提供了。

从用户层面来讲,MPMs更像其他Apache模块。而主要的不同在于:不论何时,有且仅有一个MPM必须被载入到服务器中。现有的MPM列表可以在这里找到模块索引

下表列出了不同操作系统下默认的MPMs。如果你在编译时没有进行选择,这将是默认选择的MPM

BeOS

beos

Netware

mpm_netware

OS/2

mpmt_os2

Unix

prefork

Windows

mpm_winnt

在详细深入的描述各个MPM之前,我们有必要了解一下MPM中所使用到的公共数据结构,主要包括两种:记分板和父子进程的通信管道。记分板类似于共享内存,主要用于父子进程之间进行数据交换,类似于白板。任何一方都可以将对方需要的信息写入到记分板上,同时任何一方也可以到记分板上获取需要的数据。

6.2 MPM公共数据结构

<chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on"><span lang="EN-US"><font face="Verdana">6.2.1</font></span></chsdate>记分板

<chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on"><span lang="EN-US">6.2.1</span></chsdate>.1记分板概述

ApacheMPM中通常总是包含一个主服务进程以及若干个子进程,因此不可避免的存在主进程和子进程通信的问题。Apache中采用了两种主要的通信方法:记分板和管道。

记分板就是一块共享内存块,同时可以被父进程和子进程访问,通过共享实现了父子之间的通信。尽管如此,但是记分板则更主要用于父进程对子进程进行控制。在Apache中主进程的一个重要的职责就是控制空闲子进程的数目:如果空闲子进程过多,则父进程将终止一些子进程;如果空闲子进程太少,则父进程将创建一些新的空闲子进程以备使用。因此,父进程必须随时能够知道子进程的数目以便进行调整。子进程把自己的状态信息忙碌或者空闲写入到记分板中,这样通过读取记分板,父进程就可以知道子进程的数目了。

记分板的数据结构可以描述如下:

typedef struct {

global_score *global;

process_score *parent;

worker_score **servers;

} scoreboard;

该结构定义在scoreboard.h中,由该数据结构可见,Apache中的记分板可以记录三种类型的信息:全局信息、进程间共享信息以及线程间共享信息。

global_score是记分板中描述全局信息的结构,通常这些信息是针对整个Apache服务器的,而不是针对某个进程或者某个线程的,该结构定义如下:

typedef struct {

int server_limit;

int thread_limit;

ap_scoreboard_e sb_type;

ap_generation_t running_generation;

apr_time_t restart_time;

} global_score;

从该结构中我们可以看出,全局的共享信息包括下面的几个内容:server_limit描述系统中所存在的服务进程的极限值,thread_limit则是描述的线程的极限值。ap_scoreboard是枚举类型,只有两个值SB_NOT_SHAREDSB_SHARED,分别表示该记分板是否进程间共享还是不共享。

ap_generation_t的定义实际上是整数值:typedef int ap_generation_t。该值主要用于平稳启动(graceful restart) Apache中允许在不终止Apache的情况下对Apache进行重新启动,这种启动称之为平稳启动。平稳启动的时候,主服务进程将退出,同时创建新的子进程。此时这些子进程由父进程创建,它们形成一个继承称此上的家族概念,只要是主进程产生的所有子进程都属于这个主进程家族,因此我们称它们称之为为新的(generation),在本书中我们统一用家族这个术语进行描述。只有子进程与父进程具有亲缘关系,它们才是一个家族。每一个进程在执行完任务之后都会检查它与当前的主进程是否属于同一个家族。如果属于,则继续等待处理下一个任务;否则其将退出。由于Apache在进行平稳启动的时候对于那些尚未结束的进程并不强行将其终止,而是让其继续执行,但是主进程必须退出重新启动。因此当新的主进程启动之后,这些残余的子进程显然已经跟它不是同一个家族,它们属于上一辈的。因此他们在执行完任务之后立即退出。

某个主进程产生后它就产生一个唯一的家族号,用running_generation进行记录。该值永远不会重复。主进程的所有子进程将继承该家族号。running_generation是识别其家族的唯一标记。如果子进程的running_generation与父进程相同,则说明本家族的进程尚存在;反之,如果不相同,则它们执行完后必须结束。这正应了一句古语:覆巢之下无完卵或者为树倒猢狲散阿。

restart_time则记录了主服务器重新启动的时间。

进程间通信则可以使用process_score进行,其定义如下:

typedef struct process_score process_score;

struct process_score{

pid_t pid;

ap_generation_t generation; /* generation of this child */

ap_scoreboard_e sb_type;

int quiescing;

};

通常情况下,父进程往该数据结构中写入数据,而子进程则从其中读取数据。其中pid是主进程的进程号;generation则是当前主进程以及其产生的所有子进程的家族号。sb_type的含义与global_score中的sb_type含义相同。

Quiescing

process_score用于主进程和子进程通信不同,worker_score则用于记录线程的运行信息,其定义如下:

typedef struct worker_score worker_score;

struct worker_score {

/*第一部分*/

int thread_num;

#if APR_HAS_THREADS

apr_os_thread_t tid;

#endif

unsigned char status;

/*第二部分*/

unsigned long access_count;

apr_off_t bytes_served;

unsigned long my_access_count;

apr_off_t my_bytes_served;

apr_off_t conn_bytes;

unsigned short conn_count;

/*第三部分*/

apr_time_t start_time;

apr_time_t stop_time;

#ifdef HAVE_TIMES

struct tms times;

#endif

apr_time_t last_used;

/*第四部分*/

char client[32]; /* Keep 'em small... */

char request[64]; /* We just want an idea... */

char vhost[32]; /* What virtual host is being accessed? */

};

整个worker_score结构可以被分成四部分理解:

第一部分,主要描述线程的状态和识别信息

thread_numApache识别该线程的唯一识别号,tid则是该线程的线程号。两者是不同的概念:后者是由操作系统或者线程库分配,应用程序无法参与,而前者是Apache设定,跟操作系统无关,具体的含义也只有Apache本身理解。不过thread_num通常遵循下面的设定原则:

thread_num = 线程所在的进程的索引 * 每个进程允许产生的线程极限 + 线程在进程内的索引

status则是当前线程的状态,它的状态种类与进程的状态种类相同,用SERVER_XXX常量进行识别。

第二部分,主要描述线程的状态和识别信息

第三部分,主要描述线程相关的时间信息

start_timestop_time分别是记录线程的启动和停止时间。last_used则用于记录线程最后一次使用的时间。

第四部分,主要描述线程的状态和识别信息

该部分主要描述当前线程处理的请求连接上的相关信息。client是请求客户端的主机名称或者是IP地址。request则是客户端发送的请求行信息,比如”GET /server-status?refresh=100 HTTP/<chmetcnv w:st="on" unitname="”" sourcevalue="1.1" hasspace="False" negative="False" numbertype="1" tcsc="0">1.1”</chmetcnv>,而vhost则是当前请求所请求的虚拟主机名称,比如”www.myserver.com”

worker_score结构中可以看出,该结构中的记录的大部分信息并不是线程间通信而需要的。那么这些信息到底做什么用的呢?为什么worker_score结构中需要记录这些信息呢?为此我们必须了解Apache中的一个特殊的功能模块mod_status。尽管这个模块要到第三卷才能详细介绍,但是我们还是提前描述。

为了时刻了解Apache的运行状态,一种方法就是直接在服务器上检测,另一种方法就是远程监控,通过http://www.xxxx.com/server-status URI在浏览器中显示服务器的信息:

<shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"></path><lock v:ext="edit" aspectratio="t"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 477pt; HEIGHT: 465pt" type="#_x0000_t75"><imagedata src="file:///C:%5CDOCUME~1%5CADMINI~1%5CLOCALS~1%5CTemp%5Cmsohtml1%5C01%5Cclip_image001.png" o:title=""></imagedata></shape>

每一个进程或者线程都将自身信息写入到记分板上,这样,mod_status通过读取记分板,就可以知道各个线程的运行状态信息。当然,如果需要显示的信息越多,记分板上需要保存的信息也就越多,worker_score结构也就需要扩展。

除此之外,Apache中还定义了几个与记分板相关的全局变量,它们是记分板的核心变量:

(1)AP_DECLARE_DATA extern scoreboard *ap_scoreboard_image;

Apache使用使用该变量记录全局记分板,任何进程或者线程都可以通过ap_scoreboard_image直接访问记分板。

(2)AP_DECLARE_DATA extern const char *ap_scoreboard_fname;

该全局变量描述了记分板的名称。

(3)AP_DECLARE_DATA extern int ap_extended_status;

该全局变量描述了当前记分板的状态,

(4)AP_DECLARE_DATA extern ap_generation_t volatile ap_my_generation;

该全局变量描述了当前Apache中的主进程的家族号,任何时候,主进程只要退出进行重新启动,ap_my_generation都会跟着发生变化。该变量与其余的变量相比特殊的地方在于它被声明为volatile类型。

(5)static apr_size_t scoreboard_size;

该变量记录整个记分板所占用的内存的大小。

在了解了记分板的数据结构之后,我们有必要了解一下记分板的内存组织结构,它的内存布局可以用下图进行描述:

<chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on"><span lang="EN-US">6.1.1</span></chsdate>.2记分板处理函数

从前一节的图片中我们看一看出,每个记分板都包括多个插槽,每一个插槽分别用于记录一个进程的相关信息,不过这种记录的进程信息相对非常的简单,仅仅包括进程的当前状态以及进程号。从记分板的角度而言,每一个进程可以处于12中不同的状态:

#define SERVER_DEAD 0 /* 当前的进程执行完毕*/

#define SERVER_STARTING 1 /* 进程刚开始执行 */

#define SERVER_READY 2 /* 进程已经准备就绪,正在等待客户端连接 */

#define SERVER_BUSY_READ 3 /* 进程正在读取客户端的请求*/

#define SERVER_BUSY_WRITE 4 /* 进程正在处理客户端的请求*/

#define SERVER_BUSY_KEEPALIVE 5 /* 进程在同一个活动连接上正在等待更多的请求*/

#define SERVER_BUSY_LOG 6 /* 进程正在进行日志操作*/

#define SERVER_BUSY_DNS 7 /* 进程正在查找主机名称 */

#define SERVER_CLOSING 8 /* 进程正在关闭连接 */

#define SERVER_GRACEFUL 9 /* 进程正在平稳的完成请求 */

#define SERVER_IDLE_KILL 10 /* 进程正在清除空闲进程. */

#define SERVER_NUM_STATUS 11 /* 进程的多个状态都被设置 */

进程总是从状态SERVER_STARTING开始,最后在SERVER_DEAD状态结束。当一个进程的状态处于SERVER_DEAD的时候,意味着记分板中的该插槽可以被重新利用。

<chsdate year="1899" month="12" day="30" islunardate="False" isrocdate="False" w:st="on"><span lang="EN-US">6.1.1</span></chsdate>.2.1创建记分板

记分板的所有的操作都是从创建开始的,通常只有在刚启动Apache或者平稳启动之后才需要创建。Apache中通过ap_create_scoreboard函数实现记分板的创建,该函数在scoreboard.c中实现,函数原型如下:

int ap_create_scoreboard(apr_pool_t *p, ap_scoreboard_e sb_type);

参数p指定创建记分板中所需要的内存来自的内存池,而sb_type则是创建的记分板的类型,或者为SB_SHARED,或者为SB_NOT_SHARED。前者允许记分板在不同的进程之间共享,而后者则不允许。

int running_gen = 0;

int i;

if (ap_scoreboard_image) {

running_gen = ap_scoreboard_image->global->running_generation;

ap_scoreboard_image->global->restart_time = apr_time_now();

memset(ap_scoreboard_image->parent, 0, sizeof(process_score) * server_limit);

for (i = 0; i < server_limit; i++) {

memset(ap_scoreboard_image->servers[i], 0, sizeof(worker_score) * thread_limit);

}

if (lb_limit) {

memset(ap_scoreboard_image->balancers, 0, sizeof(lb_score) * lb_limit);

}

return OK;

}

公告板的创建与几个系统值密切相关的,比如server_limitthread_limitserver_limit描述了允许同时存在的进程的最大极限,包括父进程和子进程,每一个进程通常都是用上面的process_score数据结构进行描述;而thread_limit则是每一个子进程又允许生成的子线程的数目,这些子线程用worker_score进行描述。因此创建记分板的一个重要的步骤就是分配足够的插槽。由于Apache需要记录每一个进程以及进程中的每一个线程的运行信息,因此,创建记分板之前必须能够分配足够多的空间以容纳process_scoreworker_score结构。

正如前面描述,系统中允许存在server_limit个进程,它们中的每一个都必须在记分板中拥有一个插槽,因此我们至少必须分配sizeof(process_score)*server_limit大小的内存空间,同时使用ap_scoreboard_image->parent指向该空间。

同时对于server_limit个进程中的每一个进程,他们可能产生的线程数为thread_limit,这些线程也必须在记分板中拥有相应的插槽,为此共分配server_limit*sizeof(worker_score)*thread_limit的内存大小。

Apache按照最大化的原则进行分配,一旦分配完毕肯定能够保证需要。不过这样的话可能存在很多的空闲插槽。因为即使只有一个进程和一个线程存在,Apache也是会分配所有的内存的。

现在回到上面的代码中。如果是平稳启动,那么在创建新的记分板之前系统中应该已经存在一个旧的记分板(ap_scoreboard_image不为NULL)。在这种情况下,Apache首先得到当前记分板的家族号,同时重新设置启动时间。另外一个重要的任务就是清理初始化记分板上的数据,将其全部清零,彻底扫荡前一个家族的所有信息,并将其返回出去供使用。

ap_calc_scoreboard_size();

如果创建的时候发现公告板不存在,那么这意味着这是Apache启动以来的第一次记分板创建。因此创建之前必须计算记分板分配的空间大小。创建记分板的内存大小由函数ap_calc_scoreboard_size()函数完成:

AP_DECLARE(int) ap_calc_scoreboard_size(void)

{

ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);

ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &server_limit);

if (!proxy_lb_workers)

proxy_lb_workers = APR_RETRIEVE_OPTIONAL_FN(ap_proxy_lb_workers);

if (proxy_lb_workers)

lb_limit = proxy_lb_workers();

else

lb_limit = 0;

scoreboard_size = sizeof(global_score); u

scoreboard_size += sizeof(process_score) * server_limit; v

scoreboard_size += sizeof(worker_score) * server_limit * thread_limit; w

if (lb_limit)

scoreboard_size += sizeof(lb_score) * lb_limit;x

return scoreboard_size;

}

如前所述,记分板内存大小由thread_limitserver_limit决定,这两个值可以通过ap_mpm_query进行查询键值AP_MPMQ_HARD_LIMIT_DAEMONSAP_MPMQ_HARD_LIMIT_THREADS获取。一旦确定这两个核心值,那么我们就可以计算记分板的分配空间,它包括四个部分:

(1)global_score的大小,因此在整个系统中global_score只有一个,因此它所占的大小为sizeof(global_score),如u说示。

(2)、由于每一个进程都必须在记分板中拥有一个插槽来记录其相关信息,因此server_limit个进程所占的插槽的大小为server_limit*sizeof(process_score),如v所示。

(3)、对于每一个进程而言,其允许产生的线程的数目为thread_limit个,因此server_limit个进程允许产生的总线程数目为thread_limit个,这些线程也必须在记分板中拥有各自的信息插槽它们所占的内存为server_limit*thread_limit*sizeof(worker_score),如w所示。

(4)、,如x所示。

很容易看出,Apache必须为记分板分配的空间大小为sizeof(global_score) + server_limit*sizeof(process_score) + server_limit*thread_limit*sizeof(worker_thread)+ sizeof(lb_score) * lb_limit。计算后的内存大小保存在scoreboard_size全局变量中。

#if APR_HAS_SHARED_MEMORY

if (sb_type == SB_SHARED) {

void *sb_shared;

rv = open_scoreboard(p);

if (rv || !(sb_shared = apr_shm_baseaddr_get(ap_scoreboard_shm))) {

return HTTP_INTERNAL_SERVER_ERROR;

}

memset(sb_shared, 0, scoreboard_size);

ap_init_scoreboard(sb_shared);

}

else

#endif

{

/* A simple malloc will suffice */

void *sb_mem = calloc(1, scoreboard_size);

if (sb_mem == NULL) {

ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL,

"(%d)%s: cannot allocate scoreboard",

errno, strerror(errno));

return HTTP_INTERNAL_SERVER_ERROR;

}

ap_init_scoreboard(sb_mem);

}

正常情况下,记分板应该作为共享内存存在从而被访问,但是并不是所有的操作系统都支持共享内存的操作。因此不同的操作系统,可能会采取不同的措施。

对于那些不支持共享内存的操作系统,Apache只是简单的调用calloc函数分配scoreboard_size大小的内存块,同时调用ap_init_scoreboard对其进行初始化而已;如果操作系统支持共享内存,那么Apache将采用IPC技术创建一块共享内存,同时使用apr_shm_baseaddr_get得到该共享内存的首地址,并对其进行初始化。

ap_scoreboard_image->global->sb_type = sb_type;

ap_scoreboard_image->global->running_generation = running_gen;

ap_scoreboard_image->global->restart_time = apr_time_now();

<spa

分享到:
评论

相关推荐

    Apache源代码全景分析第1卷:体系结构与核心模块 part1

    书中详细介绍了Apache的基础体系结构和核心模块的实现机制,包括配置文件、模块化结构、多任务并发,以及网络连接和请求读取,其中多任务并发体系结构是《Apache源代码全景分析第1卷:体系结构与核心模块》分析的...

    Apache源代码全景分析第1卷:体系结构与核心模块 part2

    书中详细介绍了Apache的基础体系结构和核心模块的实现机制,包括配置文件、模块化结构、多任务并发,以及网络连接和请求读取,其中多任务并发体系结构是《Apache源代码全景分析第1卷:体系结构与核心模块》分析的...

    大数据处理框架.pdf

    流处理系统 1批处理系统 批处理的过程包括将任务分解为较⼩的任务,分别在集群中的每个计算机上进⾏计算,根据中间结果重新组合数据,然后计算和组合最终结 果。所以批处理系统主要操作⼤量的、静态的数据,并且等到...

    大数据中台架构栈.doc

    下面结合一个大数据实时处理系统阐述下 Flume 在实际应用中所扮演的重要角色。该实时处理系统整体架构如下:通过将 Agent 部署在 Web 效劳器,一旦发生新增的日志数据,就会被 Flume 程序监听到,并且最终会传输到 ...

    java开源包101

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包1

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包11

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包2

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包3

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包6

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包5

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包10

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包4

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包8

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包7

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包9

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    Java资源包01

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    JAVA上百实例源码以及开源项目

     数字证书:从文件中读取数字证书,生成文件输入流,输入文件为c:/mycert.cer,获取一个处理X.509证书的证书工厂…… Java+ajax写的登录实例 1个目标文件 内容索引:Java源码,初学实例,ajax,登录  一个Java+ajax写...

    JAVA上百实例源码以及开源项目源代码

     数字证书:从文件中读取数字证书,生成文件输入流,输入文件为c:/mycert.cer,获取一个处理X.509证书的证书工厂…… Java+ajax写的登录实例 1个目标文件 内容索引:Java源码,初学实例,ajax,登录  一个Java+ajax写...

Global site tag (gtag.js) - Google Analytics