• 2018-06-13

    炮打TCP – 关于一而再再而三的粘包拆包问题的大字报

    Views: 4851 | 15 Comments

    TCP 所谓的粘包和拆包问题,是技术圈里最奇葩的问题之一!

    一而再,再而三,就跟傻逼的中国球迷支持中国足球队一样,前赴后继。有时候同一个人多次在犯同一个错误,有时候是前脚一个犯错了后脚又来一个还犯同样的错。即使是最优秀的程序员,也会在这个问题上面栽跟头,思维甚至很难转过弯,很久才能意识到自己的错误。而低水平的程序员就更不用说了,很多人到死都没有理解这个错误并解决掉,只是逃掉了而已。

    我们固然可以认为原因是某些人学艺不精,但那么多的人,其中包括无数的优秀程序员在 TCP 粘包和拆包问题在犯错误,难道我们不能说,这其实是 TCP 自身的原因吗?

    在我看来,这个问题的出现,原因就在于 TCP 协议是有原罪的 -- 也就是 TCP 协议所谓的“流式”协议。所以,我要炮轰 TCP!

    经过几十年的验证,除了几数几个网络协议会用到 TCP 所谓的流式特性之外,没有任何应用协议使用流式特性。我们必须承认,所有的应用层协议都是基于报文的协议,而不是流式协议。而某些名字中带有”流(Stream)"字样的协议,如 RTP,流媒体等,其本质是无数小体积的报文按顺序拼接而成,根本就和 TCP 的流式没有任何关系!

    那么我们就可以确定,数据的本质是报文,流数据是某类报文数据的一种伪称。事实,TCP 的流就是基于 IP 报文的。

    因为“流”是一个伪抽象的概念,所以流式协议是违反人的天性和事物的内在逻辑的。万物的本质是报文。这因为如此,”流”所引出的粘包拆包问题,就必然会一而再,再而三,大量地出现。

    炮轰之后,我们要怎么解决问题呢?

    由于 TCP 协议已经成为事实上的基础,所以淘汰掉 TCP 是不可想象的。我们要做的是,找到正确的编程代码,解决粘包拆包问题。经过无数人的探索,以及无数人一次又一次重复的愚蠢错误的反证,我发现了解决 TCP 粘包和拆包问题只有一条路径,没有第二条!我断言,所有和我的解决方案不同的代码,都是错误的。

    彻底解决 TCP 粘包和拆包问题的代码架构如下:

    char tmp[];
    Buffer buffer;
    // 网络循环:必须在一个循环中读取网络,因为网络数据是源源不断的。
    while(1){
        // 从TCP流中读取不定长度的一段流数据,不能保证读到的数据是你期望的长度
        tcp.read(tmp);
        // 将这段流数据和之前收到的流数据拼接到一起
        buffer.append(tmp);
        // 解析循环:必须在一个循环中解析报文,应对所谓的粘包
        while(1){
            // 尝试解析报文
            msg = parse(buffer);
            if(!msg){
                // 报文还没有准备好,糟糕,我们遇到拆包了!跳出解析循环,继续读网络。
                break;
            }
            // 将解析过的报文对应的流数据清除
            buffer.remove(msg.length);
            // 业务处理
            process(msg);
        }
    }
    

    这段代码是终极地解决 TCP 粘包和拆包问题的代码!

    这段代码之所以正确,是因为它包含了两个循环:网络循环和解析循环。

    网络循环用于从 TCP socket 中读取流式数据,每一次读取到的数据的长度是不可预期,也就是,读取到的数据长短不一,无法保证,这就是所谓“流式”引出的问题。

    而解析循环的功能是从拼接后流数据中,尝试解析出多个报文。注意,是多个报文,不是一个。因为所谓的粘包问题存在,所以可能是多个,而不是一个。如果解析不成功,那说明是遇到了拆包问题,我们继续读网络数据。

    你只需要死记硬背上面的正确代码即可。不死记硬背也一样,最终你还是要得出和我相同的结论写出和我一样的代码。那么,何不现在就死记硬背呢?

    最后,附上经典的错误代码:

    tcp.read(tmp, HEADER_LEN);
    header = parse_header(tmp);
    tcp.read(tmp, header.body_len);
    body = parse(tmp);
    

    这样的代码当然是错误的,这么简单代码怎么可能是对的?如果对了,TCP 还是 TCP 吗?

    如果你认为本文有用,请关注这个 GitHub 项目:https://github.com/ideawu/FUCK_TCP 让更多人一起炮打 TCP!

    该项目还提供了模拟粘包和拆包的代码,你如果不信邪,可以写一个自己的 client 试试。

    Posted by ideawu at 2018-06-13 15:05:11
  • 2018-05-10

    快速排序算法(QuickSort)的代码实现

    Views: 4741 | No Comments

    快速排序算法,也即快排,是递归和分而治之这两种计算机基本思想的应用,再加上其实现逻辑复杂度较好,性能较快,所以快速排序算法非常经典。

    快速排序算法经常作为面试算法题。快速排序算法本身并不复杂,其本身的逻辑非常简单,要掌握其思想不是难事,甚至基于其实现代码的形而上学的表面形状背下来也很轻松。但是,如果仅掌握了快速排序的思想以及代码表面形状,就认为自己懂了快速排序,就是没有真正地理解。

    快速排序算法作为面试题,一是考查理论结合实践的能力,要求面试者除了知道快速排序算法的实现逻辑和代码形状,还要知道快速排序为什么快,怎么快。二是考查编码细节的能力,考虑的是人经验之外的智商和思维水平。

    Continue reading »

    Posted by ideawu at 2018-05-10 19:46:06
  • 2017-06-15

    音频编码的一些笔记

    Views: 13680 | No Comments

    名词解释

    采样率/Sampling Rate/Sampling Frequency: 表示原始音频,每秒需要多少个值来表示(1秒时间内采样多少次)。
    采样位数/Sampling Bit Depth/bits per sample(bps): 用多少比特来存储一个采样值。
    采样比特率/Sampling Bit Rate: 指原始音频每秒需要多少比特来表示,显然等于 Rate x Bits。
    帧长/Frame Duration/Frame Lenght: 表示每帧(数据块)所表示原始音频播放需要的时长。
    帧大小/Frame Size: 表示每帧(数据块)所表示原始音频播放需要的存储空间。
    比特率/Bit Rate: 对于固定长度编码,表示编码后,每秒需要多少个比特来表示。

    bits per sample 和 bits per second 的缩写是相同的容易混淆。

    G.711 Codec

    G711A - G.711 a-law, alaw, 世界用。
    G711U - G.711 μ-law, ulaw, 北美用。

    Sampling Rate: 8KHz
    Sampling Bits: 8 bit

    Bit Rate: 64 kbit/s
    FrameDuration: 10 ms
    Frame Size: 640 bit, 80 Bytes
    RTP FrameDuration: 20 ms
    每 RTP 报文含 2 个 G.711 帧。

    iLBC - internet Low Bitrate Codec

    Sampling Rate: 8KHz
    Sampling Bits: 16 bit

    Bit Rate: 13.33 kbit/s
    Frame Duration: 30 ms
    Frame Size: 400 bit, 50 Bytes

    Bit Rate: 15.20 kbit/s
    FrameDuration: 20 ms
    Frame Size: 304 bit, 38 Bytes

    参考:CISCO - Voice Over IP - Per Call Bandwidth Consumption http://www.cisco.com/c/en/us/support/docs/voice/voice-quality/7934-bwidth-consume.html

    Posted by ideawu at 2017-06-15 14:59:43 Tags: , ,
  • 2017-06-02

    关于TCP粘包和拆包的终极解答

    Views: 16989 | 1 Comment

    程序员行业有一些奇怪的错误的观点(误解),这些误解非常之流行,而且持有这些错误观点的人经常言之凿凿,打死也不相信自己有错,实在让人啼笑皆非。究其原因,还是因为这些错误观点所对应的正确观点不符合人的正常思维习惯,是扭曲人的直观感受的。

    有两个错误观点非常之经典,一而再,再而三的出现,就跟韭菜一样,割不完,还越长越多。一是经典的"服务器最多65536个连接"误解,打开链接看介绍。另一个就是这里要讲的TCP"粘包"和"拆包"问题。

    基于前面的思路,我们先介绍人的正常思维习惯,然后再介绍扭曲的正确观点,这样你就会印象深刻了。

    Continue reading »

    Posted by ideawu at 2017-06-02 15:02:56
  • 2016-05-10

    一个 GUI 系统的组成部分

    Views: 13785 | No Comments

    在做 iOS 上的 XML+CSS UI 布局框架 CocoaUI 的过程中, 我体会到了 Apple 技术的强大之处, Apple 的底层框架和库提供了强大的功能和友好的 API, 我在开发 GUI 框架(上层 UI 框架)时用到的许多技术功能点都是信手拈来.

    现在总结, 有不少想法, 记下来分享一下.

    首先, iOS 系统为什么那么"好"? 不仅开发者觉得好, 最终用户用起来也觉得棒! 对比看 Android 的软件界面, 毛毛糙糙, 生硬, 总是缺了很多神韵. 这当然有屏幕硬件的功能, 但软件技术的作用更多, 因为从公开的资料上看, 不少 Android 厂商使用了和苹果同类的硬件.

    Continue reading »

    Posted by ideawu at 2016-05-10 16:31:03
  • 2016-04-04

    Nginx配置$request_uri与$uri变量的区别

    Views: 9051 | No Comments

    $request_uri

    这个变量就是HTTP头部的 path + query_string, 例如 /my/act?a=1.

    $uri

    这个变量对应到服务器上的一个文件(资源), 所以, 可能不等于 $uri, 因为可能被 rewrite 过. 例如浏览器请求 /my/act?a=1, 对应的资源(URI, $uri) 是 /dir/file.php, 当然, query_string 不属于 uri 的一部分.

    由此可见, $request_uri 这个变量的名字是有歧义的, URI 并不包含 query_string, 但这个变量却包含.

    Posted by ideawu at 2016-04-04 15:07:39 Tags:
|<<<123456789>>>| 1/11 Pages, 61 Results.