谈网络中的数据收发处理

事先说明下情况:
写socket 的时候,一般都是会在 数据包 加上【包头】,指示这个数据包的size。

(消息防篡改可能还会加密, 再校验)
可以参考【对称加密算法】,【非对称加密算法】

0.网络拥堵问题

- 无线移动 信号不稳定
- 有线带宽占满,网络阻塞。阻塞超时,会重传。  

     (超时越多,重传越多)↓
   ↑(重传越多,超时越多)


    严重的场合,会陷入循环。

    所有才有下面,修改



- 有的操作系统或者程序,会有优化处理  
     * windows的 [Nagle](https://en.wikipedia.org/wiki/Nagle%27s_algorithm)
     * google BBR 会自动根据网络状况,修改滑动窗口大小。  [google BBR](https://github.com/google/bbr)

所以 网络上TCP协议的校验, 是TCP协议上的。为了做重传。

但实际结果:延时高的时候,当然数据不一定正确 or 不一定能传送到
这个时候,就需要我们自己定义包头, 收发端 一起来规范格式。

防止数据出问题,可以在业务层 做处理, TCP协议毕竟是80~90年代的产物,不是那么靠谱。
( 当然最近版的linux 内核好像对网络层 改动很大。提高了很多性能。

微软估计也是因为技术创新这点,才拥抱Linux的吧。 )

1.网络中的 数据包结构设计

一般是  { [package size]   [ md5  ]    [buffer  .....  ]  }
 size:     4Byte       32byete          packageSize
- package头的size 是固定的长度, (里面一般是[buffer]的size)
- md5 size 随意,看你自己要生成多少位的数据了,一般是32个字符, 32byte   (不一定非要MD5  可以用其他校验方式)
- buffer   一般是对这个buffer 进行MD5, package size就是他的长度。

2.是否 有【粘包】这个概念

一般是指数据包 ,每次recv 大小不明确,

- 2.1 正常情况
        - 如果先 detach() recv thread,再 detach() send thread,就没有问题。这是没有问题的。

- 2.2 异常情况
        - 如果先 detach() send thread,再 detach() recv thread. 如果另一端,收到后立马再发送。 
    
           则因为,send() 之后,没有recv(), 导致前面send()的数据包可能丢失。
           那么这次 recv()的数据,包含上次(上上次, 上上上次)远程 echo的数据, 所以会导致数据 变成以下状态.(可能完整,可能不完整)
           
            //假设一个完整数据包就是 "data"字符
           * ta...}{data... }  //缺少da
           *{data...}{da...    //缺少ta
           *{data...}{data...} //运气好,没有丢失数据
           

        - 还有一种是,socket开启后,recv()正常,然后再shutdownRecv() 没有接收数据,在缓存中,  下次开启recv()时,导致网络中(接受方网络硬件中的缓存)也会出现以上问题。
            当发送方send()数据后,
                数据可能在网络中(光纤线路, 机房路由中),
                可能在你的路由器,
                可能在 你的主机 网卡缓存中(你还没有取), 
                可能 recv()处理出错
                可能 你正确recv()数据后, 你的业务流程 却没有处理(软件崩溃,断电,资源耗尽 等等)
                必须完成业务逻辑, 进行持久化.  才能完整一个业务流程.  
            中间任何环节都可能存在问题.

结论:

    一般是没有网络粘包这个说法的。 一般情况下 TCP的SYN 序号,和重传  已经足够处理大部分网络阻塞,延时需求  
    只不过 有的场合 会出现点意外,如果业务设计 做到足够好,就可以不用管粘包
    就像陈硕说的,服务器并不是要保证7*24小时正常运行,而是无论哪台机器,出问题之后,还能保证业务100%不出错

        无论哪种,处理数据不知道每个包的大小,处理起来就格外麻烦。所以需要处理socket 业务的时候好好考虑下。
        1.包格式
        2.异常的时候,该结束  还是独自解析粘包的数据?(业务设计的时候,如何保持强一致性)  ==》回复接收成功
        3.包的校验
相关: setsockopt 设置 SO_LINGER 选项, 保证socket close之前,把数据发送出去。

3.网络IO模型

简单网络IO模型有(不讨论API,网上资源已经足够多了。 linux 可以在这里查看资料 https://www.kernel.org/doc/man-pages/

- 阻塞
    * 一recv,一send
    * 一recv,多send
    
- 非阻塞
    * send thread, recv thread(哪有业务,就处理哪,效率最高)


- 半阻塞
    * send 为非阻塞,recv 为阻塞
    * recv 为非阻塞,send 为阻塞

其实我一直觉得 [Registered I/O] 就是微软对Linux [epoll]的致敬。

  • windows:select/iocp/[Registered I/O]
  • linux:select/poll/ epoll
  • freebsd:kqueue

转载请注明来源-

原文地址:https://www.cnblogs.com/scotth/p/6837605.html