客户端与服务端网络通信和设计相关

概述

OSI 七层模型:

实际网络通信保证可靠性是很大挑战,总结出主要几点:
   1、网络最大传输单元(MTU),以太网最大MTU 1500, ipv4 最小MTU 578, ipv6 最小MTU 1280
   (目前主机普遍MTU为1500,但传输中途的主机路由不一定为1500,也会导致分包组包的消耗,极端情况(被攻击等)
   导致目标主机内存消耗大,需要分片数据暂存接收缓冲区后续直到最后一个ip分片包到达或组包超时丢弃释放资源)
   
   2、应用程序保证可达可靠,依赖通信协议定义是否有ACK(确认)机制,TCP协议栈自身ACK机制依旧无法达到.
   
   3、linux内核网络子系统中协议栈如TCP、UDP等发收缓冲区是有界的,保证可靠性应用程序就增加复杂性,
    需要新增Buffer缓冲区保证可靠性,随着极端情况Buffer不断增大还需考虑程序稳定性引入限流等功能。
 
  4、不确定数据是否到达目标,引入重传机制,如需要保证Exactly once delivery的话引入程序复杂度,加大
   开销成本

网络通信流程

通信就如: 一次生病去医院看病的过程

备注:目标主机 => hospital,源主机 => home , 公交站台 => 中间路由或主机

准备到达医院看病有多种选择,掏出地图(DNS)查询医院具体地址,知道具体地址,还需查询(ipv4 ARP,ipv6
 NDP)医院资质(mac 物理地址).防止遇到莆田系列导致损失极大.

最快情况:
出发看病本来坐直达公交是最快的

次选方案:
出发看病本来坐直达公交是最快的,但是当前路线塞车严重,取而次之选择其他路线(中间路由中转),到达医院

忘记带身份证或社保卡(组包情况):
直达路线到达医院,准备挂号发现忘记带社保卡,就打电话给女主人送社保卡过来,直达路线正好是塞车高峰,
选择其他路线到达医院传递社保卡(ip分片情况,因为路线不明确所以不能在中间路由组包,只能在目标主机)

最坏情况:
因为塞车严重或医院当天科室挂号已经满了等等情况,导致了当天不能治病,回去之后发现下午在医院挂上号
于是下午又再次出发去医院治病(TCP内核模块的重发机制,超时重传和快速重传)

汇报情况(确认机制):
男主人在医院治疗时间有点长,发现家中有小孩还需要吃饭,于是就打电话到家中座机叫小主人自己外面吃饭,
防止小主人一直等待,当心,不知情况(可靠需要通过目标主机响应才知道是否到达)

意外情况:
公交车在路上发生车祸,掉进海里,警察到达事故现场后查明车中人员情况,如果查到有被害人家属信息,
然后电话通知被害人的家属,家人遇险
(中间路由接收缓冲区溢出,导致丢包等情况,反馈ICMP报文,为了安全问题不一定会反馈,或按照一定速率控制)

 

服务端处理流程

一家医院(一台服务器)资源有限,一个医生一次只能接收一位病人,如下图:

医院科室的医生数量有限,每个医生(代指线程)一次只能处理病人,先去科室挂号,然后分配医生,
或预约挂号(VIP用户)指定高级医师(优先处理或专门资源处理)。

为了防止医生过于劳累会影响看病人效率导致病人越来越来(任务堆积过多),每天当天挂号
和预约当天的挂号的病人数量有限(服务是需要做限流处理,防止服务承受压力超过阀值)

防止过劳处理(IO密集型长时间对任务处理过长)

防止病人救治过长,导致其他病人无法接收治疗的情况一直处于等待(循环的临界值,死锁,导致线程假死状态无法
 处理其他任务,致使资源浪费和服务稳定)


网络通信之流控

  1. client -> server 请求(write)流程,如下 

 

tcp协议有滑动窗口的定义,窗口为0拒接数据流;
当洪流攻击时,程序系统不断调用read从recebuffer中copy到应用buffer,
上层处理缓慢会导致应用buffer内存不断扩大,最终导致内存溢出
所以需要设置高低水位,buffer达到maxValue,对当前
socket通道进行流控,不影响对外提供服务

  1. server -> client 请求(write)流程,如下 

socket缓冲区都是有界,需保证数据可靠性,增加额外的 Outbuffer 保证 write 到 kenel 中 sendbuffer 满了写入为0字节
原因:tcp协议栈接受到对端ack才会释放对应的区域(有 SACK 机制快速重传和快速回复来缩短时耗);速率太快(IO 密集型情况 read > write)
或者第三方攻击不响应Ack延迟最大响应等等导致

实现

read示例伪代码

/**在read操作是添加高低水开关,防止一致read导致buffer
不断扩大*/
for (; ; ) {
        try {
            try {
                selector.select(1000);
                Iterator ite = selector.selectedKeys().iterator();
                while (ite.hasNext()) {
                    SelectionKey key = (SelectionKey) ite.next();
                    ite.remove();
                    if (key.isAcceptable()) {
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel channel = server.accept();
                        channel.configureBlocking(false);
                        channel.register(selector, SelectionKey.OP_READ);
                    } else if (key.isReadable() && isRead) {
                        //执行逻辑
                    }
                }
            } catch (IOException e) {

            }
        } catch (Throwable e) {
        }
    }

网络通信,防止read>write,堆积过多,处理不及时会
导致资源浪费,服务不稳定,流控措施策略必须添加,
也是防止第三方洪流攻击的措施之一

 

 

总结

一个服务的可靠性、稳定性、性能是很复杂,任何一个环节都能导致问题

TCP内核协议栈的重试发送次数,握手交互超时设置,TIME-WAIT状态下配置或快速回收,多服务侦听同一 ip:port设置等等。
dns查询优化的配置,
arp缓存等众多因素都会产生影响

ping 查看路由跳转次数,和往返计算的大致时间,判断网络状况是否正常., traceroute 查看src =》 dst 经过的路由情况

lsof 查看文件句柄相关,程序是否存在文件句柄(包括socketFD)泄露,导致达到定义的数量限制 ulimit -n 查看   
注释:tcp连接处理的半连接和连接队列是临时作为缓冲区,因为一次不能同时处理(当前并发连接数量),应用程序   
系统调用accept就被弹出,实际长连接(TCP)数一般epoll上限是FileMax ,通过 ulimit -n 查看

netstat 查看网络当前状态,
连接端地址是否有异常,多数相同ip地址或同一地点的需要注意是否被攻击,一条连接 靠两元组ip:port确定,
生产环境一个相同ip几万端口就有可能爬虫和被攻击之类的 

还有很多实用工具iostat,free,meminfo,vmstat……
posted @ 2019-05-23 10:44  cat_with_mouse  阅读(563)  评论(0编辑  收藏  举报