Android上的libcurl
Android上的libcurl
curl是一个利用URL语法在命令行方式下工作的文件传输工具。
它支持的协议有:FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE 以及 LDAP。
curl同样支持HTTPS认证,HTTP POST方法, HTTP PUT方法, FTP上传, kerberos认证, HTTP上传, 代理服务器, cookies, 用户名/密码认证, 下载文件断点续传, 上载文件断点续传, http代理服务器管道( proxy tunneling), 甚至它还支持IPv6, socks5代理服务器, 通过http代理服务器上传文件到FTP服务器等等,功能十分强大。
4.1 安装与配置
获取代码
git clone https://github.com/curl/curl.git
编译
cd 到curl目录
./buildconf
./configure
make
sudo make install
4.2 curl使用
curl的用法为:
curl [options] [URL…]
,其中options是下载需要的参数,大约有80多个,curl的各个功能完全是依靠这些参数完成的。这里只介绍几种简单的用法,详细的curl的参数在http://curl.haxx.se/docs/说明。
1、读取网页
curl http://www.baidu.com
2、保存网页、下载文件
以page.html命名下载网页:
curl –o page.html http://www.baidu.com
下载某个文件:
curl –O http://101.200.158.37:8980/group1/M00/00/00/ZcieJVfknTiAOScnAAD_2E_cxs4484.png
3、使用cookie来记录session信息
cookie 信息存到cookie1.txt中:
curl –o page.html –D cookie1.txt http://www.linuxidc.com
使用上次的cookie并生成新的cookie:
curl –o page.html –D cookie2.txt -b cookie2.txt
http://www.linuxidc.com
4、断点续传
比如下载一个文件中,突然掉线了,可以这样开始续传:
curl -c -O http://101.200.158.37:8980/group1/M00/00/00/ZcieJVfkjueAe-ctADs41Y5PrD4740.mp3 -o 11.mp3
另外可以用-r选项进行分块下载
5、上传文件
比如我们向ftp传一个文件:
curl -T localfile -u name:passwd ftp://upload_site:port/path/
PS:对于ftp服务器用-u name:passwd选项
7、http提交一个表单GET与POST模式
GET模式什么option都不用,只需要把变量写在url里面就可以了比如:
$curl http://www.linuxidc.com/login.cgi?user=nickwolfe&password=12345
POST模式的选项是 -d
curl -d “user=nickwolfe&password=12345” http://www.linuxidc.com/login.cgi
4.3 libcurl API
libcurl的官方文档在: https://curl.haxx.se/
1、curl编程流程
LibCurl编程流程在基于LibCurl的程序里,主要采用callback function (回调函数)的形式完成传输任务,用户在启动传输前设置好各类参数和回调函数,当满足条件时libcurl将调用用户的回调函数实现特定功能。
下面是利用libcurl完成传输任务流程:
(1)
curl_global_init(); //初始化libcurl
(2)
curl_easy_init(); //函数得到 easy interface型指针
(3)
curl_easy_setopt(); //设置传输选项
(4)
curl_easy_setopt();//设置的传输选项,实现回调函数以完成用户特定任务
(5)
curl_easy_perform();//完成传输任务
(6)
curl_easy_cleanup();//释放内存
在整过过程中设置curl_easy_setopt()参数是最关键的,几乎所有的libcurl程序都要使用它。
2、重要API
(1)初始化全局libcurl
CURLcode curl_global_init(long flags);
描述:
这个函数只能用一次。(其实在调用curl_global_cleanup 函数后仍然可再用)
如果这个函数在curl_easy_init函数调用时还没调用,它讲由libcurl库自动完成。
参数:flags
CURL_GLOBAL_ALL //初始化所有的可能的调用。
CURL_GLOBAL_SSL //初始化支持 安全套接字层。
CURL_GLOBAL_WIN32 //初始化win32套接字库。
CURL_GLOBAL_NOTHING //没有额外的初始化。
(2)释放全局libcurl
void curl_global_cleanup(void);
描述:
在结束libcurl使用的时候,用来对curl_global_init做的工作清理。类似于close的函数。
(3)打印版本号
char *curl_version(void);
描述:
打印当前libcurl库的版本。
(4)得到CURL的指针
CURL *curl_easy_init(void);
描述:
curl_easy_init用来初始化一个CURL的指针(有些像返回FILE类型的指针一样). 相应的在调用结束时要用curl_easy_cleanup函数清理.
一般curl_easy_init意味着一个会话的开始. 它的返回值一般都用在easy系列的函数中.
(5)释放CURL的指针
void curl_easy_cleanup(CURL *handle);
描述:
这个调用用来结束一个会话.与curl_easy_init配合着用.
参数:
CURL类型的指针.
(6)操作curl(重要)
CURLcode curl_easy_setopt(CURL *handle, CURLoption option, parameter);
描述:
这个函数最重要了.
几乎所有的curl 程序都要频繁的使用它.
它告诉curl库.程序将有如何的行为.
比如要查看一个网页的html代码等.
(这个函数有些像ioctl函数)
参数:
CURL *handle // CURL类型的指针
CURLoption option // 各种CURLoption类型的选项.
//(都在curl.h库里有定义,man 也可以查看到)
parameter //这个参数 既可以是个函数的指针,也可以是某个对象的指针,
//也可以是个long型的变量.它用什么这取决于第二个参数.
详细说明:
本节主要介绍curl_easy_setopt中跟http相关的参数。注意本节的阐述都是以libcurl作为主体,其它为客体来阐述的。
option 的主要取值有以下:
- CURLOPT_URL
设置访问URL
- CURLOPT_WRITEFUNCTION,CURLOPT_WRITEDATA
回调函数原型为:
size_t function( void *ptr, size_t size, size_t nmemb, void *stream);
函数将在libcurl接收到数据后被调用,因此函数多做数据保存的功能,如处理下载文件。
CURLOPT_WRITEDATA 用于表明CURLOPT_WRITEFUNCTION函数中的stream指针的来源。
3.CURLOPT_HEADERFUNCTION,URLOPT_HEADERDATA
回调函数原型为
size_t function( void *ptr, size_t size,size_t nmemb, void *stream);
libcurl一旦接收到http 头部数据后将调用该函数。 CURLOPT_WRITEDATA 传递指针给libcurl,该指针表明CURLOPT_HEADERFUNCTION 函数的stream指针的来源。
- CURLOPT_READFUNCTION CURLOPT_READDATA
libCurl需要读取数据传递给远程主机时将调用CURLOPT_READFUNCTION指定的函数,
函数原型是:
size_t function(void *ptr, size_t size, size_t nmemb,void *stream);
CURLOPT_READDATA 表明CURLOPT_READFUNCTION函数原型中的stream指针来源。
5.CURLOPT_NOPROGRESS, CURLOPT_PROGRESSFUNCTION, CURLOPT_PROGRESSDATA
跟数据传输进度相关的参数。
CURLOPT_PROGRESSFUNCTION 指定的函数正常情况下每秒被libcurl调用一次。 为了使CURLOPT_PROGRESSFUNCTION被调用,CURLOPT_NOPROGRESS必须被设置为false。 CURLOPT_PROGRESSDATA指定的参数将作为CURLOPT_PROGRESSFUNCTION指定函数的第一个参数
6.CURLOPT_TIMEOUT,CURLOPT_CONNECTIONTIMEOUT CURLOPT_TIMEOUT 由于设置传输时间,CURLOPT_CONNECTIONTIMEOUT 设置连接等待时间
(7) 提交请求
CURLcode curl_easy_perform(CURL *handle);
描述:
这个函数在初始化CURL类型的指针 以及curl_easy_setopt完成后调用.
就像字面的意思所说perform就像是个舞台.
让我们设置的
option 运作起来.
参数:
CURL类型的指针.
返回值:
返回0意味一切ok,非0代表错误发生。
主要错误码说明:
- CURLE_OK
任务完成一切都好
2 CURLE_UNSUPPORTED_PROTOCOL
不支持的协议,由URL的头部指定
3 CURLE_COULDNT_CONNECT
不能连接到remote 主机或者代理
4 CURLE_REMOTE_ACCESS_DENIED
访问被拒绝
5 CURLE_HTTP_RETURNED_ERROR
Http返回错误
6 CURLE_READ_ERROR
读本地文件错误
4.4 libcurl 案例
(1) 获取html网页
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
int main(int argc, char *argv[])
{
CURL *curl; //定义CURL指针
CURLcode res; //定义CURLcode类型的变量,保存返回状态码
if (argc != 2) {
printf("Usage: ./a.out <url>\n");
exit(1);
}
curl = curl_easy_init();
if (curl != NULL) {
//设置curl选项. 其中CURLOPT_URL是让用户指定url. argv[1]中存放的命令行传进来的网址
curl_easy_setopt(curl, CURLOPT_URL, argv[1]);
//调用curl_easy_perform 执行我们的设置.并进行相关的操作. 在这里只在屏幕上显示出来.
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
printf("curl easy perform error\n");
}
//清除curl操作.
curl_easy_cleanup(curl);
}
return 0;
}
gcc -Wall test1_downhtml.c -o test1 -lcurl
./test1 www.baidu.com
(2)、网页下载保存
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <curl/curl.h>
size_t write_data( void *ptr, size_t size, size_t nmemb, void *stream)
{
FILE fp = (FILE)stream;
int writen = fwrite(ptr, size, nmemb, fp);
return writen;
}
int main(int argc, char *argv[])
{
CURL *curl = NULL;
FILE *fp = NULL;
if (argc != 3) {
printf("Usage: ./a.out <url> <file>\n");
exit(1);
}
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, argv[1]);
if ((fp = fopen(argv[2], "w")) == NULL) {
printf("fopen error\n");
goto END;
}
//CURLOPT_WRITEFUNCTION 将后继的动作交给write_data函数处理
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
curl_easy_perform(curl);
END:
curl_easy_cleanup(curl);
curl_global_cleanup();
return 0;
}
gcc -Wall test2_download_html.c -o test2 -lcurl
./test2 www.baidu.com baidu.html
(3)、GET请求
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
int main(int argc, char *argv[])
{
CURL *curl = NULL;
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, "127.0.0.1:8080/?username=ldb&passwd=123456");
//将返回的http头 输出到标准输出上
curl_easy_setopt(curl, CURLOPT_HEADERDATA, stdout);
//将返回的get请求数据,输出到标准输出上
curl_easy_setopt(curl, CURLOPT_WRITEDATA, stdout);
curl_easy_perform(curl);
curl_easy_cleanup(curl);
return 0;
}
(4)、POST请求
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#define POSTURL “http://127.0.0.1:8080/login”
#define POSTFIELDS “{“username”:“liudanbing”, “passwd”:“123456”, “type”:1}”
#define FILENAME “curlpost.log”
size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp)
{
FILE fptr = (FILE)userp;
int ret = 0;
ret = fwrite(buffer, size, nmemb, fptr);
return ret;
}
int main(int argc, char *argv[])
{
CURL *curl;
CURLcode res;
FILE *fptr;
if ((fptr = fopen(FILENAME, "w")) == NULL) {
fprintf(stderr, "fopen file error: %s\n", FILENAME);
exit(1);
}
curl = curl_easy_init();
//URL地址
curl_easy_setopt(curl, CURLOPT_URL, POSTURL);
//Post 数据
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, POSTFIELDS);
//对返回的数据进行操作的函数地址
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
//设置WRITEFUNCTION的第四个参数值
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fptr);
//设置为非0表示本次操作为POST
curl_easy_setopt(curl, CURLOPT_POST, 1);
// 设置为非0在执行时打印请求信息
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
//设置为非0将响应头信息同响应体一起传给WRITEFUNCTION
curl_easy_setopt(curl, CURLOPT_HEADER, 1);
//设置为非0,响应头信息Location
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
//设置对应的COOKIEFILE路径
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "./libcurl.cookie");
res = curl_easy_perform(curl);
if (res != CURLE_OK ) {
fprintf(stderr, "post perform error\n");
exit(1);
}
curl_easy_cleanup(curl);
return 0;
}
4.5 Android上使用libcurl
下载和编译
libcurl想要在Android上开发,显然不能只能用C++的so文件。
所以我们需要将curl的源代码通过Android.mk重新编译一次,但是显然很是麻烦。
这里我用就用别人写好的东西。
https://github.com/gcesarmza/curl-android-ios
这个是一个已经写好的移动端使用libcurl的源代码。
下载curl_android_ios
git clone https://github.com/gcesarmza/curl-android-ios.git
由于这个库依赖于 curl库和 openssl,所以需要再次关联下载
git submodule init && git submodule update
编译android版本
/curl-android-ios-master/curl-compile-scripts/
./bulid_android.sh
(1) 问题1
如果提示需要NDK_ROOT环境变量,需要在~/.bashrc加入NDK安装的路径.
vim ~/.bashrc
export NDK_ROOT=/home/itcast/BC-Project/Android-SDK/ndk-bundle
source ~/.bashrc
(2) 问题2
./build_Android.sh: 行 34: ./Configure: 没有那个文件或目录
Error running the ssl configure program
可能是没有下载依赖库openssl 和curl导致。
这个编译可能会话费很长时间,因为他要编译很多种平台的Android。如x86,mips,arm等等。
最后,我们会得到以下静态库文件。
[arm64-v8a] StaticLibrary : libcurl.a
[x86_64] StaticLibrary : libcurl.a
[mips64] StaticLibrary : libcurl.a
[armeabi] StaticLibrary : libcurl.a
[armeabi-v7a] StaticLibrary : libcurl.a
[x86] StaticLibrary : libcurl.a
[mips] StaticLibrary : libcurl.a
对应我们Android的设备的平台型号,选择一款就好了,一般我们都是用的[armeabi]
配置和部署
将生成的libcurl.a 和所对应的头文件拷贝到Android项目中的jni/路径下。
cd ./curl-android-ios-master/prebuilt-with-ssl/android/
cp armeabi/libcurl.a ~/AndroidStudioProjects/testApp/jni/
cp include/curl/ ~/AndroidStudioProjects/testApp/jni/ -R
以上如果您使用的开发环境是Linux。
如果是Windows,需要将armeabi下的libcurl.a通过远程传输,拷贝到当前windows项目中的jni路径下.
测试libcurl
目前我们jni/路径下已经有 libcurl.a 和curl/头文件了。
现在我们可以实现一个login的jni接口,让java调用,来完成客户端和服务端的网络通信。
(1)首先在java类中,定义一个native接口。
//登陆服务器login接口
public native void testLibcurl();
(2) 给该native通过javah生成头文件.
cd testApp/app/src/main/java/
javah -jni com.cpp.itcast.testapp.HelloJni
(3)测试libcurl代码
在jni.cpp中 实现jni接口。 就是访问一个网页,然后将网页的信息打印到debug上。
size_t print_html( void *ptr, size_t size, size_t nmemb, void stream)
{
int len = sizenmemb;
char *buf = new char[len+1];
memcpy(buf, ptr, len);
buf[len] = '\0';
__android_log_print(ANDROID_LOG_ERROR, "jnitag", "%s\n", buf);
delete [] buf;
return len;
}
JNIEXPORT void JNICALL Java_com_cpp_itcast_testapp_HelloJni_testLibcurl
(JNIEnv *, jobject)
{
CURL *curl = NULL;
CURLcode ret;
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, "www.baidu.com");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, print_html);
ret = curl_easy_perform(curl);
if (ret != CURLE_OK) {
__android_log_print(ANDROID_LOG_ERROR, "jnitag", "curl_easy_perform error");
return ;
}
curl_easy_cleanup(curl);
return ;
}
(4)修改Android.mk文件
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
libcurl.a
LOCAL_MODULE := libcurl
LOCAL_SRC_FILES := libcurl.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
#libmyJni.so
LOCAL_MODULE := myJni
LOCAL_SRC_FILES := test.cpp jni.cpp
LOCAL_LDLIBS := -llog -lz
LOCAL_STATIC_LIBRARIES := libcurl
include $(BUILD_SHARED_LIBRARY)
(5) 编译生成libmyJni.so
进入到jni/路径 执行。
ndk-build APP_ABI=“armeabi”
(6)测试 该Jni接口
此过程需要两个必要条件:
1.Android移动设备必须可以上网。
2.给该APP应用开辟上网权限
开辟上网权限如下:
打开testApp/app/src/main/AndroidManifest.xml:
在 标签里面加入如下标签:
<uses-permission android:name="android.permission.INTERNET"/>
3.在app源根文件夹下build.gradle文件的配置
这里面加注释的是额外需要关注的地方。
compileSdkVersion 24
buildToolsVersion "24.0.3"
defaultConfig {
applicationId "com.example.ace.obo"
minSdkVersion 10
targetSdkVersion 24
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// 不声明ndk标签,项目默认会创建一个libapp.so的文件
ndk {
// 声明创建so库的文件名,会自动添加lib前缀, 添加了前缀,不会自动添加
moduleName "OBOjni"
//声明启用Android日志, 在c/c++的源文件中使用的#include <android/log.h> 日志将得到输出
//这里我们关联了两个库 一个是liblog 和 libz
ldLibs "log","z"
// 声明创建指定cpu架构的so库, 不声明的话, 默认(gradle 1.5.0)会生成7中架构,如果你的libcurl没有提供别的平台,那么就会链接失败,
//所以此条配置很重要,这里我们只生成一个平台
abiFilters "armeabi"
}
}
4.另外最好加上如下配置
项目根文件夹下的gradle.properties文件中添加如下配置(解决AS中NDK插件过时不能编译的问题)
android.useDeprecatedNdk=true
5.最后执行测试代码,在真机上运行,观察结果.
HelloJni.getInstance().testLibcurl();
注意事项:
如果在生成连接到时候 出现 error: linker command failed with exit code 1 (use -v to see invocation)错误
需要过滤生成的ABI平台