网络协议详解之 HTTP 协议

2016/11/20

概述

HTTP(HyperText Transfer Protocal)超文本传输协议, 是一个基于请求与响应模式的、无状态的应用层协议。它是 WEB 上应用最广泛的协议。它一般基于 TCP 的连接方式。其主要特点有:

  • 支持 C/S 通信模式
  • 简单快速。HTTP 协议简单,使得 HTTP 服务器的程序规模小,因而通信快。
  • 灵活。HTTP 协议允许客户端和服务端传输任意类型任意格式的数据。不同的类型由 Content-Tyoe 标记
  • 面向无连接。无连接是指每次建立的连接只处理一个文请求。
  • 无状态。无状态是指协议对于事务处理没有记忆能力。如果后续处理需要前面的信息,则它必须重传。这样可有导致每次连接传送的数据量增大。

HTTP URL

URL(Uniform Resource Locator) 统一资源定位符,它包含了查找某个资源的信息。其格式如下:

http 指定协议的名称,表示要通过 HTTP 协议来定位网络资源。 host 是一个合法的网络域名 或 IP 地址。 port 指定使用的网络端口,缺省值为 80 。 abs_path 指定请求的资源的 URI .如果 URL 中没有给出 URI, 则必须以 "/" 符号结束(这个工作通常由浏览器完成)。

连接

浏览器与服务器联系的最常用方法是与服务器的 80 端口建立 TCP 连接。使用 TCP 的意义在于,浏览器和服务器都不需要担心如何处理长消息、可靠性与拥塞控制,这些事将由 TCP 来处理。 在早期的 HTTP1.0 中,连接建立起来后会在一个请求和一个响应后立即释放。因为那时的 HTML 很简单,基本只有文本,使用这种模式就够了。但是随着时代的发展,HTML里包含了太多的东西,使用单独的 TCP 来传递每个资源代价太大。于是 HTTP1.1诞生了,它支持持续连接(persistent connection),它可以在一个 TCP 连接上进行多次请求响应,还可以发送流水线请求。这种做法减少建立多个 TCP 连接所用的时间,减少服务器的空闲时间,提高了性能。

HTTP 协议的请求

每个 HTTP Request (请求) 由一行或多行 ASCII 文本组成。其中第一行的第一个词为请求方法的名称,然后是请求资源的 URI,再后是协议的版本。这几个部分使用空格分开:

Method 为请求方法,必须为大写。 Request-URI 是一个统一资源标识符。  HTTP-Version 表示请求的 HTTP 协议版本。 CRLF 表示换行符。 例如:

常用的请求方法如下: 继续阅读

libcurl 使用笔记

2016/11/17

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 简单示例

2 curl_easy API 介绍

继续阅读

Note , 6,533

lambda 表达式 (in Python)

2016/11/16

概述lambda

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

  • lambda 只是一个表达式,而不是一个代码块。类似于 C++ 中的 define ,但比 define 要简单.Python 中的 lambda 只能封装有限的逻辑进去。
  • lambda 拥有自己的全名空间,且不能访问自有参数列表之外及全局的参数。

语法

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

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

以上输出:

在 python 中的使用

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

  • map : 对迭代器招待 function 操作,并将操作后的 list 返回
  • filter : function 应该返回一个布尔值,对迭代器操作为 False 的,将从 List 里排除
  • reduce : 对迭代器进行累计操作,即将上一次操作的结果作为function 的第一个参数进行下一次操作。reduce 最后一个参数作为可选参数,将在首先参与运算。

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

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

上面两种写法都输出:

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

树莓派(Debian)配置 DNS 服务

2016/11/14

流程:

  1. 安装 bind9
  2. 配置 domain.zone
  3. 配置 named.conf
  4. 检查配置
  5. 启动服务

安装 bind9

非 root 用户请注意使用 sudo

配置domain.zone

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

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

 

配置 named.conf

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

检查配置

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

启动服务

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

验证DNS

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

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

Note 6,670

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 , 6,631

无效的 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 11,046

函数调用约定(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 6,603

GBK 字符中的转义符陷阱

2016/07/19

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

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

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

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

 

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

 

Note 6,186