豌豆

没有荚

libcurl 是一个免费开源的 客户端 的网络传输库,它支持多

种协议,包括
DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet and TFTP等,还支持 SSL 认证。它简单好用,用它自己的话来说,就是 free, thread-safe, IPv6 compatible, feature rich, well supported, fast, thoroughly documented and is already used by many known, big and successful companies and
numerous applications。

1 基本流程

使用 libcurl 的一般流程:

  1. curl_global_init() 进行库的初始化

  2. curl_easy_init() 获取CURL* 指针

  3. curl_easy_setopt() 设置传输参数,包括回调等

  4. curl_easy_perform() 完成传输

  5. curl_easy_cleanup() 释放内存

  6. curl_global_cleanup() 释放库内存

我们需要着重关心的,是第 3 步。在这一步里,我们将指定 libcurl 如何将参数传递给服务端。

1.1 简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include "curl/curl.h"

using namespace std;

size_t read_callback(char *buffer, size_t size, size_t nitems, void *instream)
{
cout << buffer << endl;
return size * nitems;
}

int main(void)
{

curl_global_init(CURL_GLOBAL_ALL);
CURL* handle = curl_easy_init();
if (!handle)
{
cout << "curl_easy_init error" << endl;
}

struct curl_slist* headers = NULL;
curl_slist_append(headers, "user-agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) \
Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0");
curl_slist_append(headers, "accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");

curl_easy_setopt(handle, CURLOPT_URL, "http://curl.haxx.se/");
curl_easy_setopt(handle, CURLOPT_HEADER, 1L);
curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(handle, CURLOPT_READFUNCTION, read_callback);

CURLcode status = curl_easy_perform(handle);
if (status != CURLE_OK)
{
cout << "curl_easy_perform Error:" << status << endl;
}
curl_easy_cleanup(handle);

curl_global_cleanup();

return 0;
}

2 curl_easy API 介绍

Read more »

概述[

lambda

](https://code.wandoer.com/wp-content/uploads/2016/01/images.jpeg)

python 中的 lambda 比较简单,只是作为创建匿名函数来使用

  • lambda 只是一个表达式,而不是一个代码块。类似于 C++ 中的 define ,但比 define 要简单.Python 中的 lambda 只能封装有限的逻辑进去。

  • lambda 拥有自己的全名空间,且不能访问自有参数列表之外及全局的参数。

语法

labmda 函数的语法只包含一个语句:

1
lambda [arg ...] : exception

lambda 可以传入多个参数,使用逗号 “,” 分隔。 exception 使用这些参数进行运算并将结果返回。这里的 exception 隐藏了 return 关键词。 示例:

1
2
fun = lambda a,b,c : a+b+c
print "call fun(1,2,3) : " , fun(1,2,3)

以上输出:

1
call fun(1,2,3) : 6

在 python 中的使用

lambda 的使用比较灵活。例如可以在函数内部定义匿名函数,实现简单逻辑并在函数内部使用。另一个常见的应用场景则是作为匿名函数对象在迭代器中使用。比如:

1
2
3
map(function, iterable, ...)
filter(function, iterable)
reduce(function, iterable[, initializer])
  • map : 对迭代器招待 function 操作,并将操作后的 list 返回

  • filter : function 应该返回一个布尔值,对迭代器操作为 False 的,将从 List 里排除

  • reduce : 对迭代器进行累计操作,即将上一次操作的结果作为function 的第一个参数进行下一次操作。reduce 最后一个参数作为可选参数,将在首先参与运算。

这几个函数的一般用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def funMap(val):
val = val + 1
return val

def funFilter(val):
return val % 2 == 0

def funReduce(x,y):
return x+y

#main==========
#构造数组
lst = map(funMap,range(0,11))
print lst

#过滤出数组中的偶数
lst = filter(funFilter,lst)
print lst

#将数组中的每个数累加(首先加10)
print reduce(funReduce,lst,10)

可以看到,我们需要为每个函数再定义一个函数,作为参数传入。 如果我们使用 lambda, 可以简化这些工作:

1
2
3
4
5
6
7
8
9
10
#构造数组
lst = map(lambda x : x + 1, range(0,11))
print lst

#过滤出数组中的偶数
lst = filter(lambda x : x % 2 == 0, lst)
print lst

#将数组中的每个数累加(首先加10)
print reduce(lambda x,y : x + y, lst, 10)

上面两种写法都输出:

1
2
3
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
[2, 4, 6, 8, 10]
40

但是,显然使用lambda的写法更加优雅。

流程:

  1. 安装 bind9

  2. 配置 domain.zone

  3. 配置 named.conf

  4. 检查配置

  5. 启动服务

安装 bind9

1
apt-get install bind9

非 root 用户请注意使用 sudo

配置domain.zone

选任意一个地方新建一个文件 ,文件名可以为 domain.zone (如 wandoer.com.zone).该文件会被named.conf 引用 。由于 named.conf位于 /etc/bind/ 路径下,为了方便管理,这里在此路径下建立文件夹 zones/ 来管理zone 文件

这里给出 wandoer.com.zone 的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$TTL    604800

@ IN SOA localhost. root.localhost. (
2 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ; Minimum
)

$ORIGIN wandoer.com.

@ IN NS localhost. ;
about IN A 118.186.246.46 ;
photo IN A 118.186.246.46 ;

配置 named.conf

在 /etc/bind/ 路径下有几个 named.conf.* 文件,这里选 named.conf.default-zones 文件进行配置.打开文件,在最后加入以下几行:

1
2
3
4
5
zone "wandoer.com" {
type master;
file "/etc/bind/zones/wandoer.com.zone";
allow-update { none; };
};

检查配置

1
named-checkzone  wandoer.com  /etc/bind/zones/wandoer.com.zone

如果配置无误,则会显示 OK .否则配置提示进行修正。

启动服务

1
service bind9 start

bind 默认将日志放在 /var/log/syslog 中。如果服务启动失败,则可以查看该日志查找原因。

验证DNS

打开 /etc/resolv.conf ,在第一个 nameserver 前再添加一个 nameserver,指向树莓派本机IP:

1
namedserver  192.168.1.100

然后 ping wandoer.com 查看是否已经将此域名解析到指定的IP.

read/write 为什么会被阻塞

首先应该知道的是,当write成功返回时,只是将buf中的数据复制到了缓冲区,至于数据什么时候被发往网络,什么时候被对方主机接收,什么时候被对方进程读取,系统调用层面不会给予任何保证和通知。
当kernel的该socket的发送缓冲区已满时,write就会被阻塞。每个socket都拥有自己的send buffer和receive buffer,其大小由系统自动调节。

1
2
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

已经发送到网络的数据依然需会在send buffer中暂存,只有当收到对方的ack后,kernel才从buffer中将这一部分清除。接收端将收到的数据暂存在receive buffer中,自动进行确认。但如果socket所在的进程来不及时将数据从receive buffer中取出,最终导致receive buffer填满,由于TCP的滑动窗口和拥塞控制,接收端会阻止发送端向其发送数据。这些控制皆发生在TCP/IP栈中,对应用程序是透明的,应用程序继续发送数据,最终导致send buffer填满,write调用阻塞。
一般来说,由于接收端进程从socket读数据的速度跟不上发送端进程向socket写数据的速度,最终导致发送端write调用阻塞。
而read调用的行为则相对容易理解,从socket的receive buffer中拷贝数据到应用程序的buffer中。read调用阻塞,通常是发送端的数据没有到达。

Read more »

C++ 返回的 char* 字符串(utf-8)中,夹杂了一些无效的字符,在移动端引发了一些问题。表现为:在 Android 端,引发了 JNI 异常:

env->NewStringUTF(data):

JNI DETECTED ERROR IN APPLICATION: input is not valid Modified UTF-8: illegal continuation byte 0xe6

而在 iOS 端,则为

[NSString stringWithUTF8String:data]

的返回值为 nil

在 Android 端,这个BUG有人向 google 提交了 issue,暂还没有得在解决。目前的解决办法是多做一步转换,先将 char* 转为 JByteArray,再转成jString:

1
2
3
4
5
6
7
//int size, char* data
jbyteArray array = (*env)->NewByteArray(env, size);
(*env)->SetByteArrayRegion(env, array, 0, size, data);
jstring strEncode = (*env)->NewStringUTF(env, "UTF-8");
jclass cls = (*env)->FindClass(env, "java/lang/String");
jmethodID ctor = (*env)->GetMethodID(env, cls, "<init>", "([BLjava/lang/String;)V");
jstring object = (jstring) (*env)->NewObject(env, cls, ctor, array, strEncode);

而在 iOS端,则可以使用 libiconv 将 UTF-8 字符串转为 GBK,再转为 UTF-8 字符串,转换时,使用 “//IGNORE” 选项忽略无效的字符,即可。

引子

一位朋友在使用函数指针时出现了一个错误:这个函数指针 FP 要求有 4 个参数,而他将一个只有 3 个参数的函数作为 FP 使用,编译和运行都没有报错,但这样造成的运行结果可能是不正确的。下面一个例子来重现这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef int(*FunPt)(int a, int b, int c, int d);

int add(int a, int b, int c)
{
return a + b + c;
}

int main()
{
cout << ((FunPt)(add))(1, 2, 3, 4) << endl;

return 0;
}

如上例,这两个函数的签名不同。 FunPt 要求 4 个参数,而函数 add 只有 3 个参数。虽然在编译和运行时都没有报错,但毫无疑问,返回的结果是错误的。为什么会出现这样的错误呢,这要从函数的调用约定说起。

__cdecl 调用约定

从 C 语言时代开始就有了这个调用约定。它又称 C调用约定,是 C 程序默认的调用约定(现在也是 C++ 程序默认的调用约定)。在这种约定下,函数参数从右向左入栈,函数堆栈由调用者清理,所以它允许函数参数的个数不确定,且它生成的可执行文件大小 会比 __stdcall 函数大。
按C编译方式,_cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。

__stacall 调用约定

它是 Pascal 程序的默认调用方式,所以又称 Pascal 调用约定。和 __decel 一样,参数是从右到左入栈的方式,不同的是堆栈将由被调用函数来清理。
按C编译方式,_stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数

__fastcall 调用约定

它通 CPU 寄存器传递参数,所以调用较快。这也是为什么叫 “fastcall” 的原因。
按C编译方式,_fastcall调用约定在输出函数名前面加“@”符号,后面加“@”符号和参数的字节数

__thiscall 调用约定

它是 C++ 成员函数是默认调用约定。由于成员函数调用还有一个this指针,因此必须特殊处理。如果参数个数确定,this指针通过ecx传递给被调用者,函数自身清理栈;如果参数个数不确定,this指针在所有参数压栈后被压入堆栈,由调用者清理栈。它的参数也是从右向左入栈的。

比较

下面来比较 cdecl 和 stdcall 两种方式的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

int __cdecl cAdd(int a, int b)
{
return a + b;
}

int __stdcall sAdd(int a, int b)
{
return a + b;
}

int main(void)
{
int s1 = cAdd(1, 2);
int s2 = sAdd(3, 4);
return 0;
}

借助 Visual Studio ,我们查看汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main(void)
{
//......
int s1 = cAdd(1, 2);
00FA17BE push 2
00FA17C0 push 1
00FA17C2 call cAdd (0FA1154h)
00FA17C7 add esp,8 ;清理栈
00FA17CA mov dword ptr [s1],eax
int s2 = sAdd(3, 4);
00FA17CD push 4
00FA17CF push 3
00FA17D1 call sAdd (0FA1316h)
00FA17D6 mov dword ptr [s2],eax
return 0;
00FA17D9 xor eax,eax
}

可以看出:main 在调用由 __cdecl 标记的 cAdd 函数后,清理了栈,而调用由 __stdcall标记的 sAdd 函数后,并没有清理栈。

在工作中很多时候我们会使用 GBK 编码来存储数据。但在有些操作中,例如在解析 JSON/XML 或操作数据库时,会因为 GBK 引发一些问题,导致操作失败或引发异常。 这是因为,GBK 中,有些字符会带有 0x5C 的数据。它本是字符串的一部分,但很可能被当作转义符来处理(''),这就使得操作的数据不正确或引发异常。 这些字符是:

乗俓僜刓匼哱圽塡奬媆峔嶾廫慭怽揬昞朶梊榎橽歕沑漒瀄焅燶猏玕琝甛璡痋盶癨瞈砛碶穃竆筡篭糪絓綷縗繺羂耚肻腬臶臷芢蒤薥蚛蝄蟎衆蟎裓覾譢豛赲踈躙輁郳醆鈂鉢鎈鏫閈闬隲頫颸餦馶骪鯸鮘鳿鵟鸤黒齖

除此之外,还有一些特殊符号,如一些制表符,扩充汉字等,详情请戳《gbk-汉字内码扩展规范编码表》下载,表里凡是在 0x5C 位置的字符都在此列。

[

qq%e6%88%aa%e5%9b%be20160919114316

](https://code.wandoer.com/wp-content/uploads/2016/07/QQ截图20160919114316.png)

我们在使用GBK编码时要特别注意这些字符

NDK(Native Debelopment Kit) 是 Android 提供的一个工具集,它允许在 Android app 使用 native 代码(C/CPP). 它集成了交叉编译器, 使用 mk 文件对平台、CPU 等进行隔离。在不同的平台下,只需要配置相应的 mk 文件即可以编译相就的模块。

1 NDK project 基本结构

[

ndkprojectFoloer

](https://code.wandoer.com/wp-content/uploads/2016/07/x.png)

2 Android.mk

Android.mk 文件存在于工程路径中的 jni/ 路径下。它可以看作是 GNUMakefile 的一部分,用来向编译器描述如何从 native 源码构建共享库(动态库)。
Android.mk 可以将源码分成不同的模块。这些 模块 可以是静态库、共享库(甚至是可执行文件)。静态库用来生成共享库,共享库则会和 app 打包。

Read more »

在上一章(《多语言编程之 C and Java:JNI 30 分钟入门》)中讲解了 JNI 的基本使用。这一章将讨论一些更加实用的用法:如何在 JNI 中访问 Java 对象的成员变量或静态变量,以及回调 Java 中的成员方法或静态方法。

1 JNI 访问 Java 成员变量

1.1 实例

Java 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package transvar;
public class TransVar{
static{
System.loadLibrary("hello");
}

//成员变量(字段)
private int age = 88;
private String name = "Jims";

//静态成员变量
private static int years = 20;

private native void modifyInstanceVariable();
}

这个 Java 类中包含 3 个私有成员变量,名为 age 的 int 型变量, 名为 name 的 String 型变量和一个名为 years 的 int 型静态变量。我们将在使用 C 来访问或修改这些成员变量。

C 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
JNIEXPORT void JNICALL Java_transvar_TransVar_modifyInstanceVariable
(JNIEnv *env, jobject obj)
{
//获取 this object 的类
jclass thisClass = (*env)->GetObjectClass(env,obj);
if(thisClass == NULL)
{
printf("thisClass null\n");
}

//获取字段ID
jfieldID fidAge = (*env)->GetFieldID(env, thisClass, "age", "I");
jfieldID fidName = (*env)->GetFieldID(env, thisClass, "name", "Ljava/lang/String;");
jfieldID fidYear = (*env)->GetStaticFieldID(env, thisClass, "years", "I");

//获取字段
jint age = (*env)->GetIntField(env, obj, fidAge);
jint years = (*env)->GetStaticIntField(env, obj, fidYear);
jstring name = (*env)->GetObjectField(env, obj, fidName);
const char* pszName = (*env)->GetStringUTFChars(env, name, NULL);

printf("[%d Years Later for %s] \n", years, pszName);

(*env)->ReleaseStringUTFChars(env, name, pszName);

age += years;

//修改成员变量
(*env)->SetIntField(env, obj, fidAge, age);
}

1.2 访问 Java 变量

在 JNI 中我们获取了这 3 个变量,并对 age 变量进行了修改。
由这段代码可以看出,访问 Java 成员变量的一般步骤为:

  1. 通过 GetObjectClass() 获取 Java 类的引用。

  2. 通过 GetFieldID()/GetStaticFieldID() 从 Java 类中获取 FieldID 。

  3. 基于 FieldID, 通过 GetField()/GetStaticField() 取回变量。

  4. 基于 FieldID, 通过 SetField()/GetStaticField() 修改变量。

GetFieldID()/GetStaticFieldID() 原型如下:

1
2
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
Read more »

1 引言

1.1 简介

在实际项目中,很多时候是以多种语言混合开发的。常用的模式是使用 C/C++ 做底层支撑,然后上层辅之以高级语言。 JNI可以使 native 代码与 Java 对象交互,而不会像 Java 代码中的功能那样受到诸多限制, 往往充作 Java 与 C/C++/Assembly 语言的粘合剂。

JNI 明确分开了 Java 代码与 native 代码的执行,为两者的通信定义了一套清晰的API.避免 native 代码对 JVM 的直接内存引用。

2 JNI 入门

2.1 JNI 调用 C

2.1.1 java 中的 native 方法声明

HelloJNI.java

1
2
3
4
5
6
7
8
9
10
11
public class HelloJNI{
static{
System.loadLibrary("hello"); //运行时加载本地库 hello.dll(Windows)或libhello.so(Unixes) 或 libhello.dylib(MacOX)
}

private native void sayHello(); //声明 native 方法

public static void main(String[] args ){
new HelloJNI().sayHello(); //调用本地方法
}
}

当HelloJNI类加载的时候,静态初始化调用 System.loadLibrary() 来加载本地库 hello.dll(或libhello.so)。如果不能加载 hello 库,会抛出 UnsatisfiedLinkError 异常。
然后我们使用 native 关键字声明了 native 方法 sayHello() ,这表明该方法是使用其它语言实现的。
最后 main() 方法实例化了 HelloJNI 并调用了 sayHello().
然后我们编译这个类:

1
> javac HelloJNI.java

2.1.2 生成 C/C++ 头文件

使用 javah 创建头文件:

1
> javah HelloJNI

会得到如下 .h 文件 HelloJNI.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloJNI
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

这份自动生成的头文件里声明一个 C 函数 Java_HelloJNI_sayHello:

1
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

这个方法名是由javah转换来的,转换规则为:

1
Java_{package_and_classname}_{function_name}(JNI arguments)

包名中的 点 会被转换为 下划线
函数中带有两个参数(虽然我们在本例中并没有使用这两个参数)

  • JNIEnv*: 指向 JNI environment ,提供对 JNI 方法的访问

  • jobject: 指向 this Java 对象

2.1.3 C 代码的实现

HelloJNI.c

1
2
3
4
5
6
7
8
#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"

JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv * env, jobject obj)
{
printf("Hello World!");
}

这个实现将在控制台打印万能的 “Hello World!”
文件中包含了 jni.h 头文件。这个头文件由 java 提供,位于 %JAVA_HOME%/include 路径下(Windows下还需要引用 %JAVA_HOME%/include/win32).

2.1.4 编译 C 库

接下来,编译这个 c 文件:

1
2
3
4
5
6
#Windows gcc
> gcc -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o hello.dll HelloJNI.c

#Windows VS cl.exe link.exe
> cl /c /I%JAVA_HOME%/include /I%JAVA_HOME%/include/win32 HelloJNI.c
> link HelloJNI.obj /DLL /OUT:hello.dll

2.1.5 检查成果

Read more »
0%