24. 从零用Rust编写正反向代理,细说HTTP行为中的几种定时器 ...

打印 上一主题 下一主题

主题 868|帖子 868|积分 2604

wmproxy

wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,内网穿透,后续将实现websocket代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子
项目地址

国内: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
敏感的时间

  现实生活中大家都对时间有着概念,比如“快上班了,要不然要迟到了。”、“这班怎么这么久,怎么还没下班?”、“啊?已经晚上12点啦,等我这把游戏玩完。”、“叮叮叮,起床闹钟一直在催着你起床了。”
  闹钟、自然变化、生物钟为我们提供着时间的保证。而计算机的世界里,就靠着硬件定时器,控制着时间的流逝,如果哪一天本地时间和别人的时间不一致了,此时需要找别人对时,这也就是经典的网络时间协议(NTP)。
  现实的生活中,通常以分钟或者小时乃至天去和别人约定时间,而在计算机的世界里,在我们看来那就是朝生暮死的蚍蜉一般,他们生命较短,所以对他们来说,通常用s或者ms来乃至μs做通知,所以需要严格的遵守时间约定,绝对不允许有赖床的行为。
HTTP行为中的定时器

HTTP/HTTPS/WebSocket的访问撑起了互联网总流量的半臂江山,日常接触中APP或者游戏这种对外服务的基本上均是通过HTTP及HTTPS进行服务,长链接由于兼容小程序这类的大部分游戏直接也从普通的Socket转为WebSocket直接做全网的兼容。WebSocket的基础协议是由HTTP升级而来,所以也归于HTTP协议。
主要有以下列行为定时器

  • 连接超时定时器
  • 读操作超时定时器
  • 写操作超时定时器
  • 读/写操作超时定时器(在规则的时间内同时完成读和写)
  • keep-alive超时定时器(连接保持的最长时间)
定时器类的定义

相关的超时数据均存放在该类里,接下来类型进行相应的处理
  1. #[derive(Debug)]
  2. pub struct TimeoutLayer {
  3.     pub connect_timeout: Option<Duration>,
  4.     pub read_timeout: Option<Duration>,
  5.     pub write_timeout: Option<Duration>,
  6.     pub timeout: Option<Duration>,
  7.     /// keep alive 超时时长
  8.     pub ka_timeout: Option<Duration>,
  9.     read_timeout_sleep: Option<Pin<Box<Sleep>>>,
  10.     write_timeout_sleep: Option<Pin<Box<Sleep>>>,
  11.     timeout_sleep: Option<Pin<Box<Sleep>>>,
  12.     ka_timeout_sleep: Option<Pin<Box<Sleep>>>,
  13. }
复制代码
连接超时定时器

  此时约定的是客户端向服务端请求TCP连接建立的最长时间,如果没有约定,将由系统连接超时的或者确认不可达的时候才返回失败。
  如果没有连接超时,那么以下我们的型业务场景模拟:

  • 在弱网的环境下,比如手机信号不好的地方,一次连接请求建立可能会花费10秒左右才能返回,那么如果我们没有连接超时,用户在等待了8秒,客户端的界面都没有办法给出任何的响应,就会认为服务端出现了问题或者频繁的重启客户端应用不断的进行重试而得不到预期的反馈。在App设计中,对这种情况的用户体验极差,需要在指定的时间内给用户反馈出当前网络无法访问。
  • 客户端的设计模型中,如果有定时请求或者埋点数据之类,如果没有超时机制,容易出现短时间内打开的socket过多出现资源耗尽的情况,其次客户端不知道是否将数据已经进行发送,不确认是否需要将该条数据进行缓存以便下一次推送。
在设计模型中,连接超时必不可少,因为各种业务场景的不同,需要要不同的时间内得到预期的反馈。以下是连接超时在Rust中的实现,因为只存在于客户端主动连接服务端,所以连接超时只在客户端实现。
  1. async fn inner_connect<A: ToSocketAddrs>(&self, addr: A) -> ProtResult<TcpStream> {
  2.     if self.inner.timeout.is_some() {
  3.         // 获取是否配置了连接超时, 如果有连接超时那么指定timeout
  4.         if let Some(connect) = &self.inner.timeout.as_ref().unwrap().connect_timeout {
  5.             match tokio::time::timeout(*connect, TcpStream::connect(addr)).await {
  6.                 Ok(v) => {
  7.                     return Ok(v?)
  8.                 }
  9.                 Err(_) => return Err(ProtError::Extension("connect timeout")),
  10.             }
  11.         }
  12.     }
  13.     let tcp = TcpStream::connect(addr).await?;
  14.     Ok(tcp)
  15. }
复制代码
通过指定超时时间来对连接的建立监听。
读操作超时定时器

大部分HTTP请求,只有得到完整的数据才能进行处理,少部分如文件上传这种可以边上传边操作,而是否能开始操作关系到请求的响应时间。
读超时大概有以下的可能:

  • 服务器处理请求的时间太长,导致客户端等待超时。
  • 服务器返回的数据量过大,导致客户端读取数据的时间超过了规定的时间。
  • 网络延迟或网络不稳定,导致客户端无法在规定的时间内读取完数据。
这造成客户端无法及时处理数据,可以报错好让客户端换备用线路或者备用服务器等以便及时的处理数据。
读操作我们不管服务端或者客户端,不管http/1.1或者http/2均由is_read_end字段来判定,在未读完当前请求的数据前,判断是否超时。
[code]pub fn poll_ready(    &mut self,    cx: &mut Context
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

何小豆儿在此

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表