tcp、http 学习小结
前言
最近因为cdn的一个问题,困扰了自己好久。因为需要统计网站访问的成功数,而且要求比较精确。目前的实现不能满足要求,因为没有区别访问成功与否,也没有对超时做处理。期间解决这个问题,走了不少弯路,现在在这里记录下来,为自己也为别人提供一个方便。
关键字
- tcp
- 1.tcp三次握手
- 2.tcp序列号,确认号
- 3.tcp 头部扩展option域
- http
- 1.http 1.0与http1.1
- 2.get,response
- 3.http 返回码
- wireshark
- 工具介绍
一.tcp小结
tcp头格式:
tcp结构体struct tcphdr
struct tcphdr {
__u16 source;
__u16 dest;
__u32 seq;
__u32 ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u16 res1:4,
doff:4,
fin:1,
syn:1,
rst:1,
psh:1,
ack:1,
urg:1,
ece:1,
cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
__u16 doff:4,
res1:4,
cwr:1,
ece:1,
urg:1,
ack:1,
psh:1,
rst:1,
syn:1,
fin:1;
#else
#error "Adjust your <asm/byteorder.h> defines"
#endif
__u16 window;
__u16 check;
__u16 urg_ptr;
};
tcp建立连接与关闭连接
tcp建立连接需要经过三次握手syn->syn/ack->ack,三次握手每一步都是必需的,在网络环境比较差的时候,会发生握手包丢失,tcp会自动重复发送握手包,直到连接建立或者超时。
tcp建立连接时候的初始序列号并不是0,而是经过一种算法得到的,具体百度。这可以保证tcp序列号不至于错乱,在wireshark中显示的序列号是相对序列号,wireshark把每次syn的序列号作为基准0,这样的目的是看起来更直观。
syn:
syn/ack:
ack:
步骤 | flags | 标志位 | seq number | ack number | tsval | tsecr |
---|---|---|---|---|---|---|
syn | 0x002 | ack=1 | 0x92 0x68 0xa8 0x53 | 0 | 582971879 | 0 |
syn/ack | 0x012 | ack=1,syn=1 | 0xb2 0x29 0xde 0x5b | 0x92 0x68 0xa8 0x54 | 776961681 | 582971879 |
ack | 0x010 | ack=1 | 0x92 0x68 0xa8 0x54 | 0xb2 0x29 0xde 0x5c | 582971887 | 776961681 |
可以看出来初始序列号并不是0,每次的确认号表示的是期望下次从对方收到的序列号的值。序列号与确认号都是uint32_t类型,占据32位,达到最大又会从0开始。
类似的结束时候
步骤 | flags | 标志位 | seq number | ack number | tsval | tsecr |
---|---|---|---|---|---|---|
fin/ack | 0x011 | fin=1,ack=1 | 0xb2 0x29 0xe4 0x68 | 0x92 0x68 0xa8 0x99 | 776961691 | 582971887 |
ack | 0x010 | ack=1 | 0x92 0x68 0xa8 0x99 | 0xb2 0x29 0xe4 0x69 | 582971898 | 776961691 |
fin/ack | 0x011 | fin=1,ack=1 | 0x92 0x68 0xa8 0x99 | 0xb2 0x29 0xe4 0x69 | 582971898 | 776961691 |
ack | 0x010 | ack=1 | 0xb2 0x29 0xe4 0x69 | 0x92 0x68 0xa8 0x9a | 776961720 | 582971898 |
tcp关闭连接是经过四次握手,建立连接经过三次握手,值得提出的是建立和关闭连接并不消耗序列号,只有成功建立或者关闭序列号才会加一,具体可以自己抓包分析下。
tcp option头部
上面的tsval,与tsecr是在tcp头部的扩展字段(kind)。本身并不包含于tcphdr结构体中,因为这是由后续rfc文档建议添加的字段,在tcp模块编写之后提出的。因为tcp option的存在,所以tcp头的长度一般不是20(tcp->doff == 4),tcp头最小是20bytes。
下面是kind字段意义列表:
Kind | Meaning | Reference |
---|---|---|
0 | End of Option List | [RFC793] |
1 | No-Operation | [RFC793] |
2 | Maximum Segment Size | [RFC793] |
3 | WSOPT - Window Scale | [RFC1323] |
4 | SACK Permitted | [RFC2018] |
5 | SACK | [RFC2018] |
6 | Echo (obsoleted by option 8) | [RFC1072] |
7 | Echo Reply (obsoleted by option 8) | [RFC1072] |
8 | TSOPT - Time Stamp Option | [RFC1323] |
9 | Partial Order Connection Permitted | [RFC1693] |
10 | Partial Order Service Profile | [RFC1693] |
11 | CC | [RFC1644] |
12 | CC.NEW | [RFC1644] |
13 | CC.ECHO | [RFC1644] |
14 | TCP Alternate Checksum Request | [RFC1146] |
15 | TCP Alternate Checksum Data | [RFC1146] |
16 | Skeeter | [Knowles] |
17 | Bubba | [Knowles] |
18 | Trailer Checksum Option | [Subbu & Monroe] |
19 | MD5 Signature Option | [RFC2385] |
20 | SCPS Capabilities | [Scott] |
21 | Selective Negative Acknowledgements | [Scott] |
22 | Record Boundaries | [Scott] |
23 | Corruption experienced | [Scott] |
24 | SNAP | [Sukonnik] |
25 | Unassigned (released 12/18/00) | |
26 | TCP Compression Filter | [Bellovin] |
其中我用到的是kind为8的字段,意思是在每个数据包上加上tcp时间戳。时间戳分为tsval与tsecr两个,分别由连接的两端进行维护,每次传输时需要在tsecr上对对方上次的数据包的时间戳进行确认,并在tsval加上自己最新的时间戳。
tcp时间戳并不是系统时间,通过date -d @776961691可以看到结果是Mon Aug 15 22:41:31 CST 1994,时间明显不对。tcp时间戳不采取系统时间的理由也很充分,每次同步时间都会导致时间突变,这就会导致时间戳也突变,这是不可取的。实际上tcp采用的是系统从开机以来的相对时间,这样只要不关机,就不会变化。系统默认每次HZ是250,这是在内核编译时指定的,也就是jiffies每增加一就是4ms的时间。因此可以通过两次tsval时间差计算出两次发包的时间间隔。
http://blog.csdn.net/zhandoushi1982/article/details/5536210
http
http是应用层协议,一般是建立在tcp之上的。也就是说http是面向用户,并不关心底层连接的实现。有句话叫做“http面向连接的,但是是无状态的”。这句话可以这么理解:基于tcp实现的http,每次发送请求都会先建立tcp连接,在连接建立之后发送http数据。然而http又是无记忆的,http并不知道自己已经发送过哪些数据,所以对于同样的请求,比如每次打开www.baidu.com,刷新页面都是重新发送的数据。http把这当作一次新的请求。
http数据是紧挨着tcp头之后的。
一个完整的包结构是这样的:
-{Ethernet | Ip | tcp/udp | HTTP}
http现在最流行的是http 1.1版本,相比于之前版本,效率提高了许多。http 1.0是短连接,每次传输数据会建立一个tcp连接,使用完毕立马关闭,下次使用重新建立连接。http 1.1则默认长连接,这在http头域connection选项中可以设置,值为close为短连接,keep-alive为长连接。
验证如下:
telnet www.baidu.com 80
输入:
GET / HTTP/1.1
两次回车,(这是因为http规定行末,以及头结束必须为\r\n)
GET / HTTP/1.0
http 1.0
http 1.1
先写这么多。。。
参考链接给在下面,懒得写了
http://blog.chinaunix.net/uid-8059407-id-2034302.html
http://www.cse.scu.edu/~dclark/am_256_graph_theory/linux_2_6_stack/linux_2tcp_8h-source.html
http://blog.csdn.net/mrwangwang/article/details/8537775
http://blog.csdn.net/zhandoushi1982/article/details/5536210