客户端与服务端网络通信和设计相关
概述
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密集型长时间对任务处理过长)
防止病人救治过长,导致其他病人无法接收治疗的情况一直处于等待(循环的临界值,死锁,导致线程假死状态无法
处理其他任务,致使资源浪费和服务稳定)
网络通信之流控
- client -> server 请求(write)流程,如下
tcp协议有滑动窗口的定义,窗口为0拒接数据流; 当洪流攻击时,程序系统不断调用read从recebuffer中copy到应用buffer, 上层处理缓慢会导致应用buffer内存不断扩大,最终导致内存溢出 所以需要设置高低水位,buffer达到maxValue,对当前 socket通道进行流控,不影响对外提供服务
- 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……