读《《图解TCP/IP》》有感

TCP/IP


近期几天读完《《图解TCP/IP》》,收获蛮多,记得上学时读stevens的《《TCP/IP具体解释》》时那是一个囫囵吞枣,没认真看也看不下去。等有时间再拜读下《《TCP/IP具体解释》》吧,预计能有不少共鸣。

如今认为。要想比較透彻理解TCP/IP,还得须要有server编程经验,学校应该同一时候开设《socket编程》》相关课程,最好同一个老师教,能够串讲,不然光理论不实战就是花架子,并且也不easy真正理解TCP/IP协议。

TCP/UDP一览

仅仅有较为透彻的理解了TCP/UDP才知道为啥TCP叫传输控制协议。UDP叫用户数据包协议。TCP在背后帮你做了非常多事,连接管理,保存时序,超时重发,拥塞控制。流控。通过mss自己主动帮你做分片,以降低传输途中路由器分片消耗(IPV6路由器是不会分片的),这叫传输控制协议,突出“控制”二字。而UDP什么都不帮你做,不保证时序。不做超时重传,是须要上层协议来保证,也就是用户来写规则,这也是第一个U(user)的含义。同一时候UDP没有mss,所以一次性发送数据最好不要太多。以免分片导致性能损耗。

那UDP为什么叫用户数据包协议呢。说到数据包就得说说TCP和UDP的面向连接和无连接了。

我们知道TCP是面向连接的,而UDP是无连接的。这就直接导致了TCP会通过三次握手连接server,假设serverport不正确TCP是知道,但UDP不一样。压根不知道server段的情况。即使server不在线,client照样发送数据。

In [22]: import socket

In [23]: s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

In [24]: s.sendto("hello world",("127.0.0.1",9999))
Out[24]: 11

没有服务端。我们照样发送数据,sendto()照样返回。假设你之前不知道。这会让你大吃一惊,这也正说明了UDP不做数据包确认。

TCP传输的是流式数据,也就是说中间是没有分段的,这也就导致了我们常有的粘包问题,所以我们须要封包和解包,常见的都是字节长度+数据的方式,如websocket。也就是说我们接受一次,可能是对方多次发送后的数据集合,也可能是发送一次的部分数据,这都有可能,不能假设是哪种情况。这不经让我想起曾经上学时写的简单socketclient,server程序。都是一发一收。我们没封包解包不也正常吗?这就要说说Linux recv系统调用了。

int recv( SOCKET s, char FAR *buf, int len, int flags);

recv运行流程(来自网上)

1.recv等待s发送缓冲区数据发送完成,假设中途出现网络错误,recv函数返回SOCKET_ERROR

2.recv检查s的接受缓冲区。假设接受缓冲区没有数据或者

正在接受数据。那么recv一直等待,直到把数据接受完成。当接受完成后,recv会把s接受缓冲区的数据copy到buf中(缓冲区数据可能大于buf长度。所以须要多次调用recv。

recv仅仅是copy数据,真正接受数据是底层协议来完成的)

那这就好理解了,因为我们client一般是先send再recv,再发送完成之后recv会等待,并且我们数据量比較小,每次间隔比較大。所以有数据了,recv就返回了。当然这也是存在隐患的,发送数据大。一次recv可能接受不了。那就须要多次recv。这时就须要封包了,不然你怎么知道什么时候该返回。

就拿Http来说,服务端通过Content-Length和chunked编码来封包,让浏览器解析数据。

当然http1.0事实上Content-Length是非必须的,因为每次发送完数据后。服务端都会close,clientrecv会返回0,这时候也就知道数据结束了,也就是close作为了EOF标志。一帮下载都是通过这样的方式,所以也不会有粘包。

而UDP是不会出现粘包的,接收肯定能完整接收到发送的数据,世界瞬间清净了,这也是UDP**数据包**的概念,一个一个的数据包,接收的都是完整的数据包。

校验和

TCP/IP中校验和校验算法都几乎相同,也就是所谓的

反码求和

步骤例如以下

1.将校验和置为0

2.将数据转换为16字节整形,不足补零,求和(採用32位加法,将高16位和低16位相加,再将可能产生的进位与低16位相加)

3.和求反

最后发送者将校验和替换为求反的结果,将数据发给接受者,接受者忽略第一步採用相同算法。假设最后结果不为0,将丢弃该数据包。

手痒了一下。于是用golang模拟了下

//校验和算法
//对每16位(2字节)进行二进制反码求和,反码求和的意思是先对每16位求和。再将得到的和转为反码
func CheckSum(data []byte) uint16 {
//padding 16
all := len(data) / 2
if all*2 < len(data) {
all++
data = append(data, 0)
} u16 := (*[0xffff]uint16)(unsafe.Pointer(&data[0]))[:all]
var sum uint32
for _, u := range u16 {
sum += uint32(u)
}
sum = (sum >> 16) + (sum & 0xffff) //把高位的进位,加到低八位
sum += sum >> 16 //前一步可能有进位
return (uint16)(^sum)
} func main() {
check := make([]byte, 2)
data := []byte("tcp/ip checksum test1")
//首位作为校验和字段
all := []byte(string(check) + string(data))
sum := CheckSum(all)
fmt.Println(all, sum)
//校验和写入首位
copy(all, (*[2]byte)(unsafe.Pointer(&sum))[:])
fmt.Print(all, CheckSum(all))
}

结果:

[0 0 116 99 112 47 105 112 32 99 104 101 99 107 115 117 109 32 116 101 115 116 49] 22732

[204 88 116 99 112 47 105 112 32 99 104 101 99 107 115 117 109 32 116 101 115 116 49] 0

我们发现最后的确是0。说明我们算法没问题,至于为什么应该是0,小伙伴们想想就知道了。

TCP序列号与滑动窗体

以下说说TCP的seq,ack,在两方通信时seq開始是随机生成的,

假设A開始seq100,ack 1,发送数据size 100

通信 seq ack size
A->B 100 1 10
B->A 200 111 0
A->B 111 201 1000
B->A 201 1112 0

1.A->B 初始随机seq 100,ack 1,发送数据10字节

2.B->A 初始睡觉seq200,告诉A下一个发送111。前面的数据都收到了,因为B仅仅是回应包 size 0

3.A->B A收到B的回应。看到ack为111。知道前面的10个自己都已经成功发送了。所以seq为111,发送1000个字节,并回应B下一个開始发送201

4.B->A ack为1000+111+1。告诉A下次从1112開始发送

仅仅要对方回应了ack。那么就代表ack之前的数据都已经成功接收了。这是对方就能够把自己缓冲区的相应数据给清空了,不然须要保存以待重传。因为每次都单项传输。吞吐量有点低,所以TCP引用了滑动窗体。也就是在没有明确收到ack之前能够连续发送w个包。w就是滑动窗体的大小。因为引用了滑动窗体。有些ack丢失也没有关系。仅仅要收到了兴许的ack确认就代表之前的数据都已经成功接收了。

CSMA/CD

通信专业的对CSMA/CD肯定都不陌生。我发现老师特别喜欢说这玩意,但我曾经始终不明确这玩意有啥用?事实上CSMA/CD是用于共享网络的,也就是通过hub或同轴电缆等连接的总线型或星型拓扑结构。因为这东西我们基本都没见过(过时了),因为须要冲突检查所以是半双工通信。当交换机出来时,CSMA/CD就被淘汰了。通过交换机的port转发实现了全双工通信。不知道是当初老师的问题还是我太弱,如今才明确应用场景。

如今的无线也是半双工的。是CSMA/CD的改良版,叫CSMA/CA。

最新文章

  1. linux内核学习之六 进程创建过程学习
  2. mfc
  3. yii学习笔记
  4. javascript中的闭包解析
  5. JVM调优-Java中的对象
  6. DirectX基础学习系列8 渐进网格以及外接体
  7. 常见的MIME类型
  8. linux sed使用
  9. phalcon的一些中文手册和帮助文档地址收集
  10. 第三方账号登录--QQ登录,以及QQ微博账号登录
  11. 王立平--string.Empty
  12. ios实现文字的自适应
  13. Laravel 5.2 教程 - 队列
  14. DotNetCore跨平台~Quartz热部署的福音~监控文件夹的变化
  15. 【转】Matlab作图语句小结
  16. [iOS]详解调整UIButton的title和image的位置
  17. Android的Intent机制详解
  18. 中文分词实战——基于jieba动态加载字典和调整词频的电子病历分词
  19. js 获取时区
  20. WingIDE 常用快捷键

热门文章

  1. docer中运行crontab
  2. python的模块itsdangerous
  3. mipmap一
  4. java源码阅读String
  5. MySQL增加访问ip
  6. redhat6.4 install 163 source
  7. 纯css 实现 三角形、梯形等 效果
  8. js 判断是否包含
  9. angularjs与server交互
  10. 【VBA】获取Excle的安装路径