TCP/IP中write/read的行为

2016/10/12

read/write 为什么会被阻塞

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

已经发送到网络的数据依然需会在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调用阻塞,通常是发送端的数据没有到达。 阅读全文…

Note , 884

无效的 UTF-8 字符串在移动端引发的问题

2016/09/27

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:

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

Note 1,478

函数调用约定(stdcall cdecl thiscall fastcall)

2016/08/31

引子

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

如上例,这两个函数的签名不同。 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 两种方式的代码:

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

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

Note 1,007

GBK 字符中的转义符陷阱

2016/07/19

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

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

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

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

 

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

 

Note 825

多语言编程之 C and Java: Android NDK

2016/07/11

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

1 NDK project 基本结构

ndkprojectFoloer

 

 

 

 

 

2 Android.mk

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

阅读全文…

多语言编程之 C and Java:JNI进阶–字段访问与方法回调

2016/07/09

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

1 JNI 访问 Java 成员变量

1.1 实例

Java 实现

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

C 实现

1.2 访问 Java 变量

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

  1. 通过 GetObjectClass() 获取 Java 类的引用。
  2. 通过 GetFieldID()/GetStaticFieldID() 从 Java 类中获取 FieldID 。
  3. 基于 FieldID, 通过 Get<type>Field()/GetStatic<type>Field() 取回变量。
  4. 基于 FieldID, 通过 Set<type>Field()/GetStatic<type>Field() 修改变量。

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

阅读全文…