当前位置: 首页 > article >正文

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 的主要取值有以下:

  1. CURLOPT_URL

设置访问URL

  1. 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指针的来源。

  1. 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代表错误发生。
主要错误码说明:

  1. 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 = size
nmemb;
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平台


http://www.kler.cn/news/8042.html

相关文章:

  • ToBeWritten之杂项
  • uds的0x14服务介绍
  • ToBeWritten之物联网 BlueTooth/BLE 协议
  • 中金支付经历了4个月完成主要出资人前置审批
  • 关于Dataset和DataLoader的概念
  • Charles 安装及配置,详细步骤(不错,保存一下)
  • Hibernate的一级缓存是什么?具有哪些特点?
  • Linux系统编程(三)—— 文件编程(1)目录和文件
  • Python 常函数
  • 00后也太卷了吧!进厂起薪18K,原来面试时候都说了这些......
  • 操作技巧 | Revit中如何新建系统类型并赋予颜色?
  • PTA L1-003 个位数统计(15分)C语言
  • 每日做题总结——day01
  • 分布式锁介绍及解决方案
  • 【Java实战篇】Day6.在线教育网课平台
  • chrome中debugger调试定位不准确
  • CASE WHEN函数语句多条件下使用详解
  • 射频识别(RFID)技术的基本原理、特性、发展和应用
  • 请问网络安全员,渗透师,和黑客三者是什么关系?
  • 深入了解jvm垃圾回收