zoukankan      html  css  js  c++  java
  • 读《《图解TCP-IP》》有感

    读《《图解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。

  • 相关阅读:
    局域网无法访问vmware虚拟机WEB服务器解决办法
    zipimport.ZipImportError: can't decompress data; zlib not available 解决办法
    如何在win下使用linux命令
    《redisphp中文参考手册》php版
    Python关键字参数与非关键字参数(可变参数)详解
    Python与 PHP使用递归建立多层目录函数
    第一场个人图论专题
    poj_2762,弱连通
    word宏的问题
    fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏
  • 原文地址:https://www.cnblogs.com/lxjshuju/p/7231049.html
Copyright © 2011-2022 走看看