学习TCP/IP 协议

TCP/IP 协议(栈) 是什么

TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议)是指能够在多个不同网络间实现信息传输的协议簇。TCP/IP协议不仅仅指的是TCP 和IP两个协议,而是指一个由FTP、SMTP、TCP、UDP、IP等协议构成的协议簇, 只是因为在TCP/IP协议中TCP协议和IP协议最具代表性,所以被称为TCP/IP协议。

计算机网络体系结构分层

TCP/IP协议在一定程度上参考了OSI的体系结构。OSI模型共有七层,从下到上分别是物理层、数据链路层、网络层、运输层、会话层、表示层和应用层。但是这显然是有些复杂的,所以在TCP/IP协议中,它们被简化为了四个层次。

计算机网络体系结构分层

OSI 七层模型目前只是一个模型,目前实际网络应用的是TCP/IP 四层模型。

缺陷

像OSI模型一样,TCP/IP模型和协议也有自己的问题。

  • (1)该模型没有明显地区分服务、接口和协议的概念。因此,对于使用新技术来设计新网络,TCP/IP模型不是一个太好的模板。
  • (2)TCP/IP模型完全不是通用的,并且不适合描述除TCP/IP模型之外的任何协议栈。
  • (3)链路层并不是通常意义上的一层。它是一个接口,处于网络层和数据链路层之间。接口和层间的区别是很重要的。
  • (4)TCP/IP模型不区分物理层和数据链路层。这两层完全不同,物理层必须处理铜缆、光纤和无线通信的传输特征;而数据链路层的工作是确定帧的开始和结束,并且按照所需的可靠程度把帧从一端发送到另一端。

两台计算机通过TCP/IP协议通讯的过程如下所示

tcpip

TCP/IP数据包的封装

不同的协议层对数据包有不同的称谓,在传输层叫做段(segment),在网络层叫做数据报(datagram),在链路层叫做帧(frame)。数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,最后将应用层数据交给应用程序处理。

tcpip.datagram.png

以太网 格式

tcpip.ethernetformat.png

ARP格式

2016-04-12_570cbf887a6d7.png

IP 格式

ipformat

TCP 格式

tcpformat

TCP 可选项

other choose item

TCP状态流转图

TCP

各种状态表示的意思

  • CLOSED:表示初始状态
  • LISTEN:表示服务器端的某个 socket 处于监听状态,可以接受连接
  • SYN_SENT:在服务端监听后,客户端 socket 执行 CONNECT 连接时,客户端发送 SYN 报文,此时客户端就进入 SYN_SENT 状态,等待服务端确认。
  • SYN_RCVD:表示服务端接收到了 SYN 报文。
  • ESTABLISHED:表示连接已经建立了。
  • FIN_WAIT_1:其中一方请求终止连接,等待对方的 FIN 报文。
  • FIN_WAIT_2:在 FIN_WAIT_2 之后, 当对方回应 ACK 报文之后,进入该状态。
  • TIME_WAIT:表示收到了对方的 FIN 报文,并发送出了 ACK 报文,就等 2MSL 之后即可回到 CLOSED 状态。
  • CLOSING:一种罕见状态,发生在发送 FIN 报文之后,本应是先收到 ACK 报文,却先收到对方的 FIN 报文,那么就从 FIN_WAIT_1 的状态进入 CLOSING 状态。
  • CLOSE_WAIT:表示等待关闭,在 ESTABLISHED 过渡到 LAST_ACK 的一个过渡阶段,该阶段需要考虑是否还有数据发送给对方,如果没有,就可以关闭连接,发送 FIN 报文,然后进入 LAST_ACK 状态。
  • LAST_ACK:被动关闭一方发送 FIN 报文之后,最后等待对方的 ACK 报文所处的状态。
  • CLOSED:当收到 ACK 保温后,就可以进入 CLOSED 状态了。
TCP时序图

在这个例子中,首先客户端主动发起连接、发送请求,然后服务器端响应请求,然后客户端主动关闭连接。两条竖线表示通讯的两端,从上到下表示时间的先后顺序,注意,数据从一端传到网络的另一端也需要时间,所以图中的箭头都是斜的。双方发送的段按时间顺序编号为1-10,各段中的主要信息在箭头上标出,例如段2的箭头上标着SYN, 8000(0), ACK 1001, <mss 1024>,表示该段中的SYN位置1,32位序号是8000,该段不携带有效载荷(数据字节数为0),ACK位置1,32位确认序号是1001,带有一个mss选项值为1024。

建立连接的过程:

客户端发出段1,SYN位表示连接请求。序号是1000,这个序号在网络通讯中用作临时的地址,每发一个数据字节,这个序号要加1,这样在接收端可以根据序号排出数据包的正确顺序,也可以发现丢包的情况,另外,规定SYN位和FIN位也要占一个序号,这次虽然没发数据,但是由于发了SYN位,因此下次再发送应该用序号1001。mss表示最大段尺寸,如果一个段太大,封装成帧后超过了链路层的最大帧长度,就必须在IP层分片,为了避免这种情况,客户端声明自己的最大段尺寸,建议服务器端发来的段不要超过这个长度。

服务器发出段2,也带有SYN位,同时置ACK位表示确认,确认序号是1001,表示“我接收到序号1000及其以前所有的段,请你下次发送序号为1001的段”,也就是应答了客户端的连接请求,同时也给客户端发出一个连接请求,同时声明最大尺寸为1024。

客户端发出段3,对服务器的连接请求进行应答,确认序号是8001。

在这个过程中,客户端和服务器分别给对方发了连接请求,也应答了对方的连接请求,其中服务器的请求和应答在一个段中发出,因此一共有三个段用于建立连接,称为’‘三方握手(three-way-handshake)’‘。在建立连接的同时,双方协商了一些信息,例如双方发送序号的初始值、最大段尺寸等。

tcpip.tcpconnection.png

UDP 格式

udpformat

跨路由器通讯过程

transferovernet

Multiplexing过程

注意,虽然IP、ARP和RARP数据报都需要以太网驱动程序来封装成帧,但是从功能上划分,ARP和RARP属于链路层,IP属于网络层。虽然ICMP、IGMP、TCP、UDP的数据都需要IP协议来封装成数据报,但是从功能上划分,ICMP、IGMP与IP同属于网络层,TCP和UDP属于传输层。

multiplexing

为什么学习 TCP/IP 协议

很多公司招聘JD上,写着网络编程经验,说明实际工作中还是有很大的应用市场(应用:微信、QQ, 领域:IM、车联网、服务治理)。学会了它,可以非常轻松了解,浏览器输入URL Enter背后发生了什么。有了这个基础可以更好的学习Socket编程等等等。

腾讯JD

如何学习 TCP/IP

Redis

我们利用tcpdump工具抓包来分析实际网络请求数据。

本地安装Redis

step 0: 查看本地的网卡list

$ tcpdump -D
1.en0 [Up, Running]
2.p2p0 [Up, Running]
3.awdl0 [Up, Running]
4.llw0 [Up, Running]
5.utun0 [Up, Running]
6.utun1 [Up, Running]
7.utun2 [Up, Running]
8.utun3 [Up, Running]
9.utun4 [Up, Running]
10.lo0 [Up, Running, Loopback]
11.bridge0 [Up, Running]
12.en1 [Up, Running]
13.en2 [Up, Running]
14.gif0 [none]
15.stf0 [none]

step 1: 开启抓包工具,命令

tcpdump -w /tmp/logs -i lo0 port 6379 -s0

-i lo0 抓取回路网口

port 监听redis的端口

-s0 防止包截断

step 2: 开启Redis-cli 发送ping命令

$ redis-cli
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>

step 3: 停止抓包,用Vim打开

tcpdump -r /tmp/logs -n -nn -A -x| vim -

-x 以16进制形式展示,便于后面分析

首先分析一下我们的ping命令

报文数据中的 [、]、{ 和 } 是为了方便区分数据,我自己加上的。[]包围的部分为本报文中的 IP 头,{}包围的部分为本报文中的 TCP 头。

    09:18:06.471045 IP 127.0.0.1.50510 > 127.0.0.1.6379: Flags [P.], seq 18:32, ack 11469, win 6200, options [nop,nop,TS val 192620081 ecr 192618164], length 14: RESP "ping" //请求的数据
        0x0000:  [4500 0042 0000 4000 4006 0000 7f00 0001
        0x0010:  7f00 0001]{c54e 18eb 7c1d d1ad 9576 97dc
        0x0020:  8018 1838 fe36 0000 0101 080a 0b7b 2631
        0x0030:  0b7b 1eb4}2a31 0d0a 2434 0d0a 7069 6e67
        0x0040:  0d0a

IP 层
a) 0x4 4bit, ip 协议版本 0x4 表示 IPv4。
b) 0x5 4bit,ip首部长度
c) 0x00 8bit,服务类型 TOS 现在大多数的TCP/IP实现都不支持TOS特性 。可以看到,本报文 TOS 字段为全 0
d) 0x0042 16bit, IP 报文总长度 单位字节,换算下来,该数据报的长度为 66 字节,数一下上面的报文,恰好 66B。
从占位数来算, IP 数据报最长为 2^16=65535B,但大部分网络的链路层 MTU(最大传输单元)没有这么大,一些上层协议或主机也不会接受这么大的,故超长 IP 数据报在传输时会被分片
e) 0x0000 16bit,标识  唯一的标识主机发送的每一个数据报。通常每发送一个报文,它的值+1。当 IP 报文分片时,该标识字段值被复制到所有数据分片的标识字段中,使得这些分片在达到最终目的地时可以依照标识字段的内容重新组成原先的数据。
f) 0x4000 3bit 标志 + 13bit 片偏移 3bit 标志对应 R、DF、MF。目前只有后两位有效,DF位:为1表示不分片,为0表示分片。MF:为1表示“更多的片”,为0表示这是最后一片。
13bit 片位移:本分片在原先数据报文中相对首位的偏移位。(需要再乘以8)
g) 0x40 8bit 生存时间TTL IP 报文所允许通过的路由器的最大数量。每经过一个路由器,TTL减1,当为 0 时,路由器将该数据报丢弃。TTL 字段是由发送端初始设置一个 8 bit字段.推荐的初始值由分配数字 RFC 指定。发送 ICMP 回显应答时经常把 TTL 设为最大值 255。TTL可以防止数据报陷入路由循环。
h) 0x06 8bit 协议 指出 IP 报文携带的数据使用的是哪种协议,以便目的主机的IP层能知道要将数据报上交到哪个进程。TCP 的协议号为6,UDP 的协议号为17。ICMP 的协议号为1,IGMP 的协议号为2。该 IP 报文携带的数据使用 TCP 协议,得到了验证。
i) 0x0000 16bit IP 首部校验和
j) 0x7f000001 32bit 源地址
k) 0x7f000001 32bit 目的地址

TCP 层
a) 0xc54e  16bit,源端口
b) 0x11eb 16bit,目的端口
c) 0x7c1dd1ad 32bit,序号
d) 0x957697dc 32bit,确认号
e) 0x8 4bit,TCP 报文首部长度  也叫 offset,其实也就是数据从哪里开始。8 * 4 = 32B,因此该 TCP 报文的可选部分长度为 32 - 20 = 12B,这个资源还是很紧张的! 同 IP 头部类似,最大长度为 60B。
f) 0b000000 6bit, 保留位 保留为今后使用,但目前应置为 0。
g) 0b011000 6bit,TCP 标志位 上图可以看到,从左到右依次是紧急 URG、确认 ACK、推送 PSH、复位 RST、同步 SYN 、终止 FIN。 从抓包可以看出,该报文是带了 ack 的,所以 ACK 标志位置为 1。
h) 0x1838 16bit,滑动窗口大小 解析得到十进制 6200,跟 tcpdump 解析的 win 字段一致。
i) 0xfe36 16bit,校验和 由发送端填充,接收端对 TCP 报文段执行 CRC 算法,以检验 TCP 报文段在传输过程中是否损坏,如果损坏这丢弃。 检验范围包括首部和数据两部分,这也是 TCP 可靠传输的一个重要保障
j) 0x0000 16bit,紧急指针 仅在 URG = 1 时才有意义,它指出本报文段中的紧急数据的字节数。 当 URG = 1 时,发送方 TCP 就把紧急数据插入到本报文段数据的最前面,而在紧急数据后面的数据仍是普通数据。

TCP可选项
a) 0x01 NOP 填充,没有 Length 和 Value 字段, 用于将TCP Header的长度补齐至 32bit 的倍数
b) 0x01 同上。
c) 0x080a 可选项类型为时间戳,len为 10B,value 为0x0b7b 0x2631 0x0b7b 0x1eb4,加上 0x080a,恰好 10B!
启用 Timestamp Option后,该字段包含2 个 32bit 的Timestamp(TS val 和 TS ecr)。
d) 0x0b7b 0x2631 解析后得到 192620081,恰好与 tcpdump 解析到的 TS 字段的 val一致!
e) 0x0b7b 0x1eb4 解析后得到 192618164,恰好与 tcpdump 解析到的 TS 字段的 ecr一致!

该 IP 报文长度为 66B,IP 头长度为 20B,TCP 头部长度为 32B,因此得到数据的长度为 66 - 20 - 32 = 14B,这与 tcpdump 解析到的 length 字段一致

ascii 表 (man 7 ascii)

0x2a31         -> *1
0x0d0a         -> \r\n
0x2434         -> $4
0x0d0a         -> \r\n
0x7069 0x6e67  -> ping
0x0d0a         -> \r\n

    09:18:06.471069 IP 127.0.0.1.6379 > 127.0.0.1.50510: Flags [.], ack 32, win 6379, options [nop,nop,TS val 192620081 ecr 192620081], length 0
        0x0000:  4500 0034 0000 4000 4006 0000 7f00 0001
        0x0010:  7f00 0001 18eb c54e 9576 97dc 7c1d d1bb
        0x0020:  8010 18eb fe28 0000 0101 080a 0b7b 2631
        0x0030:  0b7b 2631
    09:18:06.471115 IP 127.0.0.1.6379 > 127.0.0.1.50510: Flags [P.], seq 11469:11476, ack 32, win 6379, options [nop,nop,TS val 192620081 ecr 192620081], length 7: RESP "PONG" // 返回的数据
        0x0000:  4500 003b 0000 4000 4006 0000 7f00 0001
        0x0010:  7f00 0001 18eb c54e 9576 97dc 7c1d d1bb
        0x0020:  8018 18eb fe2f 0000 0101 080a 0b7b 2631
        0x0030:  0b7b 2631 2b50 4f4e 470d 0a
    09:18:06.471132 IP 127.0.0.1.50510 > 127.0.0.1.6379: Flags [.], ack 11476, win 6200, options [nop,nop,TS val 192620081 ecr 192620081], length 0
        0x0000:  4500 0034 0000 4000 4006 0000 7f00 0001
        0x0010:  7f00 0001 c54e 18eb 7c1d d1bb 9576 97e3
        0x0020:  8010 1838 fe28 0000 0101 080a 0b7b 2631
        0x0030:  0b7b 2631

step4: tcpdump 补充 filter可以简单地分为三类:type, dir 和 proto。

  • type 区分报文的类型,主要由 host(主机), net(网络,支持 CIDR) 和 port(支持范围,如 portrange 21-23) 组成。
  • dir 区分方向,主要由 src 和 dst 组成。
  • proto 区分协议支持 tcp、udp 、icmp 等。

下面说几个 filter 表达式。

proto[x:y] start at offset x into the proto header and read y bytes

[x] abbreviation for [x:1]

注意:单位是字节,不是位!

打印特定 TCP Flag 的数据包

TCP Flags 在 tcpdump 抓取的报文中的体现:
[S]:SYN(开始连接)
[.]: 没有 Flag
[P]: PSH(推送数据)
[F]: FIN (结束连接)
[R]: RST(重置连接)
[S.] SYN-ACK,就是 SYN 报文的应答报文。

tcpdump 'tcp[13] & 16!=0'
# 等价于
tcpdump 'tcp[tcpflags] == tcp-ack'

HTTP Server (三次握手,四次挥手)

简单的搭建一个http server

[root@0384b25d7893 ~]# cat nchttp.sh
PORT=$1
while true; do ( echo "HTTP/1.0 200 Ok"; echo; echo "TEST TASK" ) | nc -l $PORT; done

启动脚本

[root@0384b25d7893 ~]# sh nchttp.sh 8000

检查端口8000 一个IPV4, 一个IPV6 监听文件描述符

[root@0384b25d7893 myphp]# netstat -nalp |grep 8000
tcp        0      0 0.0.0.0:8000            0.0.0.0:*               LISTEN      8832/nc
tcp6       0      0 :::8000                 :::*                    LISTEN      8832/nc
[root@0384b25d7893 myphp]#

运行curl 请求

[root@0384b25d7893 myphp]# curl http://localhost:8000/?a=b

服务端连接断开(过2MSL 周期自动断开)

[root@0384b25d7893 myphp]# netstat -nalp |grep 8000
tcp        0      0 0.0.0.0:8000            0.0.0.0:*               LISTEN      8844/nc
tcp        0      0 127.0.0.1:8000          127.0.0.1:36512         TIME_WAIT   -
tcp6       0      0 :::8000                 :::*                    LISTEN      8844/nc

tcpdump 抓到的数据包

tcpdump package

数据解读

  • [S] (SYN)
  • [S.] (SYN ACK)
  • [F] (FIN)
  • [F.] (FIN ACK)
  • [P.] (PUSH ACK)

第一列 用于标识行

1 06:38:46.052767 IP localhost.36258 > localhost.irdmi: Flags [S], seq 3125423480, win 43690, options [mss 65495,sackOK,TS val 3395569 ecr 0,nop,wscale 7], length 0
2 05:38:46.052809 IP localhost.irdmi > localhost.36258: Flags [S.], seq 4241729365, ack 3125423481, win 43690, options [mss 65495,sackOK,TS val 3395569 ecr 3395569,nop,wscale 7], length 0
3 05:38:46.052847 IP localhost.36258 > localhost.irdmi: Flags [.], ack 1, win 342, options [nop,nop,TS val 3395569 ecr 3395569], length 0

4 05:38:46.053017 IP localhost.irdmi > localhost.36258: Flags [P.], seq 1:28, ack 1, win 342, options [nop,nop,TS val 3395570 ecr 3395569], length 27

5 05:38:46.053056 IP localhost.irdmi > localhost.36258: Flags [F.], seq 28, ack 1, win 342, options [nop,nop,TS val 3395570 ecr 3395569], length 0

6 05:38:46.054869 IP localhost.36258 > localhost.irdmi: Flags [.], ack 28, win 342, options [nop,nop,TS val 3395570 ecr 3395570], length 0
7 05:38:46.055778 IP localhost.36258 > localhost.irdmi: Flags [P.], seq 1:83, ack 29, win 342, options [nop,nop,TS val 3395570 ecr 3395570], length 82

8 05:38:46.055870 IP localhost.irdmi > localhost.36258: Flags [.], ack 83, win 342, options [nop,nop,TS val 3395570 ecr 3395570], length 0
9 05:38:46.058091 IP localhost.36258 > localhost.irdmi: Flags [F.], seq 83, ack 29, win 342, options [nop,nop,TS val 3395570 ecr 3395570], length 0
10 05:38:46.058153 IP localhost.irdmi > localhost.36258: Flags [.], ack 84, win 342, options [nop,nop,TS val 3395570 ecr 3395570], length 0

localhost.36258(定为客户端) localhost.irdmi(定为服务端)

三次握手

Line 1 客户端发起连接请求 Flags[S] seq 3125423480 win 43690

Line 2 服务端发起连接请求,并应答客户端连接请求 Flags[S.] seq 4241729365 ack 3125423481 (Line 1 seq + 1 = ack) win 43690

Line 3 客户端应答服务端连接请求 Flags[.] ack 1 win 342

四次挥手

Line 5 服务端主动关闭连接 Flags[F.]

Line 6-8 传输数据 服务端不在主动发数据,等待客户端ack确认

Line 9 客户端关闭连接 Flags[F.] 确认服务端ACK

Line 10 服务端给客户但发送 ACK

实际应用场景

参考

supervisor 运行的PHP脚本假死排查

supervisor status

[ ~]$ sudo supervisorctl -c /etc/supervisord.conf status
....bigdata-lodgeunit-state_01   RUNNING   pid 27801, uptime 47 days, 0:50:00
....prepare_09                   RUNNING   pid 5159, uptime 20 days, 11:13:15

strace 命令查看一下系统调用情况

[]$ strace -p 27801
Process 27801 attached
restart_syscall(<... resuming interrupted call ...>) = 0
rt_sigaction(SIGPIPE, NULL, {SIG_IGN, [PIPE], SA_RESTORER|SA_RESTART, 0x7ffff4a56670}, 8) = 0
rt_sigaction(SIGPIPE, {SIG_IGN, [PIPE], SA_RESTORER|SA_RESTART, 0x7ffff4a56670}, NULL, 8) = 0
poll([{fd=19, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 1, 0) = 0 (Timeout)
rt_sigaction(SIGPIPE, {SIG_IGN, [PIPE], SA_RESTORER|SA_RESTART, 0x7ffff4a56670}, NULL, 8) = 0
poll([{fd=19, events=POLLIN}], 1, 1000) = 0 (Timeout)
rt_sigaction(SIGPIPE, NULL, {SIG_IGN, [PIPE], SA_RESTORER|SA_RESTART, 0x7ffff4a56670}, 8) = 0
rt_sigaction(SIGPIPE, {SIG_IGN, [PIPE], SA_RESTORER|SA_RESTART, 0x7ffff4a56670}, NULL, 8) = 0
poll([{fd=19, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 1, 0) = 0 (Timeout)
rt_sigaction(SIGPIPE, {SIG_IGN, [PIPE], SA_RESTORER|SA_RESTART, 0x7ffff4a56670}, NULL, 8) = 0
poll([{fd=19, events=POLLIN}], 1, 1000) = 0 (Timeout)
rt_sigaction(SIGPIPE, NULL, {SIG_IGN, [PIPE], SA_RESTORER|SA_RESTART, 0x7ffff4a56670}, 8) = 0
rt_sigaction(SIGPIPE, {SIG_IGN, [PIPE], SA_RESTORER|SA_RESTART, 0x7ffff4a56670}, NULL, 8) = 0
poll([{fd=19, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 1, 0) = 0 (Timeout)
rt_sigaction(SIGPIPE, {SIG_IGN, [PIPE], SA_RESTORER|SA_RESTART, 0x7ffff4a56670}, NULL, 8) = 0
poll([{fd=19, events=POLLIN}], 1, 1000) = 0 (Timeout)
rt_sigaction(SIGPIPE, NULL, {SIG_IGN, [PIPE], SA_RESTORER|SA_RESTART, 0x7ffff4a56670}, 8) = 0
rt_sigaction(SIGPIPE, {SIG_IGN, [PIPE], SA_RESTORER|SA_RESTART, 0x7ffff4a56670}, NULL, 8) = 0
poll([{fd=19, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 1, 0) = 0 (Timeout)
rt_sigaction(SIGPIPE, {SIG_IGN, [PIPE], SA_RESTORER|SA_RESTART, 0x7ffff4a56670}, NULL, 8) = 0
poll([{fd=19, events=POLLIN}], 1, 1000^CProcess 27801 detached
 <detached ...>
 )

lsof 查看一下文件下运行的服务(socket)

[]$ lsof -d 19 |grep 27801
php     27801  www   19u     IPv4          430131931      0t0       TCP localhost:9411->localhost:9411 (ESTABLISHED)

gdb查看一下


[]$ gdb -p 27801
.....
.....
Loaded symbols for /lib64/libnss_dns.so.2
0x00007ffff4b0cbb0 in __poll_nocancel () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install cyrus-sasl-lib-2.1.26-19.2.el7.x86_64 freetype-2.4.11-11.el7.x86_64 glibc-2.17-105.el7.x86_64 gmp-6.0.0-11.el7.x86_64 keyutils-libs-1.5.8-3.el7.x86_64 krb5-libs-1.13.2-10.el7.x86_64 libcom_err-1.42.9-7.el7.x86_64 libgcc-4.8.5-4.el7.x86_64 libjpeg-turbo-1.2.90-5.el7.x86_64 libpng-1.5.13-5.el7.x86_64 libselinux-2.2.2-6.el7.x86_64 libstdc++-4.8.5-4.el7.x86_64 libxml2-2.9.1-5.el7_1.2.x86_64 nspr-4.10.8-2.el7_1.x86_64 nss-3.19.1-18.el7.x86_64 nss-softokn-freebl-3.16.2.3-13.el7_1.x86_64 nss-util-3.19.1-4.el7_1.x86_64 openldap-2.4.40-8.el7.x86_64 openssl-libs-1.0.1e-42.el7.9.x86_64 pcre-8.32-15.el7.x86_64 xz-libs-5.1.2-12alpha.el7.x86_64 zlib-1.2.7-15.el7.x86_64
(gdb) bt
#0  0x00007ffff4b0cbb0 in __poll_nocancel () from /lib64/libc.so.6
#1  0x00007ffff5db3d49 in Curl_poll () from /usr/local/xzsoft/curl/lib/libcurl.so.4
#2  0x00007ffff5daf0c0 in curl_multi_wait () from /usr/local/xzsoft/curl/lib/libcurl.so.4
#3  0x00007ffff5da942c in curl_easy_perform () from /usr/local/xzsoft/curl/lib/libcurl.so.4
#4  0x0000000000585b49 in zif_curl_exec (execute_data=<optimized out>, return_value=0x7ffff2a14ed0) at /home/xzapps/zhaopeng/php-7.1.15/ext/curl/interface.c:3043
#5  0x000000000083ea80 in ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER () at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:1099
#6  0x00000000007edd9b in execute_ex (ex=<optimized out>) at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:429
#7  0x000000000083ed95 in ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER () at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:949
#8  0x00000000007edd9b in execute_ex (ex=<optimized out>) at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:429
#9  0x000000000083e915 in ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER () at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:1076
#10 0x00000000007edd9b in execute_ex (ex=<optimized out>) at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:429
#11 0x000000000083ed95 in ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER () at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:949
#12 0x00000000007edd9b in execute_ex (ex=<optimized out>) at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:429
#13 0x000000000083ed95 in ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER () at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:949
#14 0x00000000007edd9b in execute_ex (ex=<optimized out>) at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:429
#15 0x000000000083e915 in ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER () at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:1076
#16 0x00000000007edd9b in execute_ex (ex=<optimized out>) at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:429
#17 0x000000000083e915 in ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER () at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:1076
#18 0x00000000007edd9b in execute_ex (ex=<optimized out>) at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:429
#19 0x000000000083e915 in ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER () at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:1076
#20 0x00000000007edd9b in execute_ex (ex=<optimized out>) at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:429
#21 0x000000000083e915 in ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER () at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:1076
#22 0x00000000007edd9b in execute_ex (ex=<optimized out>) at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:429
#23 0x000000000083e915 in ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER () at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:1076
#24 0x00000000007edd9b in execute_ex (ex=<optimized out>) at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:429
#25 0x000000000083dccf in ZEND_CALL_TRAMPOLINE_SPEC_HANDLER () at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:1991
#26 0x00000000007edd9b in execute_ex (ex=<optimized out>) at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:429
#27 0x000000000083e915 in ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER () at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:1076
#28 0x00000000007edd9b in execute_ex (ex=<optimized out>) at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:429
#29 0x000000000083e915 in ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER () at /home/xzapps/zhaopeng/php-7.1.15/Zend/zend_vm_execute.h:1076

代码搜索

[]$ grep 'localhost:9411' -r .
./vendor/xiaozhu/tracker/src/Tracker.php:            $host = getenv('ZIP_KIN_HOST') ?: 'localhost:9411';
^C
[]$

curl 超时缺少设置

$host = getenv('ZIP_KIN_HOST') ?: 'localhost:9411';
$ch   = curl_init("http://".$host."/api/v2/spans");
$body = json_encode($spans);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type:application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);

新增超时配置项

// 在尝试连接时等待的秒数
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT , 120);
// 最大执行时间
curl_setopt($curl, CURLOPT_TIMEOUT, 120);

机器高负载排查

可以查询机器负载的命令

三个命令:w, uptime, top

[www@cloud-test-env79 ~]$ w
 16:36:59 up  2:23,  1 user,  load average: 19.02, 18.69, 18.31
 USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
 www      pts/1    10.3.2.67        16:36    2.00s  0.00s  0.00s w


[www@cloud-test-env79 ~]$ uptime
 16:40:56 up  2:27,  1 user,  load average: 18.40, 18.24, 18.20


top - 16:41:09 up  2:27,  1 user,  load average: 18.25, 18.21, 18.19

什么是机器负载Load? 平均负载 Load Average

Load 就是对计算机干活多少的度量(WikiPedia:the system Load is a measure of the amount of work that a compute system is doing)简单的说是进程队列的长度。Load Average 就是一段时间(1分钟、5分钟、15分钟)内平均Load

如何判断Over Load

一般来说,根据CPU数量判断,如果平均负载始终再1.2以下,机器是2CPU。那么基本不会出现CPU不够用的情况。 也就是Load平均要小于CPU的数量,一般会根据15分钟那个load的平均值。

一般而言,服务器的合理负载是CPU核数*2。也就是说对于8核的CPU,负载在16以内表明机器运行很稳定流畅。如果负载超过16了,就说明服务器的运行有一定的压力了。

我们如何排查机器高负载

vmstat是Virtual Meomory Statistics(虚拟内存统计)的缩写,可实时动态监视操作系统的虚拟内存、进程、CPU活动。

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 18  0  16640 221116      0 1426276    0    1   133   195 2053  591 78 14  7  0  0
 23  0  16640 245128      0 1426836    0    0     0    66 4208 1059 86 14  0  0  0
 19  0  16640 227160      0 1427340    0    0     0    37 4144 1040 85 15  0  0  0
 16  0  16640 259528      0 1428076    0    0     0   329 4090 1001 85 15  0  0  0
 13  0  16640 261376      0 1428476    0    0     0     0 4087 1042 84 16  0  0  0

字段说明

Procs(进程):
    r: 运行队列中进程数量 (如果长期大于1,说明cpu不足,需要增加cpu。)
    b: 等待IO的进程数量 (比如正在等待I/O、或者内存交换等。)
    Memory(内存):
    swpd: 使用虚拟内存大小
    free: 可用内存大小
    buff: 用作缓冲的内存大小
    cache: 用作缓存的内存大小
Swap:
    si: 每秒从交换区写到内存的大小
    so: 每秒写入交换区的内存大小
IO:(现在的Linux版本块的大小为1024bytes)
    bi: 每秒读取的块数
    bo: 每秒写入的块数
system:
    in: 每秒中断数,包括时钟中断
    cs: 每秒上下文切换数
CPU(以百分比表示)
    us: 用户进程执行时间(user time)
    sy: 系统进程执行时间(system time)
    id: 空闲时间(包括IO等待时间)
    wa: 等待IO时间

iostat 查看IO负载

[www@cloud-test-env79 ~]$ iostat 1 1
Linux 3.10.0-957.21.3.el7.x86_64 (cloud-test-env79)     02/24/2020      _x86_64_        (2 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          79.06    0.00   14.45    0.04    0.00    6.46

Device:            tps    kB_read/s    kB_wrtn/s    kB_read    kB_wrtn
sda               1.29        24.13         4.30     255262      45469
sdb              22.42       216.43       362.11    2289445    3830471
dm-0              0.87        21.18         1.65     224015      17504
dm-1              0.74         0.58         2.45       6156      25904

avg-cpu: 总体cpu使用情况统计信息,对于多核cpu,这里为所有cpu的平均值

  • %user: 在用户级别运行所使用的CPU的百分比.
  • %nice: nice操作所使用的CPU的百分比.
  • %sys: 在系统级别(kernel)运行所使用CPU的百分比.
  • %iowait: CPU等待硬件I/O时,所占用CPU百分比.
  • %idle: CPU空闲时间的百分比.

Device段:各磁盘设备的IO统计信息

  • tps: 每秒钟发送到的I/O请求数.
  • Blk_read /s: 每秒读取的block数.
  • Blk_wrtn/s: 每秒写入的block数.
  • Blk_read: 读入的block总数.
  • Blk_wrtn: 写入的block总数.

sar 找出系统瓶颈的利器

查看CPU使用率 sar -u


02:13:57 PM       LINUX RESTART

02:20:01 PM     CPU     %user     %nice   %system   %iowait    %steal     %idle
02:30:01 PM     all     38.17      0.00      6.64      0.02      0.00     55.17
02:40:02 PM     all     84.96      0.00     15.01      0.00      0.00      0.03
02:50:01 PM     all     84.93      0.00     15.03      0.00      0.00      0.04
03:00:01 PM     all     84.69      0.00     15.27      0.00      0.00      0.04
03:10:01 PM     all     84.62      0.00     15.35      0.00      0.00      0.04
03:20:01 PM     all     83.92      0.00     16.05      0.00      0.00      0.03
03:30:01 PM     all     84.98      0.00     14.98      0.00      0.00      0.04
03:40:02 PM     all     84.88      0.00     15.07      0.00      0.00      0.05
03:50:01 PM     all     84.63      0.00     15.34      0.00      0.00      0.03
04:00:02 PM     all     84.86      0.00     15.09      0.00      0.00      0.04
04:10:02 PM     all     84.56      0.00     15.41      0.00      0.00      0.04
04:20:01 PM     all     83.73      0.00     16.24      0.00      0.00      0.03
04:30:02 PM     all     83.97      0.00     15.99      0.00      0.00      0.04
04:40:01 PM     all     83.84      0.00     16.13      0.00      0.00      0.03
04:50:02 PM     all     84.77      0.00     15.22      0.00      0.00      0.01
Average:        all     81.44      0.00     14.86      0.00      0.00      3.71

可以看到这台机器使用了虚拟化技术,有相应的时间消耗; 各列的指标分别是:

  • %user 用户模式下消耗的CPU时间的比例;
  • %nice 通过nice改变了进程调度优先级的进程,在用户模式下消耗的CPU时间的比例
  • %system 系统模式下消耗的CPU时间的比例;
  • %iowait CPU等待磁盘I/O导致空闲状态消耗的时间比例;
  • %steal 利用Xen等操作系统虚拟化技术,等待其它虚拟CPU计算占用的时间比例;
  • %idle CPU空闲时间比例;

查看平均负载(sar -q)

指定-q后,就能查看运行队列中的进程数、系统上的进程大小、平均负载等;与其它命令相比,它能查看各项指标随时间变化的情况;

  • runq-sz:运行队列的长度(等待运行的进程数)
  • plist-sz:进程列表中进程(processes)和线程(threads)的数量
  • ldavg-1:最后1分钟的系统平均负载 ldavg-5:过去5分钟的系统平均负载
  • ldavg-15:过去15分钟的系统平均负载
02:13:57 PM       LINUX RESTART

02:20:01 PM   runq-sz  plist-sz   ldavg-1   ldavg-5  ldavg-15   blocked
02:30:01 PM        23       215     18.70     10.79      4.67         0
02:40:02 PM        15       204     18.32     17.53     11.33         0
02:50:01 PM        17       202     17.31     17.35     14.18         0
03:00:01 PM         5       202     17.28     17.45     15.75         0
03:10:01 PM        15       201     16.86     17.47     16.60         0
03:20:01 PM        18       201     17.40     17.73     17.32         0
03:30:01 PM        17       202     17.98     17.60     17.43         0
03:40:02 PM        18       199     16.85     17.40     17.45         0
03:50:01 PM        19       205     18.94     18.30     17.80         0
04:00:02 PM        18       204     17.58     17.65     17.68         0
04:10:02 PM        19       208     18.68     18.29     18.00         1
04:20:01 PM        19       200     17.22     18.03     18.09         0
04:30:02 PM        21       211     18.28     17.87     17.96         0
04:40:01 PM        16       205     17.54     18.13     18.17         0
04:50:02 PM        17       202     17.72     17.79     18.01         0
Average:           17       204     17.78     17.29     16.03         0

查看内存使用状况(sar -r)

字段注解:

  • kbmemfree:这个值和free命令中的free值基本一致,所以它不包括buffer和cache的空间.
  • kbmemused:这个值和free命令中的used值基本一致,所以它包括buffer和cache的空间.
  • %memused:物理内存使用率,这个值是kbmemused和内存总量(不包括swap)的一个百分比.
  • kbbuffers和kbcached:这两个值就是free命令中的buffer和cache.
  • kbcommit:保证当前系统所需要的内存,即为了确保不溢出而需要的内存(RAM+swap).
  • %commit:这个值是kbcommit与内存总量(包括swap)的一个百分比.
02:13:57 PM       LINUX RESTART

02:20:01 PM kbmemfree kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit  kbactive   kbinact   kbdirty
02:30:01 PM    449916   3430448     88.41       948    925236   3204692     53.61   1564160    727752     74388
02:40:02 PM    250496   3629868     93.54        76    642292   3434756     57.46   1918776    577624      2896
02:50:01 PM    217472   3662892     94.40        76    646180   3468536     58.03   1874572    655268      1220
03:00:01 PM    200560   3679804     94.83        76    645484   3483892     58.28   1869832    677108      1400
03:10:01 PM    232804   3647560     94.00        36    687240   3407608     57.01   1824896    690028      1812
03:20:01 PM    237268   3643096     93.89         0    658572   3502412     58.59   1764740    801092      2316
03:30:01 PM    285796   3594568     92.63         0    674780   3493840     58.45   1752620    832220      2832
03:40:02 PM    297988   3582376     92.32         0    744884   3409472     57.04   1748868    820916      1592
03:50:01 PM    193656   3686708     95.01         0    929264   3597268     60.18   1823876   1070348      3652
04:00:02 PM    168556   3711808     95.66         0    944632   3562620     59.60   1847804   1070788       856
04:10:02 PM    170684   3709680     95.60         0    964628   3529120     59.04   1829056   1059360      2620
04:20:01 PM    251432   3628932     93.52         0    911876   3475648     58.15   1772392   1034332      2792
04:30:02 PM    276664   3603700     92.87         0    929752   3433156     57.43   1746496   1030228      3652
04:40:01 PM    353412   3526952     90.89         0    848160   3414616     57.12   1687536    993700       944
04:50:02 PM    244372   3635992     93.70         0    914408   3458112     57.85   1802704    986708       620
Average:       255405   3624959     93.42        81    804493   3458383     57.86   1788555    868498      6906

查看页面交换发生状况(sar -W)

页面发生交换时,服务器的吞吐量会大幅下降;服务器状况不良时,如果怀疑因为内存不足而导致了页面交换的发生,可以使用这个命令来确认是否发生了大量的交换;

  • pswpin/s:每秒系统换入的交换页面(swap page)数量
  • pswpout/s:每秒系统换出的交换页面(swap page)数量
02:13:57 PM       LINUX RESTART

02:20:01 PM  pswpin/s pswpout/s
02:30:01 PM      0.00      0.00
02:40:02 PM      0.00      0.03
02:50:01 PM      0.04      0.65
03:00:01 PM      0.08      0.83
03:10:01 PM      0.09      0.20
03:20:01 PM      0.25      0.45
03:30:01 PM      0.12      0.39
03:40:02 PM      0.07      0.00
03:50:01 PM      0.24      0.84
04:00:02 PM      0.04      0.30
04:10:02 PM      0.16      0.74
04:20:01 PM      0.31      1.20
04:30:02 PM      0.08      0.84
04:40:01 PM      0.33      2.06
04:50:02 PM      0.07      0.00
Average:         0.13      0.57

要判断系统瓶颈问题,有时需几个 sar 命令选项结合起来;

  • 怀疑CPU存在瓶颈,可用 sar -u 和 sar -q 等来查看
  • 怀疑内存存在瓶颈,可用sar -B、sar -r 和 sar -W 等来查看
  • 怀疑I/O存在瓶颈,可用 sar -b、sar -u 和 sar -d 等来查看

HTTP

HTTP是什么

HTTP是Hypertext Transfer Protocol 的缩写,超文本传输协议

为什么学习HTTP

网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备……)。RESTful API是目前比较成熟的一套互联网应用程序的API设计理论, 底层协议正是使用的Http协议。做为后端开发的同学,有必要了解底层的HTTP协议内容及实现逻辑,有利于我们清楚整个具体过程到底发生了什么。

如何学习HTTP

client & server 交互场景

client&server

HTTP 报文结构

http message structure

request example

response example

经典开源的HTTP Server项目

  • 源码Tinyhttpd

  • 流程图

  • 阅读代码顺序 main -> startup -> accept_request -> execute_cgi

  • 每个函数作用
    accept_request:  处理从套接字上监听到的一个 HTTP 请求,在这里可以很大一部分地体现服务器处理请求流程。
    bad_request: 返回给客户端这是个错误请求,HTTP 状态吗 400 BAD REQUEST.
    cat: 读取服务器上某个文件写到 socket 套接字。
    cannot_execute: 主要处理发生在执行 cgi 程序时出现的错误。
    error_die: 把错误信息写到 perror 并退出。
    execute_cgi: 运行 cgi 程序的处理,也是个主要函数。
    get_line: 读取套接字的一行,把回车换行等情况都统一为换行符结束。
    headers: 把 HTTP 响应的头部写到套接字。
    not_found: 主要处理找不到请求的文件时的情况。
    sever_file: 调用 cat 把服务器文件返回给浏览器。
    startup: 初始化 httpd 服务,包括建立套接字,绑定端口,进行监听等。
    unimplemented: 返回给浏览器表明收到的 HTTP 请求所用的 method 不被支持。
    
  • cgi页面请求响应处理的核心函数—— execute_cgi()

Pipe state

参考链接

  • RESTful API 设计指南
  • 理解RESTful架构
  • Tinyhttpd for Ubuntu 14.04 中文详细注释版
  • HTTP1.1 基础: 请求和响应的消息交互细节
  • RFC文档
    • 摘要
    • The Hypertext Transfer Protocol (HTTP) is an application-level protocol for distributed, collaborative, hypermedia information systems. It is a generic, stateless, protocol which can be used for many tasks beyond its use for hypertext, such as name servers and distributed object management systems, through extension of its request methods, error codes and headers [47]. A feature of HTTP is the typing and negotiation of data representation, allowing systems to be built independently of the data being transferred.

    HTTP has been in use by the World-Wide Web global information initiative since 1990. This specification defines the protocol referred to as “HTTP/1.1”, and is an update to RFC 2068 [33].

C 语言 Demo示例学习

函数指针

函数指针是指向函数的指针变量。

通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。

函数指针可以像一般函数一样,用于调用函数、传递参数。

函数指针变量的声明:

typedef int (*fun_ptr)(int,int); // 声明一个指向同样参数、返回值的函数指针类型

实例

#include <stdio.h>

int max(int x, int y)
{
    return x > y ? x : y;
}

int main(void)
{
    /* p 是函数指针 */
    int (* p)(int, int) = & max; // &可以省略
    int a, b, c, d;

    printf("请输入三个数字:");
    scanf("%d %d %d", & a, & b, & c);

    /* 与直接调用函数等价,d = max(max(a, b), c) */
    d = p(p(a, b), c);

    printf("最大的数字是: %d\n", d);

    return 0;
}

编译执行,输出结果如下:

请输入三个数字:1 2 3
最大的数字是: 3

回调函数

函数指针作为某个函数的参数

函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。

简单讲:回调函数是由别人的函数执行时调用你实现的函数。

C typedef

C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。下面的实例为单字节数字定义了一个术语 BYTE:

typedef unsigned char BYTE;

在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写,例如:

BYTE  b1, b2;

按照惯例,定义时会大写字母,以便提醒用户类型名称是一个象征性的缩写,但您也可以使用小写字母,如下:

typedef unsigned char byte;

示例

#include <stdio.h>
#include <string.h>

typedef struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} Book;

int main( )
{
   Book book;

   strcpy( book.title, "C 教程");
   strcpy( book.author, "Runoob");
   strcpy( book.subject, "编程语言");
   book.book_id = 12345;

   printf( "书标题 : %s\n", book.title);
   printf( "书作者 : %s\n", book.author);
   printf( "书类目 : %s\n", book.subject);
   printf( "书 ID : %d\n", book.book_id);

   return 0;
}

typedef vs #define

#define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:

  • typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
  • typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。

下面是 #define 的最简单的用法:

#include <stdio.h>

#define TRUE  1
#define FALSE 0

int main( )
{
   printf( "TRUE 的值: %d\n", TRUE);
   printf( "FALSE 的值: %d\n", FALSE);

   return 0;
}

枚举类型vs常量

枚举常量是另外一种类型的常量。枚举是一个常量整型值的列表。

C enum(枚举)

枚举是 C 语言中的一种基本数据类型,它可以让数据更简洁,更易读。

枚举语法定义格式为:

enum 枚举名 {枚举元素1,枚举元素2,……};

接下来我们举个例子,比如:一星期有 7 天,如果不用枚举,我们需要使用 #define 来为每个整数定义一个别名:

#define MON  1
#define TUE  2
#define WED  3
#define THU  4
#define FRI  5
#define SAT  6
#define SUN  7

这个看起来代码量就比较多,接下来我们看看使用枚举的方式:

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};

C 命令行参数

执行程序时,可以从命令行传值给 C 程序。这些值被称为命令行参数,它们对程序很重要,特别是当您想从外部控制程序,而不是在代码内对这些值进行硬编码时,就显得尤为重要了。

命令行参数是使用 main() 函数参数来处理的,其中,argc 是指传入参数的个数,argv[] 是一个指针数组,指向传递给程序的每个参数。下面是一个简单的实例,检查命令行是否有提供参数,并根据参数执行相应的动作:

#include <stdio.h>

int main( int argc, char *argv[] )
{
   if( argc == 2 )
   {
      printf("The argument supplied is %s\n", argv[1]);
   }
   else if( argc > 2 )
   {
      printf("Too many arguments supplied.\n");
   }
   else
   {
      printf("One argument expected.\n");
   }
}

使用一个参数,编译并执行上面的代码,它会产生下列结果:

$./a.out testing
The argument supplied is testing

C 内存管理

void *calloc(int num, int size); 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。

void free(void *address); 该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。

void *malloc(int num); 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。

void *realloc(void *address, int newsize); 该函数重新分配内存,把内存扩展到 newsize。

注意:void * 类型表示未确定类型的指针。C、C++ 规定 void * 类型可以通过类型转换强制转换为任何其它类型的指针。

动态分配内存

编程时,如果您预先知道数组的大小,那么定义数组时就比较容易。例如,一个存储人名的数组,它最多容纳 100 个字符,所以您可以定义数组,如下所示:

char name[100];

但是,如果您预先不知道需要存储的文本长度,例如您向存储有关一个主题的详细描述。在这里,我们需要定义一个指针,该指针指向未定义所需内存大小的字符,后续再根据需求来分配内存,如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
   char name[100];
   char *description;

   strcpy(name, "Zara Ali");

   /* 动态分配内存 */
   description = (char *)malloc( 200 * sizeof(char) );
   if( description == NULL )
   {
      fprintf(stderr, "Error - unable to allocate required memory\n");
   }
   else
   {
      strcpy( description, "Zara ali a DPS student in class 10th");
   }
   printf("Name = %s\n", name );
   printf("Description: %s\n", description );
}

上面的程序也可以使用 calloc() 来编写,只需要把 malloc 替换为 calloc 即可,如下所示:

calloc(200, sizeof(char));

当动态分配内存时,您有完全控制权,可以传递任何大小的值。而那些预先定义了大小的数组,一旦定义则无法改变大小。

重新调整内存的大小和释放内存

当程序退出时,操作系统会自动释放所有分配给程序的内存,但是,建议您在不需要内存时,都应该调用函数 free() 来释放内存。

或者,您可以通过调用函数 realloc() 来增加或减少已分配的内存块的大小。让我们使用 realloc() 和 free() 函数,再次查看上面的实例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
   char name[100];
   char *description;

   strcpy(name, "Zara Ali");

   /* 动态分配内存 */
   description = (char *)malloc( 30 * sizeof(char) );
   if( description == NULL )
   {
      fprintf(stderr, "Error - unable to allocate required memory\n");
   }
   else
   {
      strcpy( description, "Zara ali a DPS student.");
   }
   /* 假设您想要存储更大的描述信息 */
   description = (char *) realloc( description, 100 * sizeof(char) );
   if( description == NULL )
   {
      fprintf(stderr, "Error - unable to allocate required memory\n");
   }
   else
   {
      strcat( description, "She is in class 10th");
   }

   printf("Name = %s\n", name );
   printf("Description: %s\n", description );

   /* 使用 free() 函数释放内存 */
   free(description);
}

C 预处理器

C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。我们将把 C 预处理器(C Preprocessor)简写为 CPP。

所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。下面列出了所有重要的预处理器指令:

指令	描述
#define	定义宏
#include	包含一个源代码文件
#undef	取消已定义的宏
#ifdef	如果宏已经定义,则返回真
#ifndef	如果宏没有定义,则返回真
#if	如果给定条件为真,则编译下面代码
#else	#if 的替代方案
#elif	如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif	结束一个 #if……#else 条件编译块
#error	当遇到标准错误时,输出错误消息
#pragma	使用标准化方法,向编译器发布特殊的命令到编译器中

职业规划

前几天,听PM圈子的老师分享关于项目经理的职业规划,顺便分享了职业规划的大致框架。

职业规划

量化理论

演绎论证,它们的分支命题不是复合命题, 并且其有效性或无效性取决于这些非复合命题的内在逻辑结构。

单称命题,介绍了个体变元符号x,个体常项符号(小写字母从a到u)以及表示属性的符号(大写字母)。介绍了命题函项概念: 个含有一个个体变元的表达式,当以一个个体常元代入个体変元时,它就変成一个陈述。因此,通过列举程序,可以从一个命题函项得到一个命题。

如何用概括的方法,也就是通过使用“每个”、“没有”有些”等量词,从命题函项得到命题。介绍了全称量词(x),其含义是给定任何一个x”,以及存在量词(ヨx),其含义是“至少存在一个如此这般的x”。还用对当方阵表明了全称量化和存在量化之间的关系。

怎样用命题函项和量词正确地符号化以下四种主要命题: A:全称肯定命题E:全称否定命题I:特称肯定命题O:特称否定命题还对A、E、I、O四种命题之间关系的现代解释进行了说明。

通过增加以下四个附加规则,扩展了推论规则表: 全称列举,UI 全称概括,UG 存在列举,E 存在概括,EG并且说明了怎样用这四个和前面已提出的19条推论规则,构造演绎论证有效性的形式证明,这种证明涉及非复合命题的内部结构。

如何设计含有一个、两个或三个个体的模型或可能域以及在该可能域中改写论证的各分支命题,由此用逻辑类推的反驳方法来证明一个涉及量词的论证的无效性。如果我们能展示这样一个可能域,即它至少含有一个使该论证的所有前提在域中为真而结论在其中为假的个体,那么,我们就证明了这个涉及量词的论证无效

怎样对非三段论论证进行符号化和评价。这些论证含有些不能划归为A、E、I、O命题或单称命题的命题。鉴于除外命题和其他一些命题的复杂性,必须先理解它们的逻辑含义,然后才能用命题函项和量词进行准确的翻译。

直言三段论

标准式直言三段论: 组成成分、形式、有效性和制约其正确使用的规则。

三段论大项、小项和中项的定义:

大项:结论的谓项

小项:结论的主项

中项:两个前提中都出现, 但结论中不出现的第三个项

继而又分别定义了大前提和小前提,包含大项的前提叫做大前提,包含小项的前提叫做小前提。

如果几个命题出现的次序正好是:大前提在第一位、小前提在第二位、结论在最后,我们就把这样的三段论指定为标准式的。

三段论的式与格是如何确定的。 段论的式由识别三个命题类型的字母来确定,即A、E、1、O中的三个。总共有64个不同式。 段论的格由中项在前提中的不同位置来确定。

对四个可能的格描述并定义如下:

第一格:中项在大前提中做主项、在小前提中做谓项。 模式为:M-P,S-M,所以S-P。

第二格:中项在两个前提中都儆谓项。 模式为:P-M,S-M,所以S-P

第三格:中项在两个前提中都做主项。 模式为:M一P,M-S,所以S-P。

第四格:中项在大前提中做谓项、在小前提中做主项。 模式为:P-M,M-S,所以S-P。

标准式三段论的式与格如何共同地确定其逻辑形式。由于64个式每一个都有四个格,所以共有256个标准式的直言三段论,但其中只有一小部分是有效式。

标准式三段论的六条基本规则,同时定义了违反各条规则所造成的谬误。

规则1一个有效的标准式直言三段论必须仅仅包含三个项,在整个论证中,每一个项都须在相同的意义上使用。

违反本规则所犯的错误:四项谬误。

规则2在一个有效的标准式直言三段论中,中项必须至少在一个前提中周延。

违反本规则所犯的错误:中项不周延谬误。

规则3在一个有效的标准式直言三段论中,在结论中周延的项在前提中也必须周延。

违反本规则所犯的错误:大项不当周延谬误,或者小项不当周延谬误

规则4任何有两个否定前提的标准式三段论都不是有效的。

违反本规则所犯的错误:排斥前提谬误。

规则5如果一个标准式三段论有一个前提是否定的,那么结论必须是否定的。

违反本规则所犯的错误:从否定推肯定谬误。

规则6一个有效的标准式直言三段论,如果结论为特称命题,那么其前提不能都是全称的。

违反本规则所犯的错误:存在谬误。

标准式直言三段论的15个有效形式的说明,识别它们的格与式,并说明了它们传统的拉丁名称: AAA-1( Barbara)、EAE-1( parent)、AII1(Dari)、EIO-1(Fe rio)、AEE-2( Camestres)、EAE-2( Cesare)、AOO2( Baroko)、EIO2( Festino)、AII-3( Datisi)、IAI-3( Disamis)、EIO-3( Ferison)、OAO3 ( Bokardo)、AEE-4( amenes)、IAI-4( Dimaris)、EIO-4( Fresison)。

15个有效形式的演绎推导,通过排除法程序,证明了只有15个形式是完全遵守三段论的六条基本规则的。

谬误

什么是谬误

谬误是那种看起来正确但经过考察而证明并非如此的论证。

谬误三大类非形式谬误:相干谬误、预设谬误和含混谬误。

相干谬误

相干谬误在这类谬误中,错误论证依赖于看起来可能与结论相关但事实上无关的前提。我们分七种相干谬误来解释这类推理错误。

R1.诉诸无知论证:当以一命题没有被证明是假的为理由来论证该命题是真的,或当论证一命题是假的因为它没有被证明是真的。

R2.诉诸不当权威:一个论证的前提诉诸某方或多方判断,而它或它们却不能合法地声称对手头问题具有权威。

R3.人身攻击论证:攻击不是针对所做的主张或针对论证的优点, 而是针对对手本身。 人身攻击论证有两种形式。当攻击直接针对人,以寻求诋毁和侮辱他们时,就称做“诽谤性人身攻击论证”。当攻击间接地对准人,暗示他们坚持他们的观点主要是因为他们的特殊环境或利益时,就称做“背景性人身攻击论证”。

R4.诉诸情感:细心推理被激起狂热或情感来支持预先结论的精心策划所取代。

R5.诉诸同情:细心推理被激起听者同情来达到说者所关注目标的精心策划所取代。

R6.诉诸武力:为了得到对某些结论的承诺,细心推理被直接或含沙射影的威胁所取代。

R7.不相于结论:前提不得要领,声称支持一个结论而事实上却支持或证实另一个结论。

预设谬误

预设谬误在这类谬误中,错误论证源于依赖于某些被假定为真的命题,而这些命题实际上是假的、可疑的或没有得到证明的。我们分五种预设谬误来解释这类推理错误。

P1.复杂问语:以问句预设了某些假设为真的方式来询向问题。

P2.虚假原因:把一个东西当做一个事物的原因而它实际上并不是那个事物的原因,或更一般地说,在以因果关系为基础的推理中犯错

P3.丐题:在某个论证前提中假定了结论要寻求确证的东西。

P4.偶然:把某个概括运用于它不能适当管辖的个别情况P5.逆偶然:粗心大意地从单个情况转移到一个无辦护余地的广泛概括。

含混谬误

含混谬误在这类谬误中,错误论证的形成方式是,它依赖于词或短语从在前提中的用法到在结论中的用法的意义变化。我们分五种含混谬误来解释这类推理错误。

A1.歧义:在论证的明确表述中,有意或无意地使用同一个词或短语的两个或更多意义。

A2.双关:因为陈述中的词或短语结合得松散或笨拙,论证中的这个陈述具有多于一个合理意义。

A3.重读:意义的变化作为对论证的词或短语的强调改变的结果而源于该论证之内。

A4.合成:(a)错误地从部分性质到整体性质进行推理,(b)或者, 错误地从某汇集的个别分子性质到整个汇集的性质进行推理。

A5.分解:(a)错误地从整体性质到它的一个部分的性质进行推理, (b)或者,错误地从某些实体汇集的某个全体性质到该汇集的个别实体性质进行推理。

语言的用法

语言的多种用法和形式,以及可能由于没有认识到这些复杂性而引起的错解和滥用。

语言的三种基本功能: 信息性功能、表达性功能和指令性功能

一个给定语段可能行使的多种功能的方式:同时行使两种甚或所有三种功能。

标准语法形式的句子,即陈述句、疑问句、祈使句和感叹句,并不总是行使与其名称相关的功能。陈述句可以用做指令性的或者表达性的功能;疑问句可以具有信息性的或指令性的功能,等等。语法形式不决定语言功能。

信念歧见与态度歧见。冲突双方可以既在事实是什么上致也在对事实的态度上一致,或者在两方面都对立。他们可能在事实上致,而在对事实的态度上对立。他们还可能在事实是什么上对立,但在对他们所相信的事实的态度上却致。要解决歧见问题,了解其真正本性是极其重要的。