libcurl库的资料存档

rel="File-List" href="file:///C:%5CDOCUME%7E1%5CWANGQIS%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml"> rel="Edit-Time-Data" href="file:///C:%5CDOCUME%7E1%5CWANGQIS%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_editdata.mso">

libcurl

A.在windows环境下编译libcurl

Download Microsoft Platform SDK
========================================================
    http://www.microsoft.com/downloads/details.aspx?FamilyId=A55B6B43-E24F-4EA3-A93E-40C0EC4F68E5&displaylang=en
    Install it


Download Zlib Source
========================================================
    from http://www.zlib.net/
    http://www.zlib.net/zlib123.zip


Build Zlib
========================================================
    Use Visual C++ 6
    zlib123/projects/visualc6/zlib.dsw


Download Openssl Source
========================================================
    from http://www.openssl.org/source/
    http://www.openssl.org/source/openssl-0.9.8g.tar.gz


Build Openssl
========================================================
    maybe need to install active perl
    http://lccnc.skycn.com/down/ActivePerl-5.8.8.822-MSWin32-x86-280952.zip

    # run build shell
    cd openssl-***
    ms/32all.bat


Download Curl Source
========================================================
    from http://curl.haxx.se/
    http://curl.haxx.se/download/curl-7.18.1.zip


Build Curl
========================================================
    1. Pre Build
   
    a. create bat file for debug
    call "C:/Program Files/Microsoft Visual Studio/VC98/Bin/vcvars32.bat"
    set CFG=debug-dll-ssl-dll-zlib-dll
    set OPENSSL_PATH=D:/svn_work/MDS/USS/trunk/openssl- 0.9.8 g
    set ZLIB_PATH=D:/svn_work/MDS/USS/trunk/zlib123-dll
    nmake -f Makefile.vc6

    b. create bat file for release
    call "C:/Program Files/Microsoft Visual Studio/VC98/Bin/vcvars32.bat"
    set CFG=release-dll-ssl-dll-zlib-dll
    set OPENSSL_PATH=D:/svn_work/MDS/USS/trunk/openssl- 0.9.8 g
    set ZLIB_PATH=D:/svn_work/MDS/USS/trunk/zlib123
    nmake -f Makefile.vc6

    c. save shell code to bat file
    Save to : curl-***/lib/build.bat

    d. modify curl-***/lib/Makefile.vc6
    Find
        CFLAGS     = / I. /I../include /nologo /W3 /GX /DWIN32 /YX /FD /c /DBUILDING_LIBCURL
    set as
        CFLAGS     = / I. /I../include /nologo /W3 /GX /DWIN32 /YX /FD /c /DBUILDING_LIBCURL  /I"C:/Program Files/Microsoft Platform SDK/Include"

    e. copy zlib123/projects/visualc6/Win32_DLL_Release/zlib1.lib to curl- 7.18.1 /lib/zdll.lib


    2. Build
    cd curl-***/lib
    build.bat
   

Windows下使用libcurlcurlpp

1. 下载libcurlcurlpp源代码

http://curl.haxx.se/

http://rrette.com/textpattern/index.php?s=cURLpp

例如下载

http://curl.haxx.se/download/curl-7.16.2.tar.bz2

http://rrette.com/files/curlpp/curlpp-0.7/curlpp-0.7.0.tar.gz

 

2. 下载zlib

http://www.zlib.net/

例如下载

http://www.zlib.net/zlib123-dll.zip

 

3. 编译OpenSSL

参考WindowsOpenssl安装以及编程(Visual C++)

 

4. 编译libcurl,以VS2005为例

curl- 7.16.2 .tar.bz2解压到某目录,例如C:/curl,进入C:/curl/lib目录。

zlib123-dll.zip解压到某目录,例如C:/zlib

Openssl的目录为C:/openssl

 

进入Visual Studio 2005命令提示,进入C:/curl/lib

 

编译Debug版本。

set CFG=debug-dll-ssl-dll-zlib-dll

set OPENSSL_PATH=C:/openssl

set ZLIB_PATH=C:/zlib/include

nmake -f Makefile.vc8

 

其输出:libcurld_imp.lib, libcurld.dll

 

编译Release版本。

set CFG=release-dll-ssl-dll-zlib-dll

set OPENSSL_PATH=C:/openssl

set ZLIB_PATH=C:/zlib/include

nmake -f Makefile.vc8

其输出:libcurl_imp.lib, libcurl.dll

如果需要编译其他版本,可查看设定相应的CFG 参数即可。

 

 

5.编译curlpp

curlpp- 0.7.0 .tar.gz解压到某目录,例如C:/curlpp/curlpp

set CFG=release

nmake -f Makefile.msvc

如果需要编译其他版本,可查看设定相应的CFG 参数即可。

 

需要注意的是可能需要对原文件进行一定的修改。

一种方案是:

修改Makefile.msvcLIBCURL_PATH

修改dllfct.h关于CURLPPAPI的宏定义

#define CURLPPAPI 

 

6.测试

该程序是备份CUBlog程序的C++版,需要boost

 

#include <iostream>
#include <string>
#include <queue>

#include <boost/lexical_cast.hpp>
#include <boost/regex.hpp>
#include <boost/tuple/tuple.hpp>

#include <curlpp/cURLpp.hpp>
#include <curlpp/Easy.hpp>
#include <curlpp/Options.hpp>
#include <curlpp/Exception.hpp>

#pragma comment(lib,"libcurlpp.lib")
#pragma comment(lib,"libcurl_imp.lib")

using namespace std;

#define MAX_FILE_LENGTH 20000

class WriterMemoryClass
{
    
public:
    
// Helper Class for reading result from remote host
    WriterMemoryClass(){
        
this->m_pBuffer = NULL;
        
this->m_pBuffer = (char*) malloc(MAX_FILE_LENGTH * sizeof(char));
        
this->m_Size = 0;
    
}
    
~WriterMemoryClass() {
        
if (this->m_pBuffer)
            
free(this->m_pBuffer);
    
}
    
void* Realloc(void* ptr, size_t size) {
        
if(ptr)
            
return realloc(ptr, size);
        
else
            
return malloc(size);
    
}
    
// Callback must be declared static, otherwise it won't link...
    size_t WriteMemoryCallback(char* ptr, size_t size, size_t nmemb) {
        
// Calculate the real size of the incoming buffer
        size_t realsize = size * nmemb;
        
// (Re)Allocate memory for the buffer
        m_pBuffer = (char*) Realloc(m_pBuffer, m_Size + realsize);
        
// Test if Buffer is initialized correctly & copy memory
        if (m_pBuffer == NULL) {
            realsize
= 0;
        
}
        
        
memcpy(&(m_pBuffer[m_Size]), ptr, realsize);
        m_Size
+= realsize;
        
// return the real size of the buffer...
        return realsize;
    
}
    
    
// Public member vars
    char* m_pBuffer;
    
size_t m_Size;
};

int main(int argc,char * argv[])
{
    
string list_base="http://blog.chinaunix.net/u/8780/article.php?frmid=0&page=";
    
string art_base="http://blog.chinaunix.net/u/8780/showart.php?id=";
    boost
::regex rexp("<a href=/"showart_([0-9]{6}).*?><font.*?><b>(.*?)</b></font></a>");
    boost
::regex fnp("[///:*?/"<>]");
    
typedef boost::tuple<string,string> element;
    
queue<element> q;
    
bool save=true;
    
if(argc==1)
        save
=false;
        
    
try {
        cURLpp
::Cleanup cleaner;
        cURLpp
::Easy request;
        cURLpp
::Easy handler;
       
int i=0;
        
bool cond=true;
        
while(cond) {
            i
++;
            cond
=false;
            
string list_url=list_base+boost::lexical_cast<string>(i);
            
cerr<<list_url<<endl;
            
// Get the content
            WriterMemoryClass mWriterChunk;
            
// Set the writer callback t

 

 

HTTP/FTP客户端开发库:libwww、libcurl、libfetch 以及更多

网页抓取和ftp访问是目前很常见的一个应用需要,无论是搜索引擎的爬虫,分析程序,资源获取程序,WebService等等都是需要的,自己开发抓取库 当然是最好了,不过开发需要时间和周期,使用现有的Open source程序是个更好的选择,一来别人已经写的很好了,就近考验,二来自己使用起来非常快速,三来自己还能够学习一下别人程序的优点。

闲来无事,在网上浏览,就发现了这些好东西,特别抄来分享分享。主要就是libwwwlibcurllibfetch 这三个库,当然,还有一些其他很多更优秀库,文章后面会有简单的介绍。

 

libwww
官方网站:http://www.w3.org/Library/
更多信息:http://www.w3.org/Library/User/
运行平台:Unix/LinuxWindows

以下资料来源:http://9.douban.com/site/entry/15448100/http://zh.wikipedia.org/wiki/Libwww

简介:
Libwww
是一个高度模组化用户端的网页存取API ,用C语言写成,可在 Unix Windows 上运行。 It can be used for both large and small applications including: browsers/editors, robots and batch tools. There are pluggable modules provided with Libwww which include complete HTTP/1.1 with caching, pipelining, POST, Digest Authentication, deflate, etc. The purpose of libwww is to serve as a testbed for protocol experiments. 蒂姆·伯纳斯-李 在 1992 年十一月创造出了 Libwww,用於展示网际网路的潜能。使用 Libwww 的应用程式,如被广泛使用的命令列文字浏览器 Lynx Mosaic web browser 即是用 Libwww 所写成的。 Libwww 目前为一开放原始码程式,并於日前移至 W 3C 管理。基於其为开放原始码的特性,任何人都能为 Libwww 付出一点心力,这也确保了 Libwww 能一直进步,成为更有用的软体。

操作示例:
最近我需要写点页面分析的东西,这些东西某些程度上类似搜索引擎的爬虫->parser->存储的过程。

过去我常用的抓取页面的库是libcurl,这个东西是unix常用命令curl的基础,curl被称做命令行浏览器,功能强大,支持的协议也全面。遗憾的是libcurl仅仅是个支持多协议的抓取库,不能做解析。

找来找去,发现了w 3c Libwww库,这东西功能强大的吓人,不仅有解析,还有robot(也就是爬虫了,或是叫internet walker)功能。在Libwww基础上完成的程序很多,最著名的大概是字符模式的浏览器lynx。我几乎就觉得这就我需要的东西了,立刻dive into

一整天之后,我终于能用这东西抓下来页面,并且从html页面中分析出来一些信息了,但是想更进一步就变的异常困难。因为这个库功能太复杂了。这东 西文档不详细,被人提及的也少。Libwww最近的Release 5.3.2 ,发布于 20001220日 。一个有这么多年历史的东西,竟然没多少开发者在讨论,非常不正常。

找来找去,最后在libcurlFAQ里面看到了和Libwww比较, 精选的读者来信告诉我,不仅仅是我一个人被Libwww的复杂弄的晕了头脑,我才花了一整天,写信的那个哥们竟然用了一人月,还是在里面打转,直到换了 curl才好。虽然这是libcurl推销自己的方法,不过这些失败的前辈的经验让我对自己的智商重新有了信心。看来这东西没多少人讨论是正常的...

好吧,我也投降,libcurlhtml解析功能,这没关系,我找别的办法好了...这么复杂的库,再好我也实在没办法忍受下去了,再说我需要的功能其实也真没Libwww那么复杂的。

写程序其实很容易迷失,你会看到一个似乎很完美,什么都能做的东西,一下子就喜欢上它,但是最后往往还是无福消受。往往是那些,不那么成熟,多少有点小毛病的库,组合在一起才是真正的解决方案。

 

libcurl

官方网站:http://curl.haxx.se/libcurl
更多特点:http://curl.haxx.se/docs/features.html
运行平台:Unix/LinuxWindowsWindows上貌似也有实现)

以下资料来源:http://blog.csdn.net/hwz119/archive/2007/04/29/1591920.aspx

 

Libcurl为一个免费开源的,客户端url传输库,支持FTPFTPSTFTPHTTPHTTPSGOPHERTELNETDICTFILELDAP,跨平台,支持WindowsUnixLinux等,线程安全,支持Ipv6。并且易于使用。

http://curl.haxx.se/libcurl/

 

http://curl.haxx.se/libcurl/ 下载一个稳定的版本,注意选择OS

 

编译libcurl

下载下来的是源码包,需要编译。

解压zip文件,进入curl- 7.14.0 /lib目录(我下载的是7.14.0)。

编译Debug版本。新建一个批处理bat文件,如buildDebug.bat,内容如下:

call "C:/Program Files/Microsoft Visual Studio/VC98/Bin/vcvars32.bat"

set CFG=debug-dll-ssl-dll-zlib-dll

set OPENSSL_PATH=E:/SSL/openssl- 0.9.7 e

set ZLIB_PATH=E:/zip/zlib123

nmake -f Makefile.vc6

 

其输出libcurld_imp.lib, libcurld.dll

 

编译Release版本。新建一个批处理文件BuildRelease.bat内容如下

call "C:/Program Files/Microsoft Visual Studio/VC98/Bin/vcvars32.bat"

set CFG=release-dll-ssl-dll-zlib-dll

set OPENSSL_PATH=E:/SSL/openssl- 0.9.7 e

set ZLIB_PATH=E:/zip/zlib123

nmake -f Makefile.vc6

 

其输出libcurl_imp.lib, libcurl.dll

 

上面编译的是libcurl dll使用OpenSSL Dll版本和Zlib Dll版本。如果没有,可以从www.openssl.org 或者http://www.zlib.net/ 下载。

如果需要编译其他版本,可查看Makefile.vc6,设定相应的CFG 参数即可。

 

商业软件使用libcurl时,只需要包含其copywrite声明即可。

 

Sample

 

#include <stdio.h>
#include "../curl- 7.14.0 /include/curl/curl.h"
#pragma comment(lib, "../curl-7.14.0/lib/libcurl_imp.lib")

int main(void)
{
  curl = curl_easy_init();
  
if(curl) {

    CURLcode res;    
    res = curl_easy_setopt(curl, CURLOPT_PROXY, "Test-pxy08:8080");
    res = curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
    res = curl_easy_setopt(curl, CURLOPT_URL, "http://www.vckbase.com");
    res = curl_easy_perform(curl);

    
if(CURLE_OK == res) {
      
char *ct;
      
/**//* ask for the content-type */
      
/**//* http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html */
      res = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct);

      
if((CURLE_OK == res) && ct)
        printf("We received Content-Type: %s ", ct);
    }

    
/**//* always cleanup */
    curl_easy_cleanup(curl);
  }
  
return 0;
}

 

 

libfetch
官方网站:http://libfetch.darwinports.com/ 
更多信息:http://www.freebsd.org/cgi/man.cgi?query=fetch&sektion=3
运行平台:BSD

以下资料来源:http://bbs.chinaunix.net/viewthread.php?tid=105809

前几天无双老大在FB版介绍了一下CU的巨猛的法老级灌水大师,小弟于是说要编个程序自动来灌,哈哈昨晚有所突破,找到一个很好的库,先介绍给各位大鱼小虾们,不过可别真的拿它来灌水啊,否则我被这里的班长们砍死以后的冤魂可要来算帐的喔!      
这是在FreeBSD里找到的一个库:libfetch,源代码在/usr/src/lib/libfetch里,它对httpftp协议进行了封装,提供了一些很容易使用的函数,因为昨天刚看到,还没仔细研究,我试了一个用http取网页的函数,示例如下:

#include <stio.h>
#include 
#include 

#include "fetch.h"

const char * myurl = "http://qjlemon:aaa@192.169.0.1:8080/test.html";

main()
{
        FILE * fp;
        
char buf[1024];

        fp = fetchGetURL(myurl, "";
        
if (!fp) {
                printf("error: %s ", fetchLastErrString);
                
return 1;
        }
        
while (!feof(fp)) {
                memset(buf, 0, 
sizeof(buf));
                fgets(buf, 
sizeof(buf), fp);
                
if (ferror(fp))
                        
break;
                
if (buf[0])
                        printf("%s", buf);
                
else
                        
break;
        }
        fclose(fp);
        fp = NULL;
}


这里最重要的就是fetchGetURL函数,它按指定的URL来取文件,比如URL
是以http开头的,这个函数就知道按http取文件,如果是ftp://,就会按ftp取文件,还可以指定用户名和口令。
如果文件被取到,它会返回一个FILE指针,可以象操作普通的文件一样把网页的内容取出来。
另外这个库还提供了一些函数,可以对网络操作进行更为精细的控制。
当然最有用的是还是几个PUT函数,想要灌水就得用这个哟!哈哈哈!

 

【其他相关HTTP/FTP客户端库】
资料来源:http://curl.haxx.se/libcurl/competitors.html

Free Software and Open Source projects have a long tradition of forks and duplicate efforts. We enjoy "doing it ourselves", no matter if someone else has done something very similar already.

Free/open libraries that cover parts of libcurl's features:

libcurl (MIT)

a highly portable and easy-to-use client-side URL transfer library, supporting FTP, FTPS, HTTP, HTTPS, SCP, SFTP, TELNET, DICT, FILE, TFTP and LDAP. libcurl also supports HTTPS certificates, HTTP POST, HTTP PUT, FTP uploading, kerberos, HTTP form based upload, proxies, cookies, user+password authentication, file transfer resume, http proxy tunnelling and more!

libghttp (LGPL)

Having a glance at libghttp (a gnome http library), it looks as if it works rather similar to libcurl (for http). There's no web page for this and the person who's email is mentioned in the README of the latest release I found claims he has passed the leadership of the project to "eazel". Popular choice among GNOME projects.

libwww (W3C license) comparison with libcurl

More complex, and and harder to use than libcurl is. Includes everything from multi-threading to HTML parsing. The most notable transfer-related feature that libcurl does not offer but libwww does, is caching.

libferit (GPL)

C++ library "for transferring files via http, ftp, gopher, proxy server". Based on 'snarf' 2.0.9 -code (formerly known as libsnarf). Quote from freshmeat: "As the author of snarf, I have to say this frightens me. Snarf's networking system is far from robust and complete. It's probably full of bugs, and although it works for maybe 85% of all current situations, I wouldn't base a library on it."

neon (LGPL)

An HTTP and WebDAV client library, with a C interface. I've mainly heard and seen people use this with WebDAV as their main interest.

libsoup (LGPL) comparison with libcurl

Part of glib (GNOME). Supports: HTTP 1.1, Persistent connections, Asynchronous DNS and transfers, Connection cache, Redirects, Basic, Digest, NTLM authentication, SSL with OpenSSL or Mozilla NSS, Proxy support including SSL, SOCKS support, POST data. Probably not very portable. Lacks: cookie support, NTLM for proxies, GSS, gzip encoding, trailers in chunked responses and more.

mozilla netlib (MPL)

Handles URLs, protocols, transports for the Mozilla browser.

mozilla libxpnet (MPL)

Minimal download library targeted to be much smaller than the above mentioned netlib. HTTP and FTP support.

wget (GPL)

While not a library at all, I've been told that people sometimes extract the network code from it and base their own hacks from there.

libfetch (BSD)

Does HTTP and FTP transfers (both ways), supports file: URLs, and an API for URL parsing. The utility fetch that is built on libfetch is an integral part of the FreeBSD operating system.

HTTP Fetcher (LGPL)

"a small, robust, flexible library for downloading files via HTTP using the GET method."

http-tiny (Artistic License)

"a very small C library to make http queries (GET, HEAD, PUT, DELETE, etc.) easily portable and embeddable"

XMLHTTP Object also known as IXMLHTTPRequest (part of MSXML 3.0)

(Windows) Provides client-side protocol support for communication with HTTP servers. A client computer can use the XMLHTTP object to send an arbitrary HTTP request, receive the response, and have the Microsoft® XML Document Object Model (DOM) parse that response.

QHttp (GPL)

QHttp is a class in the Qt library from Troll Tech. Seems to be restricted to plain HTTP. Supports GET, POST and proxy. Asynchronous.

ftplib (GPL)

"a set of routines that implement the FTP protocol. They allow applications to create and access remote files through function calls instead of needing to fork and exec an interactive ftp client program."

ftplibpp (GPL)

A C++ library for "easy FTP client functionality. It features resuming of up- and downloads, FXP support, SSL/TLS encryption, and logging functionality."

GNU Common C++ library

Has a URLStream class. This C++ class allow you to download a file using HTTP. See demo/urlfetch.cpp in commoncpp2- 1.3.19 .tar.gz

HTTPClient (LGPL)

Java HTTP client library.

Jakarta Commons HttpClient (Apache License)

A Java HTTP client library written by the Jakarta project.
 

WindowsOpenssl安装以及编程(Visual C++)

必备条件:

1、安装VC++ 6.0    

2、下载ActivePerl- 5.8.0 .806-MSWin32-x86并安装:

http://www.sogoz.info/down.asp?id=3683&no=1

3、下载 openssl0.98并解压。

http://www.openssl.org/source/openssl-0.9.8.tar.gz

    

安装步骤:完全可以参照openssl目录下的install.win32

1perl Configure VC-WIN32

如不成功会有明显提示

2ms/do_ms

推荐使用这种方式

    另外两种方式 如果使用也必须保证本机有编译器才能使用。

     :ms/do_masm (默认vc自带的;也可以自己下载安装)    

     :ms/do_nasm (需要下载)

    

    

3DOS窗口下转到【Microsoft Visual Studio/VC98/bin目录下执行 vcvars32.bat 以配置环境变量。

4、跳到openssl目录下,执行:

nmake -f ms/ntdll.mak

如果编译成功,最后的输出都在out32dll目录下:包括可执行文件、两个dll和两个lib文件: libeay32.dll, libeay32.lib, ssleay32.dll, ssleay32.lib

编程初步:

1 打开VCTools-Options-Directores,在Include files中增加【openssl- 0.9.8 /inc32目录;在Libray files中增加【openssl-0.9.8/out32dll

2 在源程序中包含以下三个头文件并加入LIB

#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#pragma comment(lib, "libeay32.lib")

#pragma comment(lib, "ssleay32.lib")

3 参考源码开始编程http://www.kennethballard.com/Download/intro-openssl.zip

文件:

intro-openssl.zip

大小:

8KB

下载:

下载

参考:

使用 OpenSSL API 进行安全编程

http://www-128.ibm.com/developerworks/cn/linux/l-openssl.html

OpenSSL官方站点:http://www.openssl.org/   

利用curl来做http访问

本文转自:http://www.wwoon.com/bbs/read.php?tid-881.html

 

 

/* curl_httpget.h */
#ifndef CURL_HTTPGET_H
#define CURL_HTTPGET_H
struct MemoryStruct {
char *memory;
size_t size;
};
int curl_httpget(const char* URLbuf,struct MemoryStruct *chunk,const char* cookiefile);
#endif

 

/* curl_httpget.cpp */
#include <curl/curl.h>
#include <curl/types.h>
#include <curl/easy.h>
#include "curl_httpget.h"
#include <alloc.h>


void *myrealloc(void *ptr, size_t size)
{ /* There might be a realloc() out there that doesn't like reallocing  NULL pointers, so we take care of it here */
if(ptr)
  
return realloc(ptr, size);
else
  
return malloc(size);
}


size_t
WriteMemoryCallback
(void *ptr, size_t size, size_t nmemb, void *data)
{
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)data;
mem
->memory = (char *)myrealloc(mem->memory, mem->size + realsize + 1);
if (mem->memory) {
  
memcpy(&(mem->memory[mem->size]), ptr, realsize);
  mem
->size += realsize;
  mem
->memory[mem->size] = 0;
}
return realsize;
}
int curl_httpget(const char* URLbuf,struct MemoryStruct *chunk,const char* cookiefile)
{
CURL
*curl_handle;
int errcode=0;
chunk
->memory=NULL; /* we expect realloc(NULL, size) to work */
chunk
->size = 0; /* no data at this point */
curl_global_init
(CURL_GLOBAL_ALL);
/* init the curl session */
curl_handle
= curl_easy_init();
curl_easy_setopt
(curl_handle, CURLOPT_URL, URLbuf);
curl_easy_setopt
(curl_handle, CURLOPT_COOKIEFILE,cookiefile);
/* send all data to this function */
curl_easy_setopt
(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
/* we pass our 'chunk' struct to the callback function */
curl_easy_setopt
(curl_handle, CURLOPT_WRITEDATA, (void *)chunk);
curl_easy_setopt
(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0");
curl_easy_setopt
(curl_handle, CURLOPT_COOKIEJAR,cookiefile);
/* get it! */
errcode
=curl_easy_perform(curl_handle);
  
/* cleanup curl stuff */
curl_easy_cleanup
(curl_handle);
return errcode;
}

调用方法

errcode= curl_httpget(URLbuf,&chunk,cookiefile);
  
if(!errcode){
  
int min=sizeof(content)>chunk.size?chunk.size:sizeof(content);
  
strncpy(content,chunk.memory,min);
  
}else{
    
strcpy(content,"");
  
}
  
if(chunk.memory) free(chunk.memory);

/*************************************************************/

CURLOPT_WRITEFUNCTION

Function pointer that should match the following prototype: size_t function( void *ptr, size_t size, size_t nmemb, void *stream); This function gets called by libcurl as soon as there is data received that needs to be saved. The size of the data pointed to by ptr is size multiplied with nmemb, it will not be zero terminated. Return the number of bytes actually taken care of. If that amount differs from the amount passed to your function, it'll signal an error to the library and it will abort the transfer and return CURLE_WRITE_ERROR.

This function may be called with zero bytes data if the transfered file is empty.

Set this option to NULL to get the internal default function. The internal default function will write the data to the FILE * given with CURLOPT_WRITEDATA.

The callback function will be passed as much data as possible in all invokes, but you cannot possibly make any assumptions. It may be one byte, it may be thousands. The maximum amount of data that can be passed to the write callback is defined in the curl.h header file: CURL_MAX_WRITE_SIZE.

CURLOPT_WRITEDATA

Data pointer to pass to the file write function. If you use the CURLOPT_WRITEFUNCTION option, this is the pointer you'll get as input. If you don't use a callback, you must pass a 'FILE *' as libcurl will pass this to fwrite() when writing data.

The internal CURLOPT_WRITEFUNCTION will write the data to the FILE * given with this option, or to stdout if this option hasn't been set.

If you're using libcurl as a win32 DLL, you MUST use the CURLOPT_WRITEFUNCTION if you set this option or you will experience crashes.

This option is also known with the older name CURLOPT_FILE, the name CURLOPT_WRITEDATA was introduced in 7.9.7 .

使用 OpenSSL API 进行安全编程

学习如何使用 OpenSSL —— 用于安全通信的最著名的开放库 —— API 有些强人所难,因为其文档并不完全。您可以通过本文中的提示补充这方面的知识,并驾驭该 API。在建立基本的连接之后,就可以查看如何使用 OpenSSL BIO 库来建立安全连接和非安全连接。与此同时,您还会学到一些关于错误检测的知识。

OpenSSL API 的文档有些含糊不清。因为还没有多少关于 OpenSSL 使用的教程,所以对初学者来说,在 应用程序中使用它可能会有一些困难。那么怎样才能使用 OpenSSL 实现一个基本的安全连接呢? 本教程将帮助您解决这个问题。

学习如何实现 OpenSSL 的困难部分在于其文档的不完全。不完全的 API 文档通常会妨碍开发人员 使用该 API,而这通常意味着它注定要失败。但 OpenSSL 仍然很活跃,而且正逐渐变得强大。这是为什么?

OpenSSL 是用于安全通信的最著名的开放库。在 google 中搜索“SSL library”得到的返回结果中, 列表最上方就是 OpenSSL。它诞生于 1998 年,源自 Eric Young Tim Hudson 开发的 SSLeay 库。其他 SSL 工具包包括遵循 GNU General Public License 发行的 GNU TLS,以及 Mozilla Network Security ServicesNSS)(请参阅本文后面的 参考资料 ,以获得 其他信息)。

那么,是什么使得 OpenSSL GNU TLSMozilla NSS 或其他所有的库都优越呢?许可是一方面因素 (请参阅 参考资料)。此外,GNS TLS(迄今为止)只支持 TLS v1.0 SSL v3.0 协议,仅此而已。

Mozilla NSS 的发行既遵循 Mozilla Public License 又遵循 GNU GPL,它允许开发人员进行选择。 不过,Mozilla NSS OpenSSL 大,并且需要其他外部库来对库进行编译,而 OpenSSL 是完全 自包含的。与 OpenSSL 相同,大部分 NSS API 也没有文档资料。Mozilla NSS 获得了 PKCS #11 支持,该支持可以用于诸如智能卡这样的加密标志。OpenSSL 就不具备这一支持。

先决条件

要充分理解并利用本文,您应该:

  • 精通 C 编程。
  • 熟悉 Internet 通信和支持 Internet 的应用程序的编写。

并不绝对要求您熟悉 SSL ,因为稍后将给出对 SLL 的简短说明;不过,如果您希望得到详细论述 SSL 的文章的链接,请参阅 参考资料部分。拥有密码学方面的知识固然好,但这 并不是必需的。

 


 

什么是 SSL

SSL 是一个缩写,代表的是 Secure Sockets Layer。它是支持在 Internet 上进行安全通信的 标准,并且将数据密码术集成到了协议之中。数据在离开您的计算机之前就已经被加密,然后只有 到达它预定的目标后才被解密。证书和密码学算法支持了这一切的运转,使用 OpenSSL,您将 有机会切身体会它们。

理论上,如果加密的数据在到达目标之前被截取或窃听,那些数据是不可能被破解的。不过, 由于计算机的变化一年比一年快,而且密码翻译方法有了新的发展,因此,SSL 中使用的加密协议 被破解的可能性也在增大。

可以将 SSL 和安全连接用于 Internet 上任何类型的协议,不管是 HTTPPOP3,还是 FTP。还可以用 SSL 来保护 Telnet 会话。虽然可以用 SSL 保护任何连接,但是不必对每一类连接都使用 SSL。 如果连接传输敏感信息,则应使用 SSL

 


 

什么是 OpenSSL

OpenSSL 不仅仅是 SSL。它可以实现消息摘要、文件的加密和解密、数字证书、数字签名 和随机数字。关于 OpenSSL 库的内容非常多,远不是一篇文章可以容纳的。

OpenSSL 不只是 API,它还是一个命令行工具。命令行工具可以完成与 API 同样的工作, 而且更进一步,可以测试 SSL 服务器和客户机。它还让开发人员对 OpenSSL 的能力有一个 认识。要获得关于如何使用 OpenSSL 命令行工具的资料,请参阅 参考资料部分。

 


 

您需要什么

首先需要的是最新版本的 OpenSSL。查阅参考资料部分,以确定从哪里可以获得最新的可以自己编译的源代码, 或者最新版本的二进制文件(如果您不希望花费时间来编译的话)。不过,为了安全起见, 我建议您下载最新的源代码并自己编译它。二进制版本通常是由第三方而不是由 OpenSSL 的开发人员来编译和发行的。

一些 Linux 的发行版本附带了 OpenSSL 的二进制版本,对于学习如何使用 OpenSSL 库来说,这足够了;不过, 如果您打算去做一些实际的事情,那么一定要得到最新的版本,并保持该版本一直是最新的。

对于以 RPM 形式安装的 Linux 发行版本(Red HatMandrake 等),建议您通过从发行版本制造商那里获得 RPM 程序包来更新您的 OpenSSL 发行版本。出于安全方面的原因,建议您使用 最新版本的发行版本。如果您的发行版本不能使用最新版本的 OpenSSL,那么建议您只覆盖库文件,不要覆盖 可执行文件。OpenSSL 附带的 FAQ 文档中包含了有关这方面的细节。

还要注意的是,OpenSSL 并没有在所有的平台上都获得官方支持。虽然制造商已经尽力使其能够跨平台兼容, 但仍然存在 OpenSSL 不能用于您的计算机 和/或 操作系统的可能。请参阅 OpenSSL Web 站点( 参考资料 中 的链接),以获得关于哪些平台可以得到支持的信息。

如果想使用 OpenSSL 来生成证书请求和数字证书,那么必须创建一个配置文件。在 OpenSSL 程序包 的 apps 文件夹中,有一个名为 openssl.cnf 的 可用模板文件。我不会对该文件进行讨论,因为这不在本文要求范围之内。不过,该模板文件有一些非常好的注释,而且如果 在 Internet 上搜索,您可以找到很多讨论修改该文件的教程。

 


 

头文件和初始化

本教程所使用的头文件只有三个:ssl.hbio.h err.h。它们都位于 openssl 子目录中,而且都是开发您的项目 所必需的。要初始化 OpenSSL 库,只需要三个代码行即可。清单 1 中列出了所有内容。其他的头文件 和/或 初始化函数可能 是其他一些功能所必需的。


清单 1. 必需的头文件

/* OpenSSL headers */

#include "openssl/bio.h"

#include "openssl/ssl.h"

#include "openssl/err.h"

/* Initializing OpenSSL */

SSL_load_error_strings();

ERR_load_BIO_strings();

OpenSSL_add_all_algorithms();

 


 

建立非安全连接

不管连接是 安全的还是不安全的,OpenSSL 都使用了一个名为 BIO 的抽象库来处理包括文件和套接字在内的各种类型的通信。您还可以将 OpenSSL 设置成为一个过滤器,比如用于 UU Base64 编码的过滤器。

在这里对 BIO 库进行全面说明有点麻烦,所以我将根据需要一点一点地介绍它。首先, 我将向您展示如何建立一个标准的套接字连接。相对于使用 BSD 套接字库,该操作需要 的代码行更少一些。

在建立连接(无论安全与否)之前,要创建一个指向 BIO 对象的指针。这类似于在标准 C 中 为文件流创建 FILE 指针。


清单 2. 指针

BIO * bio;

 

打开连接

创建新的连接需要调用 BIO_new_connect 。您可以在同一个调用中同时 指定主机名和端口号。也可以将其拆分为两个单独的调用:一个是创建连接并设置主机名的 BIO_new_connect 调用,另一个是设置端口号的 BIO_set_conn_port (或者 BIO_set_conn_int_port )调用。

不管怎样,一旦 BIO 的主机名和端口号都已指定,该指针会尝试打开连接。没有什么可以影响它。如果创建 BIO 对象时遇到问题,指针将会是 NULL。为了确保连接成功,必须执行 BIO_do_connect 调用。


清单 3. 创建并打开连接

bio = BIO_new_connect("hostname:port");

if(bio == NULL)

{

    /* Handle the failure */

}

if(BIO_do_connect(bio) <= 0)

{

    /* Handle failed connection */

}

 

在这里,第一行代码使用指定的主机名和端口创建了一个新的 BIO 对象,并以所示风格对该对象进行 格式化。例如, 如果您要连接到 www.ibm.com 80 端口,那么该字符串将是 www.ibm.com:80 。调用 BIO_do_connect 检查连接是否成功。如果出错,则返回 0 -1

与服务器进行通信

不管 BIO 对象是套接字还是文件,对其进行的读和写操作都是通过以下两个函数来完成的: BIO_read BIO_write 。 很简单,对吧?精彩之处就在于它始终如此。

BIO_read 将尝试从服务器读取一定数目的字节。它返回读取的字节数、 0 或者 -1。在受阻塞的连接中,该函数返回 0,表示连接已经关闭,而 -1 则表示连接出现错误。在非阻塞连接的情况下,返回 0 表示没有可以获得的数据,返回 -1 表示连接出错。可以调用 BIO_should_retry 来确定是否可能重复出现该错误。


清单 4. 从连接读取

int x = BIO_read(bio, buf, len);

if(x == 0)

{

    /* Handle closed connection */

}

else if(x < 0)

{

   if(! BIO_should_retry(bio))

    {

        /* Handle failed read here */

    }

    /* Do something to handle the retry */

}

 

BIO_write 会试着将字节写入套接字。它将返回实际写入的 字节数、0 或者 -1。同 BIO_read 0 -1 不一定表示错误。 BIO_should_retry 是找出问题的途径。如果需要重试写操作,它必须 使用和前一次完全相同的参数。


清单 5. 写入到连接

if(BIO_write(bio, buf, len) <= 0)

{

    if(! BIO_should_retry(bio))

    {

        /* Handle failed write here */

    }

    /* Do something to handle the retry */

}

 

关闭连接

关闭连接也很简单。您可以使用以下两种方式之一来关闭连接: BIO_reset BIO_free_all 。如果您还需要重新使用对象,那么请使用第一种方式。 如果您不再重新使用它,则可以使用第二种方式。

BIO_reset 关闭连接并重新设置 BIO 对象的内部状态,以便可以重新使用连接。如果要在整个应用程序中使用同一对象,比如使用一台安全的聊天 客户机,那么这样做是有益的。该函数没有返回值。

BIO_free_all 所做正如其所言:它释放内部结构体,并释放 所有相关联的内存,其中包括关闭相关联的套接字。如果将 BIO 嵌入于一个类中,那么应该在类的 析构函数中使用这个调用。


清单 6. 关闭连接

/* To reuse the connection, use this line */

BIO_reset(bio);

/* To free it from memory, use this line */

BIO_free_all(bio);

 


 

建立安全连接

现在需要给出建立安全连接需要做哪些事情。惟一要改变的地方就是建立并进行连接。其他所有内容都是相同的。

安全连接要求在连接建立后进行握手。在握手过程中,服务器向客户机发送一个证书, 然后,客户机根据一组可信任证书来核实该证书。它还将检查证书,以确保它没有过期。要 检验证书是可信任的,需要在连接建立之前提前加载一个可信任证书库。

只有在服务器发出请求时,客户机才会向服务器发送一个证书。该过程叫做客户机认证。使用证书, 在客户机和服务器之间传递密码参数,以建立安全连接。尽管握手是在建立连接之后才进行的,但是客户机或服务器可以在任何时刻请求进行一次新的握手。

参考资料 部分中列出的 Netscasp 文章 和 RFC 2246 ,对握手以及建立安全连接的其他方面的知识进行了更详尽的论述。

为安全连接进行设置

为安全连接进行设置要多几行代码。同时需要有另一个类型为 SSL_CTX 的指针。该结构保存了一些 SSL 信息。您也可以利用它通过 BIO 库建立 SSL 连接。可以通过使用 SSL 方法函数调用 SSL_CTX_new 来创建这个结构,该方法函数通常是 SSLv23_client_method

还需要另一个 SSL 类型的指针来保持 SSL 连接结构(这是短时间就能完成的一些连接所必需的)。以后还可以用该 SSL 指针来检查连接信息或设置其他 SSL 参数。


清单 7. 设置 SSL 指针

SSL_CTX * ctx = SSL_CTX_new(SSLv23_client_method());

SSL * ssl;

 

加载可信任证书库

在创建上下文结构之后,必须加载一个可信任证书库。这是成功验证每个证书所必需的。如果 不能确认证书是可信任的,那么 OpenSSL 会将证书标记为无效(但连接仍可以继续)。

OpenSSL 附带了一组可信任证书。它们位于源文件树的 certs 目录中。 不过,每个证书都是一个独立的文件 —— 也就是说,需要单独加载每一个证书。在 certs 目录下,还有一个存放过期证书的子目录。试图加载这些证书将会出错。

如果您愿意,可以分别加载每一个文件,但为了简便起见,最新的 OpenSSL 发行版本的可信任证书 通常存放在源代码档案文件中,这些档案文件位于名为“TrustStore.pem”的单个文件中。如果已经有了一个可信任证书库, 并打算将它用于特定的项目中,那么只需使用您的文件替换清单 8 中的“TrustStore.pem”(或者使用 单独的函数调用将它们全部加载)即可。

可以调用 SSL_CTX_load_verify_locations 来加载可信任证书库文件。这里要用到 三个参数:上下文指针、可信任库文件的路径 和文件名,以及证书所在目录的路径。必须指定可信任库文件或证书的目录。 如果指定成功,则返回 1,如果遇到问题,则返回 0


清单 8. 加载信任库

if(! SSL_CTX_load_verify_locations(ctx, "/path/to/TrustStore.pem", NULL))

{

    /* Handle failed load here */

}

 

如果打算使用目录存储可信任库,那么必须要以特定的方式命名文件。OpenSSL 文档清楚 地说明了应该如何去做,不过,OpenSSL 附带了一个名为 c_rehash 的工具, 它可以将文件夹配置为可用于 SSL_CTX_load_verify_locations 的 路径参数。


清单 9. 配置证书文件夹并使用它

/* Use this at the command line */

c_rehash /path/to/certfolder

/* then call this from within the application */

if(! SSL_CTX_load_verify_locations(ctx, NULL, "/path/to/certfolder"))

{

    /* Handle error here */

}

 

为了指定所有需要的验证证书,您可以根据需要命名任意数量的单独文件或文件夹。您还可以同时指定 文件和文件夹。

创建连接

将指向 SSL 上下文的指针作为惟一参数,使用 BIO_new_ssl_connect 创建 BIO 对象。还需要获得指向 SSL 结构的指针。在本文中,只将该指针用于 SSL_set_mode 函数。而这个函数是用来设置 SSL_MODE_AUTO_RETRY 标记的。使用这个选项进行设置,如果服务器突然希望进行 一次新的握手,那么 OpenSSL 可以在后台处理它。如果没有这个选项,当服务器希望进行一次新的握手时, 进行读或写操作都将返回一个错误,同时还会在该过程中设置 retry 标记。


清单 10. 设置 BIO 对象

bio = BIO_new_ssl_connect(ctx);

BIO_get_ssl(bio, & ssl);

SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);

 

设置 SSL 上下文结构之后,就可以创建连接了。主机名是使用 BIO_set_conn_hostname 函数 设置的。主机名和端口的指定格式与前面的相同。该函数还可以打开到主机的连接。为了确认已经成功打开连接,必须 执行对 BIO_do_connect 的调用。该调用还将执行握手来建立安全连接。


清单 11. 打开安全连接

/* Attempt to connect */

BIO_set_conn_hostname(bio, "hostname:port");

/* Verify the connection opened and perform the handshake */

if(BIO_do_connect(bio) <= 0)

{

    /* Handle failed connection */

}

 

连接建立后,必须检查证书,以确定它是否有效。实际上,OpenSSL 为我们完成了这项任务。如果证书有致命的 问题(例如,哈希值无效),那么将无法建立连接。但是,如果证书的问题并不是致命的(当它已经过期 或者尚不合法时),那么仍可以继续使用连接。

可以将 SSL 结构作为惟一参数,调用 SSL_get_verify_result 来查 明证书是否通过了 OpenSSL 的检验。如果证书通过了包括信任检查在内的 OpenSSL 的内部检查,则返回 X509_V_OK。如果有地方出了问题,则返回一个错误代码,该代码被记录在命令行工具的 verify 选项下。

应该注意的是,验证失败并不意味着连接不能使用。是否应该使用连接取决于验证结果和安全方面的考虑。例如, 失败的信任验证可能只是意味着没有可信任的证书。连接仍然可用,只是需要从思想上提高安全意识。


清单 12. 检查证书是否有效

if(SSL_get_verify_result(ssl) != X509_V_OK)

{

    /* Handle the failed verification */

}

 

这就是所需要的全部操作。通常,与服务器进行通信都要使用 BIO_read BIO_write 。并且只需调用 BIO_free_all BIO_reset ,就可以关闭 连接,具体调用哪一个方法取决于是否重用 BIO

必须在结束应用程序之前的某个时刻释放 SSL 上下文结构。可以调用 SSL_CTX_free 来释放该结构。


清单 13. 清除 SSL 上下文

SSL_CTX_free(ctx);

 


 

错误检测

显然 OpenSSL 抛出了某种类型的错误。这意味着什么?首先,您需要得到错误代码本身; ERR_get_error 可以完成这项任务;然后,需要将错误代码转换为错误 字符串,它是一个指向由 SSL_load_error_strings ERR_load_BIO_strings 加载到内存中的永久字符串的指针。 可以在一个嵌套调用中完成这项操作。

1 略述了从错误栈检索错误的方法。清单 24 展示了如何打印文本字符串中的最后一个 错误信息。

1. 从栈中检索错误

ERR_reason_error_string

返回一个静态字符串的指针,然后可以将字符串显示在屏幕上、写入文件,或者以任何您希望的方式进行处理

ERR_lib_error_string

指出错误发生在哪个库中

ERR_func_error_string

返回导致错误的 OpenSSL 函数


清单 14. 打印出最后一个错误

printf("Error: %s/n", ERR_reason_error_string(ERR_get_error()));

 

您还可以让库给出预先格式化了的错误字符串。可以调用 ERR_error_string 来 得到该字符串。该函数将错误代码和一个预分配的缓冲区作为参数。而这个缓冲区必须是 256 字节长。如果参数 为 NULL,则 OpenSSL 会将字符串写入到一个长度为 256 字节的静态缓冲区中,并返回指向该缓冲区的 指针。否则,它将返回您给出的指针。如果您选择的是静态缓冲区选项,那么在下一次调用 ERR_error_string 时,该缓冲区会被覆盖。


清单 15. 获得预先格式化的错误字符串

printf("%s/n", ERR_error_string(ERR_get_error(), NULL));

 

您还可以将整个错误队列转储到文件或 BIO 中。可以通过 ERR_print_errors ERR_print_errors_fp 来实现这项操作。队列是以可读格式被转储的。第一个函数将队列发送到 BIO ,第二个函数将队列发送到 FILE 。 字符串格式如下(引自 OpenSSL 文档):

[pid]:error:[error code]:[library name]:[function name]:[reason string]:[file name]:[line]:[optional text message]

其中, [pid] 是进程 ID [error code] 是一个 8 位十六进制代码, [file name] OpenSSL 库中的源代码文件, [line] 是源文件中的行号。


清单 16. 转储错误队列

ERR_print_errors_fp(FILE *);

ERR_print_errors(BIO *);

 

 

 

开始做吧

使用 OpenSSL 创建基本的连接并不困难,但是,当试着确定该如何去做时,文档可能是一个小障碍。本文向您介绍了一些基本概念,但 OpenSSL 还有很多灵活之处有待发掘,而且 您还可能需要一些高级设置,以便项目能够充分利用 SSL 的功能。

本文中有两个样例。一个样例展示了到 http://www.verisign.com/ 的非安全连接,另一个则展示了到 http://www.verisign.com/ 的安全 SSL 连接。两者都是连接到服务器并下载其主页。它们没有进行 任何安全检查,而且库中的所有设置都是默认值 —— 作为本文的一部分,应该只将这些用于教学目的。

在任何支持的平台上,源代码的编译都应该是非常容易的,不过我建议您使用最新版本的 OpenSSL。在撰写本文时,OpenSSL 的最新版本是 0.9.7 d

自动上传 Nokia N800 照片(libcurl

首先,让我们快速回顾一下。在这个分三部分的系列的 第 1 中,演示了 Nokia N800 Linux® 的内部结构,列出了它的 技术规范和物理参数,并阐述了如何设置和测试构建环境。在 第 2 的末尾,展示了一个程序,只要用户按下一个按钮,它就会将一幅图像压缩为 JPEG 文件,并将其保存在内存中。

现在,在第 3 期也是最后一期文章中,您将会看到如何将这些 JPEG 文件自动上传到远程站点。

上传文件

关于开发 Nokia N770 N800 的其他 developerWorks 文章

上传文件比我最初所希望的稍微困难一些。N800 没有提供很多文件上传和下载工具(尽管它提供了 curl)。无论如何,应该避免将文件保存在本地。

此方法从应用程序直接使用 libcurl,而不是在命令行运行 curl。与 libjpeg 一样,Libcurl 用于处理 stdio FILE 对象,而不是内存缓冲区。

幸运的是,通过扩展 GNU C 库,可以改变这种现状。 fmemopen() 函数提供了一个 stdio FILE * 对象,该对象表示内存中的一个缓冲区。通过调用 fmemopen 取代 test.jpg open,问题就解决一半了:


清单 1. 使用 fmemopen

               

static unsigned char jpegdata[JPEG_SIZE];

FILE *out;

 

out = fmemopen(jpegdata, JPEG_SIZE, "wb");

 

JPEG_SIZE 宏被定义为 512KB,通过试验可以达到这个值(在保证较高质量的前提下,我在测试中所见过的最大大小为 360KB 左右,因此我认为少量的安全冗余已经足够了)。

JPEG 库调用总体上没有什么变化。完成这些调用后,所需的只是使用 libcurl 上传文件。以下是上传文件的示例代码:


清单 2. 使用 libcurl

               

curl = curl_easy_init();

curl_easy_setopt(curl, CURLOPT_UPLOAD, TRUE);

curl_easy_setopt(curl, CURLOPT_URL,

 "ftp://test:dwtest@www.example.net/public_html/test.jpeg");

curl_easy_setopt(curl, CURLOPT_READDATA, jpeg);

curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t) size);

curl_easy_perform(curl);

curl_easy_cleanup(curl);

 

在大多数情况下,此代码都能发挥作用。我们忽略了 CURLOPT_INFILESIZE_LARGE 选项,它用于指定要上传的文件大小。 curl 上传整个文件 512KB 的情况下,大部分都是空字节。curl 库假定该大小是所建议的大小。幸运的是,我们可以再次使用 fmemopen 来解决:


清单 3. 再次使用 fmemopen

               

FILE *jpeg;

off_t size;

size = ftell(out);

jpeg = fmemopen(jpegdata, size, "rb");

 

现在,把新的 JPEG 文件句柄交给 curl,结果几乎与我所期望的一样。第一次以后的每次上传都会产生错误 18CURLE_PARTIAL_FILE,提示文件传输没有全部完成。因为文件大小是我所期望的大小,并且不会影响到图像,所以我忽略了这个错误。

借助 libjpeg libcurl,摄像机应用程序基本上能够实现所需的功能了。它上传来自摄像机的图像流,因此不会消耗太多的本地磁盘空间。当然,如果很注重这项功能,可以添加配置选项,允许用户指定文件上传位置和上传方式。

您可能觉得用同一个缓冲区打开两个 FILE 对象会有风险。其实不用担心,因为两个对象重叠的部分并不会被执行。在 libcurl 开始之前,libjpeg 就已经完成对缓冲区的处理。fflush() 看起来有些多余,但是可以确保剩余的数据仍在内存中。当然,还有更多的工作要做。

 


 

图像排队

始 终为同一个文件分配相同的名称并不是最佳方法,而且如果这样做,就得考虑一些问题,比如如果网络连接断开会发生什么拍照的速度有多快。初步的解决方案是,当后台线程试图上传编码消息时对这些消息进行存储。这会导致需要处理一些新的 事情,特别是,针对上传进行排列的 JPEG 文件链接列表和一些 pthread 代码。

以下是第一个数据结构:


清单 4. JPEG 数据列表

               

typedef struct jpeg_data {

            struct jpeg_data *next, *prev;

            size_t size;

            unsigned char *data;

} jpeg_data_t;

 

传送的 AppData 结构需要一个指向 jpeg_data_t 的指针作为列表标题;还需要一个 pthread 互斥锁来确保在线程交互中上传例程和 JPEG 创建代码不会冲突。以下是具体的 JPEG 代码,从输出文件的刷新开始:


清单 5. 排队以便稍后传递

               

fflush(out);

j = malloc(sizeof(jpeg_data_t));

j->prev = NULL;

j->size = ftell(out);

j->data = malloc(j->size);

memcpy(j->data, jpeg_data, j->size);

pthread_mutex_lock(&appdata->jpeg_mutex);

j->next = appdata->jpegs;

if (j->next)

       j->next->prev = j;

appdata->jpegs = j;

pthread_mutex_unlock(&appdata->jpeg_mutex);

fclose(out);

jpeg_destroy_compress(&comp);

 

现在只剩下两个次要的任务 —— 初始化互斥锁和启动线程运行可上传的文件的列表。最后的任务可能是最有趣的代码块;此处删除了一些可预知但是冗长的代码块,以使代码更加简洁:


清单 6. 忙碌与等待

               

void *

upload_jpegs(void *v) {

  AppData *appdata = v;

  jpeg_data_t *j = NULL;

  for (;;) {

    pthread_mutex_lock(&appdata->jpeg_mutex);

    if (appdata->jpegs) {

      /* extract last item from the list, as j */

    }

    pthread_mutex_unlock(&appdata->jpeg_mutex);

    if (j) {

      if (upload_jpeg(j)) {

            free(j->data);

            free(j);

            j = NULL;

      } else {

            jpeg_data_t *list;

            pthread_mutex_lock(&appdata->jpeg_mutex);

            /* put j back on the list */

            pthread_mutex_unlock(&appdata->jpeg_mutex);

            sleep(10);

      }

    } else {

      if (appdata->done)

            pthread_exit(0);

      sleep(5);

    }

  }

}

 

检查 appdata->done 可以发现代码的另外一处需求;当 GUI 开始清除时,由于程序已经退出,用户无法知道是否还有图片等待上传,因此清除代码设置了一个标记,让上传程序知道没有需要处理的数据,然后使用 pthread_join() 等待上传程序。

 


 

打包

执 行了诸多操作后(也借鉴了一些来自 #maemo IRC 频道的优秀技巧),摄像机应用程序现在能够正常运行了。下一步是将其打包,以便在 N800 上安装。标准方法是使用 dpkg 工具进行打包,这种方法不仅灵活而且功能强大;它也需要较大的开销,而且对于单个独立的二进制文件来说有些大材小用。

在本例中,我使用比较简单的策略,即构建一个非常小的包。一个 debian 包是一个包含 3 个文件的归档文件(通过 ar 命令生成):

  • 名为 debian-binary 的文件表示 debian 包版本(它只能包含文本 “2.0”),
  • 名为 control.tar.gz 的文件包含一些元数据,
  • 名为 data.tar.gz 的文件包含将要安装的文件。

数 据文件事实上只需包含两个文件。一个是实际的程序,另一个为 “desktop” 文件,该文件告诉应用程序管理器关于数据文件的信息。我把实际的程序安装在 /usr/bin 中,把 desktop 文件安装在 /usr/share/applications/hildon 中。我使用了一个非常小的 desktop 文件:


清单 7. desktop 文件

               

[Desktop Entry]

Encoding=UTF-8

Version=0.1

Type=Application

Name=dW Cam

Exec=/usr/bin/dwcam

X-Window-Icon=tn-bookmarks-link

X-HildonDesk-ShowInToolbar=true

X-Osso-Type=application/x-executable

Terminal=false

 

该文件告诉应用程序管理器应该为应用程序 创建一个条目,条目名为 dW Cam,该条目实际上是通过 /usr/bin/dwcam 来实现的。没有指定定制图标;系统只为应用程序使用默认的图标。可通过该图标在菜单上获取应用程序。将这两个文件编译为一个称作 data.tar.gz tarball 文件;需要使用相对路径名进行编译,例如 dwcam 的路径名为 ./usr/bin/dwcam

现 在需要处理 debian 包文件元数据。该元数据至少包含一个版权文件、一个 changelog 和一个控制文件;它们将被编译为一个称为 control.tar.gz tarball 文件。控制文件告诉 Debian 包代码如何处理应用程序。以下是这个控制文件。


清单 8. 控制文件

               

Package: cam-app

Section: user/accessories

Priority: optional

Maintainer: Peter Seebach <seebs@seebs.net>

Build-Depends: gstreamer (>= 0.10)

Stardards-Version: 3.6.0

Architecture: armel

Version: 0.1

Depends: libatk1.0-0 (>= 1.9.0 ), libc6 (>= 2.3.5-1), [...]

Description: A trivial camera application.

XB-Maemo-Icon-26:

 iVBORw0KGgoAAAANSUhEUgAAABoAAAAZCAAAAAAKtWG8AAAACXBIWXMAAAAnAAAAJwEqCZFP

 AAAAB3RJTUUH1QkMEgEBuF+MPAAAACF0RVh0Q29tbWVudABKUEVHOmdudS1oZWFkLmpwZyAy

 [...]

 

我并没有包含全部的依赖列表,但是通常情况下这应该是一个依赖关系列表。如果使用 dpkg 来构建包,这些依赖项将被自动填充。XB-Maemo-Icon-26 字段是一个小图标的 base64 表示。

一旦生成了控制和数据 tarball 文件,您需要使用 ar 将它们和 debian-binary 文件连接在一起。将 debian-binary 文件作为归档文件中的第一个文件,这一点很重要;否则,debian 工具不会将文件识别为一个 debian 包。连接完成之后,就可以安装生成的文件了,可以从命令行通过 dpkg -i dwcam-0.1.deb 命令安装,或者使用应用程序管理器,使用菜单项 Application -> Install from file... 安装。

快速检查表明,新图标已经出现在 Tools/Extras 菜单的 dW Cam 名称下面,而且应用程序能够正常运行。

 


 

改进空间

分享这篇文章……

digg

将本文提交到 Digg

del.icio.us

发布到 del.icio.us

Slashdot

很容易发现,这个功能还有很大改进空间。如果需要移植到其他平台,也许有必要设置 dpkg 以处理不同版本。如果只有一个目标平台,那么可以将编译器的标记连接起来;如果需要多个目标平台,则有点困难。构建最终程序所使用的标记非常长:


清单 9. C 编译器标记

               

cc --std=c99 -g -O2 -Wall -I/usr/include/atk-1.0 /

                 -I/usr/include/gtk-2.0 -I/usr/include/pango-1.0 /

                 -I/usr/lib/gtk-2.0/include -I/usr/include/dbus-1.0 /

                 -I/usr/lib/dbus-1.0/include -I/usr/include/libxml2 /

                 -I/usr/lib/glib-2.0/include -I/usr/include/glib-2.0 /

                 -I/usr/include/gstreamer-0.10 -o dwcam dwcam.c /

                 -lgstbase-0.10 -lgstinterfaces-0.10 -losso -lgtk-x11-2.0 /

                 -ljpeg -lhildonbase -lhildonwidgets -lcurl

 

对于如何避免来自不同库的包含文件之间的冲突,不同系统具有不同的编程习惯;一些系统使用 -I/usr/local/include 处理上述的所有包含路径。尽管如此,虽然 dpkg 系统对于我的项目来说有些大材小用,如果想要继续开发 和扩展系统或其他设备,dpkg 系统非常有用而且速度很快。

显 然,可以进行改进的另一个主要区域是二进制文件中硬编码密码的使用。应用程序应该有一个不错的用户界面,允许配置各种设置,比如如何动态地上传文件,上传 到哪里;同样地,应用程序也应该可以设置在缺乏用户指示时拍照的频率。实现这个任务的代码并不很复杂;由于该技术与 N800 没有多大关系,也为了使代码更简洁,我们省略了一部分代码。这些代码更适合针对 Gtk 开发的更全面讨论。

 

参考资料

学习

研究libcurl

今天上午装了一下dotproject, 发现安装后甘特图中文会出现乱码,再网上找了若干篇相关文章,皆说是与jpgraph对中文编码支持有问题的历史原因,也看到了解决方法,但步骤甚多,遂放弃。

晚上粗略研究了一下libcurl的接口, 发现其phpc接口很像,基本上都是
curl_easy_init
curl_easy_setopt
curl_easy_perform|exec
curl_easy_cleanup|close

其中最关键的函数是curl_easy_setopt (phpcurl_setopt) ,用来设置请求的方式,参数. 和处理请求的方法。

贴一段简单的c代码,这个代码作用适读取rainx.cn并显示在Stdout ,并显示verbose信息,用来debug.

/**
   
   
 * This programe is a simple test for curl easy lib.
   
   
 * author : rainx1982 at yahoo.com.cn
   
   
 */
   
   

   
   
    
     
   
   
#include <stdio.h>
   
   

   
   
    
     
   
   
#include <stdlib.h>
   
   

   
   
    
     
   
   
#include <curl/curl.h>
   
   

   
   
    
     
   
   

   
   
    
     
   
   
int main(int argc, char** argv)
   
   
{
   
   

   
   
    
     
   
   
    CURL* curl;
   
   
    CURLcode curl_code;
   
   
    curl        = curl_easy_init();
   
   
    curl_easy_setopt(curl, CURLOPT_VERBOSE,1);
   
   
//     curl_easy_setopt(curl, CURLOPT_GET, 1);
   
   
    curl_easy_setopt(curl, CURLOPT_URL, "http://rainx.cn/");
   
   
    curl_code   = curl_easy_perform(curl);
   
   
    curl_easy_cleanup(curl);
   
   
    return 0;
   
   

   
   
    
     
   
   
}

编译:
- gcc -o testcurl testcurl.c -lcurl

libcurl使用心得

Libcurl为一个免费开源的,客户端url传输库,支持FTPFTPSTFTPHTTPHTTPSGOPHERTELNETDICTFILELDAP,跨平台,支持WindowsUnixLinux等,线程安全,支持Ipv6。并且易于使用。

http://curl.haxx.se/libcurl/

http://curl.haxx.se/libcurl/ 下载一个稳定的版本,注意选择OS
在使用之前请大家多阅读libcurl的文档:因为如果要实际运用到项目中,最好对libcurl有具体的了解,具体在
http://curl.haxx.se/libcurl/c/
curl_easy_setopt()
curl_easy_perform()
curl_easy_getinfo()
这三个函数的使用上,需要多去钻研,多看Samples,你才能灵活使用libcurl
感谢这篇文章:
http://blog.163.com/xu_chao2000/blog/static/27770610200801303252802/
给了我许多启发,再次感谢!

给出我的一个简单的代码例子:
说明:
1.PM...
等参数,没有具体意义,就是long,boolint,char*等进行特殊封装过,你可以用普通类型取代。

2.
关键在curl_easy_setopt函数设置option,可以设置ftp,http,get,post等许多选项,请根据具体使用情况设置。

3.
对取回来的数据需要进行判断,比如http下载文件,如果文件不存在,需要进行处理。因为writer是可以将buf填充404 not found等网页内容的,不能将这个内容当成文件内容,所以需要判断http web返回来的code,进行判断。

4.
我有个问题,就是想得到服务器上filename的具体名称,verbose调试已经返回了,但是我在getinfo的时候,试过好多选项,但未找到这个存放真实服务器文件名的选项,如果有知道的麻烦告诉我,谢谢了!

#include "curl/curl.h"
#pragma comment(lib, "libcurl.lib")

PM_ULONG writer(PM_VOID *data, PM_INT size, PM_INT nmemb, PmString &content);
PM_BOOL  CurlInit(CURL *&curl, PM_LPCSTR url,PmString &content);
PM_BOOL  GetURLDataBycurl(PM_LPCSTR URL,     PmString &content);

void main()
{
    
//PM_LPSTR url="http://down1.chinaunix.net/distfiles/libpcap-1.0.20050129.tar.gz";
    PM_LPCSTR url ="http://www.baidu.com/img/baidu.gif";
    PmString content;
    
if ( GetURLDataBycurl(url,content))
    
{
        printf("%s/n",content);

    }
    getchar();
}

PM_BOOL CurlInit(CURL *&curl, PM_LPCSTR url,PmString &content)
{
    CURLcode code;
    PmString error;
    curl = curl_easy_init();
    
if (curl == NULL)
    
{
        debug0( "Failed to create CURL connection/n");
        
return PM_FALSE;
    }
    code = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error);
    
if (code != CURLE_OK)
    
{
        debug1( "Failed to set error buffer [%d]/n", code );
        
return PM_FALSE;
    }
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
    code = curl_easy_setopt(curl, CURLOPT_URL, url);
    
if (code != CURLE_OK)
    
{
        debug1("Failed to set URL [%s]/n", error);
        
return PM_FALSE;
    }
    code = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
    
if (code != CURLE_OK)
    
{
        debug1( "Failed to set redirect option [%s]/n", error );
        
return PM_FALSE;
    }
    code = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writer);
    
if (code != CURLE_OK)
    
{
        debug1( "Failed to set writer [%s]/n", error);
        
return PM_FALSE;
    }
    code = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &content);
    
if (code != CURLE_OK)
    
{
        debug1( "Failed to set write data [%s]/n", error );
        
return PM_FALSE;
    }
    
return PM_TRUE;
}

PM_ULONG writer(PM_VOID *data, PM_INT size, PM_INT nmemb, PmString &content)
{
    PM_ULONG sizes = size * nmemb;
    PmString temp(data,sizes);
    content += temp; 
    
return sizes;
}

PM_BOOL  GetURLDataBycurl(PM_LPCSTR URL,  PmString &content)
{
    CURL *curl = NULL;
    CURLcode code;
    PmString error;

    code = curl_global_init(CURL_GLOBAL_DEFAULT);
    
if (code != CURLE_OK)
    
{
        debug1( "Failed to global init default [%d]/n", code );
        
return PM_FALSE;
    } 
    
    
if ( !CurlInit(curl,URL,content) )
    
{
        debug0( "Failed to global init default [%d]/n" );
        
return PM_FALSE;
    }
    code = curl_easy_perform(curl);
    
if (code != CURLE_OK)
    
{
        debug2( "Failed to get '%s' [%s]/n", URL, error);
        
return PM_FALSE;
    }
    PM_ULONG retcode = 0;
    code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE , &retcode); 
    
if ( (code == CURLE_OK) && retcode == 200 )
    
{
        PM_DOUBLE length = 0;
        code = curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD , &length); 
        printf("%d",retcode);
        FILE * file = fopen("1.gif","wb");
        fseek(file,0,SEEK_SET);
        fwrite(content.c_str(),1,length,file);
        fclose(file);

        
//struct curl_slist *list;
        //code = curl_easy_getinfo(curl,CURLINFO_COOKIELIST,&list);
        //curl_slist_free_all (list);

        
return PM_TRUE;
    }
    
else
    
{
    
//    debug1( "%s /n ",getStatusCode(retcode));
        return PM_FALSE;
    }
    curl_easy_cleanup(curl);
    
return PM_FALSE;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值