nginx+spawn-fcgi+demo+fcgi库函数

article/2025/10/18 21:35:51

由于项目中用到了nginx+FastCGI相关内容,所以这段时间学习了一下,顺便记下相关内容。

我是在远程工作机上实验的,有个缺点就是没有root权限,所以有些步骤我就没做了,比如make install(nginx)、添加到服务管理列表等等,仅在make之后二进制所在目录上进行执行,使用上完全没问题。还有个优点就是有些库已经全部安装好了,比如openssl、zlib等等,少了一些步骤。

一.nginx

1.安装过程:

基本按这篇文章的步骤来:《Nginx安装与使用》,仅仅没有make install。

后半部分也简单介绍了nginx的配置,即conf目录下的文件,重点是nginx.conf。

有个地方要提一下,这篇文章讲了fastcgi_params与fastcgi.conf的关系,这个在很多别的文章里都没有提到,甚至在后面配置FastCGI的时候很多文章实际上都配置错了。关系如下:

fastcgi.conf只比fastcgi_params多了一行“fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;”

原本只有fastcgi_params文件,fastcgi.conf是nginx 0.8.30 (released: 15th of December 2009)才引入的。主要为是解决以下问题(参考:http://www.dwz.cn/x3GIJ):

原本Nginx只有fastcgi_params,后来发现很多人在定义SCRIPT_FILENAME时使用了硬编码的方式。例如,fastcgi_param SCRIPT_FILENAME /var/www/foo$fastcgi_script_name。于是为了规范用法便引入了fastcgi.conf。

不过这样的话就产生一个疑问:为什么一定要引入一个新的配置文件,而不是修改旧的配置文件?这是因为fastcgi_param指令是数组型的,和普通指令相同的是:内层替换外层;和普通指令不同的是:当在同级多次使用的时候,是新增而不是替换。换句话说,如果在同级定义两次SCRIPT_FILENAME,那么它们都会被发送到后端,这可能会导致一些潜在的问题,为了避免此类情况,便引入了一个新的配置文件。

因此不再建议大家使用以下方式(搜了一下,网上大量的文章,并且nginx.conf的默认配置也是使用这种方式):

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
而使用最新的方式:
include fastcgi.conf;

2.配置

上面推荐的那篇文章简单介绍了一些配置,但远远不够。
这里推荐这篇文章:《Nginx 反向代理、负载均衡、页面缓存、URL重写及读写分离详解》,正如题目所说,讲了nginx方向代理、负载均衡、页面缓存、URl重写、读写分离等功能的配置,很详细,按上面内容step by step走一遍,基本了解相关配置了。

其中有几个步骤要求修改httpd配置文件、并重启httpd,由于我没有root权限所以没法实验。

二、什么是CGI、FastCGI、PHP-CGI、PHP-FPM、Spawn-FCGI

要学习FastCGI,首先要搞清楚这几个术语,我也稍微整理了一些内容,大概搞懂了它们的关系。
推荐自己整理的文章:《【整理】什么是CGI、FastCGI、PHP-CGI、PHP-FPM、Spawn-FCGI?》

然后就是这张图描述Nginx+FastCGI运行过程​:
这里写图片描述

Nginx不支持对外部程序的直接调用或者解析,所有的外部程序(包括PHP)必须通过FastCGI接口来调用。FastCGI接口在Linux下是socket(这个socket可以是文件socket,也可以是ip socket)。为了调用CGI程序,还需要一个FastCGI的wrapper(wrapper可以理解为用于启动另一个程序的程序),这个wrapper绑定在某个固定socket上,如端口或者文件socket。

当Nginx将CGI请求发送给这个socket的时候,通过FastCGI接口,wrapper接收到请求,然后派生出一个新的线程,这个线程调用解释器或者外部程序处理脚本并读取返回数据;接着,wrapper再将返回的数据通过FastCGI接口,沿着固定的socket传递给Nginx;最后,Nginx将返回的数据发送给客户端。这就是Nginx+FastCGI的整个运作过程,如图1所示。

这里我要说下Spawn-FCGI在这种图中的主要功能:打开监听端口,绑定地址,然后fork-exec创建FastCGI进程,退出完成工作。

由于是fork-exec创建子进程,所以子进程能够继承父进程的所有打开句柄,包括监听socket。这样所有子进程都能够在同一个端口上进行监听新连接,谁拿到了谁就处理之。这是Spawn-FCGI最重要的功能。

Spawn-FCGI源码只有600多行,以后有机会可以研究一下。(参考:http://www.it165.net/pro/html/201411/27470.html)

三、spawn-fastcgi的安装

正确步骤是:
1. 解压
2. 如果没有configure,请先执行./autogen.sh,生成configure
3. ./configure
4. make
5. 编译好以后,将可执行文件移动到nginx的二进制所在目录(由于我没有make install,我的nginx二进制在nginx-1.10.1/objs目录下)

但无论下载spawn-fcgi-1.6.4还是spawn-fcgi-1.6.3,执行./autogen.sh时都提示:

configure.ac:4: error: Autoconf version 2.61 or higher is required
configure.ac:4: the top level
autom4te: /usr/bin/m4 failed with exit status: 63
aclocal: autom4te failed with exit status: 63
autoreconf: aclocal failed with exit status: 63

于是下载编译autoconf-2.68,又提示:

checking for GNU M4 that supports accurate traces… configure: error: no acceptable m4 could be found in $PATH.
GNU M4 1.4.6 or later is required; 1.4.16 or newer is recommended.
GNU M4 1.4.15 uses a buggy replacement strstr on some systems.
Glibc 2.9 - 2.12 and GNU M4 1.4.11 - 1.4.15 have another strstr bug.

然后又下载安装m4-1.4.9,make、make install,提示:

/usr/bin/install: cannot create regular file `/usr/local/bin/m4’: Permission denied
make64[2]: * [install-binPROGRAMS] Error 1

也就是说,正常顺序是m4–>autoconf–>spawn-fcgi,由于我没有root权限,无法install m4,导致无法编译spawn-fcgi。

所以这里只是记录下我遇到问题的过程,实际上我并没有解决这个问题。好在我在其他项目工程里找到了spawn_fcgi的二进制,我就假装此步骤已经完成了吧。。。。。

将spawn_fcgi二进制拷贝到nginx二进制所在目录。

四、fcgi库

很多老帖都是发的这个地址:http://www.fastcgi.com/dist/fcgi.tar.gz,实际上现在已经打不开了。
这个地址是可以下载的:ftp://ftp.slackware.com/.2/gentoo/distfiles/fcgi-2.4.0.tar.gz

这个库不是必须的,你可以用其他类似库,也是自己实现。

解压后编译:
./configure
make

在./libfcgi/.libs/目录下生成两个静态库文件:libfcgi.a、libfcgi++.a,后面编译demo的时候需要链接这两个静态库。

然后就是头文件,主要是这几个头文件:fastcgi.h,fcgiapp.h,fcgi_config.h,fcgi_config_x86.h,fcgimisc.h,fcgio.h,fcgios.h,fcgi_stdio.h,最初分布在./和./include目录下,为了后面编译方便,全部放到./include目录下。

五、demo

1. echo_cpp

其实在fcgi库中./examples目录下有好几个例子,我们先快速实现一下,再去细看其中用到的函数。

我用./examples/echo-cpp.cpp为例,编译此源码:

g++ echo-cpp.cpp -I ../include/ -o echo_cpp ../libfcgi/.libs/libfcgi.a ../libfcgi/.libs/libfcgi++.a

生成“echo_cpp”二进制。

将此二进制拷贝到nginx二进制所在目录。

经过上面一系列步骤,nginx、spawn_fcgi、echo_cpp三个二进制是在同一个目录下。

2. 启动spawn_fcgi

启动spawn_fcgi,绑定server IP和端口,注意不要跟nginx的监听端口重合、也不能绑定其他程序已使用的端口,否则会报错,

./spawn_fcgi -f ./echo_cpp -p 19890 -a 12.34.56.78 -F 10

可以用netstat -apn | grep 19890查看端口绑定是否已经成功。

3. 修改nginx.conf

在server节点下增加以下转发规则,即访问的url以.cgi结尾时进行转发:
location ~ \.cgi$ {
fastcgi_pass 12.34.56.78:19890;
fastcgi_index index.cgi;
fastcgi_param SCRIPT_FILENAME fcgi$fastcgi_script_name;
include fastcgi.conf;
}

修改后,nginx重新加载配置文件:

nginx -s reload

4.测试结果

浏览器访问(nginx服务器的ip与port):http://12.34.56.78:8010/xxx.cgi
显示的截图如下(较长,截了一部分):
这里写图片描述

六、fcgi库的方法

本节主要参考:《fastcgi中的多线程使用》,有改动。

1. 一个简单的单线程fcgi请求:

    #include <fcgi_stdio.h>void main(void){int count = 0;while(FCGI_Accept() >= 0) {printf("Content-type: text/html\r\n");printf("\r\n");printf("Hello world!<br>\r\n");printf("Request number %d.", count++);}exit(0);}

原理:有一个死循环,一直等待接受请求,有请求过来时,就处理请求,并返回结果,没有并发性。

这里为什么printf输出内容就返回到nginx呢?

因为在fcgi_stdio.h中对printf进行了宏转向,在程序里的printf 不再是标准输出了:

//fcgi_stdio.h
#undef  printf
#define printf   FCGI_printf

除此之外,fcgi_stdio.h中还对很多常见的输入输出函数进行了宏转向,例如stdin,stdout,stderr,fgets,fputs等等。

2. 进入FCGI_Accept(void)

此方法在文件fcgi_stdio.c里

int FCGI_Accept(void)
{//变量表示是否接收请求。默认为Fasle,不接收请求if(!acceptCalled) {//判断是否为cgi,变量为全局静态的,下次还会用。isCGI = FCGX_IsCGI();//状态改为接收请求。acceptCalled = TRUE;//请求的收尾,将数值清空赋初值。atexit(&FCGI_Finish);} else if(isCGI) {//不是第一次请求,并且是cgi程序。return(EOF);}if(isCGI) {//cgi的初始赋值操作,不关心。...} else {FCGX_Stream *in, *out, *error;//char** 字符串数组。FCGX_ParamArray envp;//接受请求,这个方法下面介绍int acceptResult = FCGX_Accept(&in, &out, &error, &envp);//接收失败,返回<0,这也是为什么在循环上判断是 while(FCGI_Accept() >= 0)if(acceptResult < 0) {return acceptResult;}//将得到的数据赋值给对应的输出,输入,data。FCGI_stdin->stdio_stream = NULL;FCGI_stdin->fcgx_stream = in;FCGI_stdout->stdio_stream = NULL;FCGI_stdout->fcgx_stream = out;FCGI_stderr->stdio_stream = NULL;FCGI_stderr->fcgx_stream = error;environ = envp;}//结束return 0;
}

通过上面代码可以看出,fcgi库是同时支持CGI和FCGI两种方式的。

返回值:

=0:成功
-1:失败

3. FCGX_Accept (&in, &out, &error, &envp)

等待接收请求的方法,在fcgiapp.c里

static FCGX_Request the_request;int FCGX_Accept(FCGX_Stream **in,FCGX_Stream **out,FCGX_Stream **err,FCGX_ParamArray *envp)
{int rc;//定义返回的变量。//是否初始化过。if (! libInitialized) {//FCGX_Init返回0时表示成功rc = FCGX_Init();if (rc) {return rc;}}//接收数据,下面介绍rc = FCGX_Accept_r(&the_request);//给对应流和数据赋值。*in = the_request.in;*out = the_request.out;*err = the_request.err;*envp = the_request.envp;return rc;
}

返回值:

=0:成功
=-1:失败

4. FCGX_Accept_r ()

同在fcgiapp.c里面

/**----------------------------------------------------------------------** FCGX_Accept_r --**      从http server 接收一个新的请求* Results:*      正确返回0,错误返回-1.* Side effects:**      通过FCGX_Accept完成请求的接收,创建input,output等流并且各自分配给in*      ,out,err.创建参数数据从FCGX_GetParam中取出,并且给envp。
&nbsp;*      不要保存指针和字符串,他们会在下次请求中的FcGX_Finish中被释放。*----------------------------------------------------------------------*/
int FCGX_Accept_r(FCGX_Request *reqDataPtr)
{if (!libInitialized) {return -9998;}//将当前的reqData完成请求。将内容释放,初始化。FCGX_Finish_r(reqDataPtr);//whilefor (;;) {//ipcFd 是双方通讯的管道,在上面的FCGX_Finish_r中被赋值-1。if (reqDataPtr->ipcFd < 0) {int fail_on_intr = reqDataPtr->flags & FCGI_FAIL_ACCEPT_ON_INTR;//接收一次请求,传入socket,没有请求会在这里等待。这里面是重点,可是我看不懂。reqDataPtr->ipcFd = OS_Accept(reqDataPtr->listen_sock, fail_on_intr, webServerAddressList);if (reqDataPtr->ipcFd < 0) {return (errno > 0) ? (0 - errno) : -9999;}}reqDataPtr->isBeginProcessed = FALSE;reqDataPtr->in = NewReader(reqDataPtr, 8192, 0);FillBuffProc(reqDataPtr->in);if(!reqDataPtr->isBeginProcessed) {goto TryAgain;}{//查看请求类型。char *roleStr;switch(reqDataPtr->role) {case FCGI_RESPONDER:roleStr = "FCGI_ROLE=RESPONDER";break;case FCGI_AUTHORIZER:roleStr = "FCGI_ROLE=AUTHORIZER";break;case FCGI_FILTER:roleStr = "FCGI_ROLE=FILTER";break;default:goto TryAgain;}//创建存储参数QueryString的空间。reqDataPtr->paramsPtr = NewParams(30);//将请求类型当做key-value加入参数里。PutParam(reqDataPtr->paramsPtr, StringCopy(roleStr));}//将输入流以制定的方式读取(在哪停止,是否需要跳过请求)SetReaderType(reqDataPtr->in, FCGI_PARAMS);//将参数读取写入key=value。if(ReadParams(reqDataPtr->paramsPtr, reqDataPtr->in) >= 0) {//跳出循环,否则等下一次请求。break;}//释放这些中间产生的东西。将ipcFd置为-1.
TryAgain:FCGX_Free(reqDataPtr, 1);} /* for (;;) *///将剩下的信息赋值。完成一个请求的开始部分。SetReaderType(reqDataPtr->in, FCGI_STDIN);reqDataPtr->out = NewWriter(reqDataPtr, 8192, FCGI_STDOUT);reqDataPtr->err = NewWriter(reqDataPtr, 512, FCGI_STDERR);reqDataPtr->nWriters = 2;reqDataPtr->envp = reqDataPtr->paramsPtr->vec;return 0;
}

注意:很多不同的帖子里有的用FCGX_Accept (void),有的用FCGX_Accept (&in, &out, &error, &envp),有的用FCGX_Accept_r(FCGX_Request *reqDataPtr),感觉很混乱,需要搞清楚他们之间的关系。主要是封装的程度不同,值得一提的是,如果是调用FCGX_Accept_r(FCGX_Request *reqDataPtr),需要先FCGX_Init();,其他两个函数对此内部都已封装。

5. 在接收请求之前执行的FCGX_Finish_r (reqDataPtr)

在fcgiapp.c里

void FCGX_Finish_r(FCGX_Request *reqDataPtr)
{int close;if (reqDataPtr == NULL) {return;}close = !reqDataPtr->keepConnection;if (reqDataPtr->in) {close |= FCGX_FClose(reqDataPtr->err);close |= FCGX_FClose(reqDataPtr->out);close |= FCGX_GetError(reqDataPtr->in);}FCGX_Free(reqDataPtr, close);
}

基本流程就是这样。

6. 多线程请求的例子

官网多线程的例子,./examples/threaded.c

static int counts[THREAD_COUNT];static void *doit(void *a)
{int rc, i, thread_id = (int)a;pid_t pid = getpid();FCGX_Request request;char *server_name;FCGX_InitRequest(&request, 0, 0);for (;;){static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER;static pthread_mutex_t counts_mutex = PTHREAD_MUTEX_INITIALIZER;/* Some platforms require accept() serialization, some don't.. */pthread_mutex_lock(&accept_mutex);rc = FCGX_Accept_r(&request);pthread_mutex_unlock(&accept_mutex);if (rc < 0)break;server_name = FCGX_GetParam("SERVER_NAME", request.envp);FCGX_FPrintF(request.out,"Content-type: text/html\r\n""\r\n""<title>FastCGI Hello! (multi-threaded C, fcgiapp library)</title>""<h1>FastCGI Hello! (multi-threaded C, fcgiapp library)</h1>""Thread %d, Process %ld<p>""Request counts for %d threads running on host <i>%s</i><p><code>",thread_id, pid, THREAD_COUNT, server_name ? server_name : "?");sleep(2);pthread_mutex_lock(&counts_mutex);++counts[thread_id];for (i = 0; i < THREAD_COUNT; i++)FCGX_FPrintF(request.out, "%5d " , counts[i]);pthread_mutex_unlock(&counts_mutex);FCGX_Finish_r(&request);}return NULL;
}int main(void)
{int i;pthread_t id[THREAD_COUNT];FCGX_Init();for (i = 1; i < THREAD_COUNT; i++)pthread_create(&id[i], NULL, doit, (void*)i);doit(0);return 0;
}

这种多线程模式是main函数起多个线程,每个线程都独立接受请求(需要加锁)。

还有一种是main函数起一个accpet线程接受请求,多个do_session线程处理请求,这种模式需要一个任务队列的支持。

#include <fcgi_stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string>void* do_accept(void *arg);
void* do_session(void *arg);int main()
{pthread_t pthread_id;//接收请求的单独线程pthread_create(&pthread_id, NULL, do_accept, NULL);int iThreadNum = 10;for (int index = 0; index != iThreadNum; ++ index){pthread_t pthread_id;//多个处理请求的线程pthread_create(&pthread_id, NULL, do_session, NULL);}pthread_join();return 0;
}void* do_accept(void *arg)
{FCGX_Request *request = NULL;while (1){int rc = FCGX_Accept_r(request);if (rc < 0){continue;}httpRequest.put(request); //httpRequest 是一个生产者消费者模型,此处是放入任务}
}void* do_session(void *arg)
{while(1)    {FCGX_Request *request = NULL;while (!request){request = httpRequest.get(); //此处是取出任务,应需要线程安全,此次从简}string strRequestMethod;string strGetData;string strPostData;int iPostDataLength;if (FCGX_GetParam("QUERY_STRING", request->envp)){strGetData = FCGX_GetParam("QUERY_STRING", request->envp);}if (FCGX_GetParam("REQUEST_METHOD", request->envp)){iPostDataLength = ::atoi(FCGX_GetParam("CONTENT_LENGTH", _pRequest->envp));char* data = (char*)malloc(iPostDataLength + 1);::memset(data, 0, iPostDataLength + 1);FCGX_GetStr(data, iPostDataLength, _pRequest->in);strPostData = data;free(data);}FCGX_PutS("Content-type: text/html\r\n\r\n", _pRequest->out);FCGX_PutS(strPostData.c_str(), _pRequest->out);}
}

http://chatgpt.dhexx.cn/article/tvWyb3i1.shtml

相关文章

【fcgi-2.4.0】移植fcgi-2.4.0到armv7平台

源码下载链接 Index of /lookaside/extras/fcgi/fcgi-2.4.0.tar.gz/d15060a813b91383a9f3c66faf84867e (fedoraproject.org)https://src.fedoraproject.org/lookaside/extras/fcgi/fcgi-2.4.0.tar.gz/d15060a813b91383a9f3c66faf84867e/下载fcgi-2.4.0.tar.gz 解压源码 tar …

程序异常捕获库 - CrashRpt

CrashRpt.dll用来在应用程序出现异常crash时&#xff0c;捕获到错误&#xff0c;并收集出错信息&#xff1a;MiniDump文件、硬件信息、系统信息、出错信息、进程信息、服务信息、驱动信息、启动信息、软件列表、端口信息、磁盘分区、WinSock LSP、IE插件、网卡信息。 1、使用方…

CrashRpt.dll用来在应用程序出现异常crash

欢迎加入我们的QQ群&#xff0c;无论你是否工作&#xff0c;学生&#xff0c;只要有c / vc / c 编程经验&#xff0c;就来吧&#xff01;158427611 欢迎加入我们的QQ群&#xff0c;无论你是否工作&#xff0c;学生&#xff0c;只要有c / vc / c 编程经验&#xff0c;就来吧&a…

crashRpt使用方法

从官网上下载crashRpt的源码&#xff0c;按说明编译出相应的lib和dll 1 在编译crashRpt的时候&#xff0c;在环境变量中设置 crashrptDirD:\work\AIW\WebMicaps\WebMicapsServer\src\CrashRpt&#xff0c;以简化编译 2 在主程序运行的工作目录下要拷贝crashRpt编译后生成…

CrashRpt使用

CrashRpt 中除了错误处理之外值得学习的地方还是不少的&#xff0c;如屏幕截图、邮件 发送。 这里主要提取屏幕截图的功能。 1. 从 CrashRpt 源码目录中分别复制 minizip 、 zlib 、 libpng 到一个目录作为公 共库使用&#xff0c;并分别编译它们生成 lib &#xff0c;需要设置…

crashRpt用法

从官网上下载crashRpt的源代码&#xff0c;按说明编译出对应的lib和dll 1 在编译crashRpt的时候&#xff0c;在环境变量中设置 crashrptDirD:\work\AIW\WebMicaps\WebMicapsServer\src\CrashRpt&#xff0c;以简化编译 2 在主程序执行的工作文件夹下要拷贝crashRpt编译后生…

捕获Windows C/C++程序异常奔溃工具CrashRpt

背景 在Windows环境下做C/C程序开发有段时间了&#xff0c;经常遇到程序奔溃时缺无法捕获&#xff0c;经同事推荐&#xff0c;知道了CrashRpt这个捕获C/C程序异常的工具&#xff0c;让我省去了通过增加打印调试去查询程序异常的繁琐方式。下面记录下使用这个工具的一些方法。 …

关于CrashRpt的研究

CrashRpt是轻量级的开源错误报告库 官网下载链接 ##编译工程 从官网下载下最新版&#xff08;v.1.4.3_r1645&#xff09;&#xff0c;解压后发现是visual studio的工程&#xff0c;使用的是vs2010。但我用得是vs2017&#xff0c;所以在编译之前要做一下处理&#xff1a; 对于…

C++接入CrashRpt并上报分析崩溃信息

项目需要监测在Windows平台的崩溃情况&#xff0c;折腾了两天终于弄好了&#xff0c;记录一下。 1.捕获崩溃信息 接到这个需求&#xff0c;心想应用崩溃系统会收到信号&#xff0c;应该有相应的函数可以监听&#xff0c;上网搜索&#xff0c;果不其然函数 SetUnhandledExcep…

计算机中丢失crashrpt,修复crashrpt.dll

crashrpt.dll是电脑系统必备的一个DLL组件修复程序。系统重要文件 crashrpt.dll 。crashrpt.dll(含64位)在电脑中起着重要作用。比如电脑中的部分软件的正常运行就离不开它&#xff0c;如果缺少这款dll&#xff0c;会给用户带来不少麻烦。一旦发现自己的电脑这款crashrpt.dll出…

计算机中丢失crashrpt,crashrpt.dll

crashrpt.dll是smartFTP中一款重要的dll文件&#xff0c;缺少它将导致软件的部分功能无法实现或者无法启动&#xff0c;如果计算机弹出crashrpt.dll丢失或者找不到crashrpt.dll的问题&#xff0c;下载一个crashrpt.dll文件修复一下即可解决问题&#xff0c;需要的朋友可以下载&…

crashrpt

今天原本打算在谷歌上搜索处理SEH的文章&#xff0c;以使我不需要在每一个线程中使用__try{}__except()代码块包裹代码的情况下&#xff0c;就能在任意线程抛出SEH时生成MiniDump文件。不过最后的结果是处理SEH的文章没有搜索出几篇&#xff0c;却幸运的搜索出了满足我需要的工…

Windows平台崩溃转储系统crashrpt的使用

概述 CrashRpt 是一个免费的、轻量级的开源错误报告库开源库&#xff0c;旨在拦截C程序中的异常&#xff0c;收集有关崩溃的技术信息并通过互联网向软件供应商发送错误报告&#xff0c;用于在 Microsoft Visual Studio IDE 中创建并在 Windows 中运行C应用程序。&#xff08;不…

Android APK及签名

APK是AndroidPackage的缩写&#xff0c;即Android安装包(apk)。APK是类似Symbian Sis或Sisx的 文件格式。通过将APK文件直接传到 Android模拟器或Android手机中执行即可安装。apk文件和sis一样&#xff0c;把 android sdk编译的工程打包成一个安装程序文件&#xff0c;格式为ap…

安卓应用签名

安卓应用release模式是需要签名的&#xff0c;否则无法安装&#xff0c;debug模式不需要的 签名可以用keytool生成密钥文件&#xff0c;然后在build.gradle中指定即可。 一、生成密钥文件 keytool -genkey -alias noalias -keypass abcd1234 -keyalg RSA -keysize 2048 -valid…

Android为App签名(为apk签名)

写博客是一种快乐&#xff0c;前提是你有所写&#xff0c;与人分享&#xff0c;是另一种快乐&#xff0c;前提是你有舞台展示&#xff0c;CSDN就是这样的舞台。 这篇文章是android开发人员的必备知识&#xff0c;是我特别为大家整理和总结的&#xff0c;不求完美&#xff0c;但…

AndroidStudio应用签名

1、新建存放签名文件的文件夹 Build 2、初次没有账号 点击Create new 创建 3、签名成功后会在build。gradle 生成下面的数据 4、AS中调出黑窗口写入 keytool -list -v -keystore “L:/boxin/bx.jks” 签名地址 输入密码 5、找到MD5 SHA1 后面的密钥就是你的应用签名

android app签名详解

本文及文中图片转自&#xff1a;https://mp.weixin.qq.com/s?__bizMzIwMzYwMTk1NA&mid2247493825&idx1&sne926da39c6bd51397851d7e330d6ba24&chksm96ce498ca1b9c09a7865264b26eadcf27bd012e9acc999b3eb3ca5c440266642763b6be27416&mpshare1&scene23&a…

Android应用签名之AS签名

废话不多说直接上图 如之前未生成jks文件则&#xff0c;点击create new .. 这里只要输入几个必要项 Key store path&#xff08;生产key文件的保存路径 &#xff09; Key store password&#xff08;key 存储密码&#xff09; Key alias&#xff08;key别名&#xff09; Key p…

安卓应用程序的签名

签名安卓应用程序 Android应用以它的包名作为唯一标识。如果在同一部手机上安装两个包名相同的应用&#xff0c;后面安装的应用就会覆盖前面安装的应用。为了避免这种情况的发生&#xff0c;Android要求对作为产品发布的应用进行签名。 签名主要有如下两个作用&#xff1a; 1.确…