在时间戳选项诞生之前,TCP有三个问题难以解决:

(1)通信延迟RTT(Round Trip Time)测量

   RTT对于拥塞控制是十分重要的(比如计算多长时间重传数据)。通常,测量RTT的方法是发送一个报文,记录发送时间t1;当收到这个报文的确认时记录时间t2,t2 - t1就可以得到RTT。但TCP使用延迟确认机制,而且ACK可能会丢失,使得收到ACK时也无法确定是对哪个报文的回应。

(2)序列号快速回绕

  TCP判断数据是新是旧的方法是检查数据的序列号是否位于sun.una到sun.una + 2**31的范围内,而序列号空间的总大小为2*32,即约4.29G。在万兆局域网中,4.29G字节数据回绕只需几秒钟,这时TCP就无法准确判断数据的新旧。

(3)SYN Cookie的选项信息

  TCP开启SYN Cookie功能时由于Server在收到SYN请求后不保存连接,故SYN包中携带的选项(WScale、SACK)无法保存,当SYN Cookie验证通过、新连接建立之后,这些选项都无法开启。

  使用时间戳选项就可以解决上述问题。

  问题(1)解决方法:发送一个报文时将发送时间写入时间戳选项,在收到的ACK报文时通过其时间戳选项的回显值就能知道它确认的是什么时候发送的报文,用当前时间减去回显时间就可以得到一个RTT。

  问题(2)解决方法:收到一个报文时记录选项中的时间戳值,收到下一个报文时将其中的时间戳与上次的进行对比即可。时间戳回绕的速度只与对端主机时钟频率有关。Linux以本地时钟计数(jiffies)作为时间戳的值,假设时钟计数加1需要1ms,则需要约24.8天才能回绕一半,只要报文的生存时间小于这个值的话判断新旧数据就不会出错,这个功能被称为PAWS(Protect Against Wrapped Sequence numbers)。这样虽然可以解决问题(2),但随着硬件时钟频率的提升,时间戳回绕的速度也会加快,用时间戳解决序列号回绕问题的方法早晚会遇到困境。

  问题(3)解决方法:将WScale和SACK选项信息编码进32 bit的时间戳值中,建立连接时会收到ACK报文,将报文的时间戳选项的回显信息解码就可以还原WScale和SACK信息(这部分内容见《3.6 SYN Cookie》)。

  时间戳选项格式:

  其中,TSval是本端填写的时间戳,TSecr是回显给对端的时间戳。两端必须都分别在SYN包和SYN|ACK包中开启时间戳选项,时间戳功能才能生效。

  SYN包的时间戳选项在tcp_transmit_skb调用tcp_syn_options函数时设置:

 498 static unsigned int tcp_syn_options(struct sock *sk, struct sk_buff *skb,499                 struct tcp_out_options *opts,500                 struct tcp_md5sig_key **md5)501 {                      502     struct tcp_sock *tp = tcp_sk(sk);503     unsigned int remaining = MAX_TCP_OPTION_SPACE;504     struct tcp_fastopen_request *fastopen = tp->fastopen_req;505 506 #ifdef CONFIG_TCP_MD5SIG507     *md5 = tp->af_specific->md5_lookup(sk, sk);508     if (*md5) {509         opts->options |= OPTION_MD5;510         remaining -= TCPOLEN_MD5SIG_ALIGNED;511     }512 #else513     *md5 = NULL;514 #endif
...528     if (likely(sysctl_tcp_timestamps && *md5 == NULL)) {   //开启MD5选项就不能使用时间戳,why?529         opts->options |= OPTION_TS;530         opts->tsval = TCP_SKB_CB(skb)->when + tp->tsoffset; //设置时间戳531         opts->tsecr = tp->rx_opt.ts_recent; //设置回显值532         remaining -= TCPOLEN_TSTAMP_ALIGNED;533     }
...

  SYN|ACK报文的时间戳是在tcp_synack_options选项中设置:

 560 static unsigned int tcp_synack_options(struct sock *sk,561                    struct request_sock *req,562                    unsigned int mss, struct sk_buff *skb,563                    struct tcp_out_options *opts,564                    struct tcp_md5sig_key **md5,565                    struct tcp_fastopen_cookie *foc)566 {567     struct inet_request_sock *ireq = inet_rsk(req);568     unsigned int remaining = MAX_TCP_OPTION_SPACE;569 570 #ifdef CONFIG_TCP_MD5SIG571     *md5 = tcp_rsk(req)->af_specific->md5_lookup(sk, req);572     if (*md5) {573         opts->options |= OPTION_MD5;574         remaining -= TCPOLEN_MD5SIG_ALIGNED;575 576         /* We can't fit any SACK blocks in a packet with MD5 + TS577          * options. There was discussion about disabling SACK578          * rather than TS in order to fit in better with old,579          * buggy kernels, but that was deemed to be unnecessary.580          */581         ireq->tstamp_ok &= !ireq->sack_ok;582     }583 #else584     *md5 = NULL;585 #endif
...596     if (likely(ireq->tstamp_ok)) { //对端开启时间戳597         opts->options |= OPTION_TS;598         opts->tsval = TCP_SKB_CB(skb)->when;599         opts->tsecr = req->ts_recent;600         remaining -= TCPOLEN_TSTAMP_ALIGNED;601     }
...

   非SYN包中的时间戳选项由tcp_transmit_skb调用tcp_established_options函数填写:

 623 static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb,624                     struct tcp_out_options *opts,  625                     struct tcp_md5sig_key **md5)626 {627     struct tcp_skb_cb *tcb = skb ? TCP_SKB_CB(skb) : NULL;628     struct tcp_sock *tp = tcp_sk(sk);629     unsigned int size = 0;630     unsigned int eff_sacks;
...642     if (likely(tp->rx_opt.tstamp_ok)) { //对端开启时间戳选项643         opts->options |= OPTION_TS;    644         opts->tsval = tcb ? tcb->when + tp->tsoffset : 0;645         opts->tsecr = tp->rx_opt.ts_recent;646         size += TCPOLEN_TSTAMP_ALIGNED;647     }
...

  时间戳选项的值opts->tsval 由TCP_SKB_CB(skb)->when和tp->tsoffset构成(SYN|ACK的只有前者,why?),其中tp->tsoffset为用户使用TCP_TIMESTAMP socket选项设置的偏移值,TCP_SKB_CB(skb)->when为skb发送的时间:

1811 static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,
1812                int push_one, gfp_t gfp)
1813 {
...
1882         TCP_SKB_CB(skb)->when = tcp_time_stamp;
...

  tcp_time_stamp就是Linux的jiffies:

 674 #define tcp_time_stamp      ((__u32)(jiffies))

  tcp_options_write函数将选项写入报文:

 409 static void tcp_options_write(__be32 *ptr, struct tcp_sock *tp,410                   struct tcp_out_options *opts)411 {412     u16 options = opts->options;    /* mungable copy */
...428     if (likely(OPTION_TS & options)) {429         if (unlikely(OPTION_SACK_ADVERTISE & options)) { //如果有SACK_PERMIT选项则将其与时间戳选项写在一起以便节省空间430             *ptr++ = htonl((TCPOPT_SACK_PERM << 24) |431                        (TCPOLEN_SACK_PERM << 16) |432                        (TCPOPT_TIMESTAMP << 8) |433                        TCPOLEN_TIMESTAMP);434             options &= ~OPTION_SACK_ADVERTISE;435         } else {436             *ptr++ = htonl((TCPOPT_NOP << 24) |437                        (TCPOPT_NOP << 16) |438                        (TCPOPT_TIMESTAMP << 8) |439                        TCPOLEN_TIMESTAMP);440         }441         *ptr++ = htonl(opts->tsval); //写入时间戳值442         *ptr++ = htonl(opts->tsecr); //写入回显值443     }
...

  收到报文时 TCP使用tcp_parse_options函数解析时间戳选项:

 3481 void tcp_parse_options(const struct sk_buff *skb,
3482                struct tcp_options_received *opt_rx, int estab,
3483                struct tcp_fastopen_cookie *foc)
3484 {
3485     const unsigned char *ptr;
3486     const struct tcphdr *th = tcp_hdr(skb);
3487     int length = (th->doff * 4) - sizeof(struct tcphdr);
...
3534             case TCPOPT_TIMESTAMP:
3535                 if ((opsize == TCPOLEN_TIMESTAMP) &&
3536                     ((estab && opt_rx->tstamp_ok) || //建立状态且时间戳已经开启
3537                      (!estab && sysctl_tcp_timestamps))) { //非建立状态但时间戳允许开启
3538                     opt_rx->saw_tstamp = 1; //发现时间戳选项
3539                     opt_rx->rcv_tsval = get_unaligned_be32(ptr);
3540                     opt_rx->rcv_tsecr = get_unaligned_be32(ptr + 4);
3541                 }
3542                 break;
...

  另外, tcp_parse_aligned_timestamp函数用于快速解析报文中只有时间戳选项时的时间戳值:

3591 static bool tcp_parse_aligned_timestamp(struct tcp_sock *tp, const struct tcphdr *th)
3592 {
3593     const __be32 *ptr = (const __be32 *)(th + 1);
3594
3595     if (*ptr == htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16)
3596               | (TCPOPT_TIMESTAMP << 8) | TCPOLEN_TIMESTAMP)) {
3597         tp->rx_opt.saw_tstamp = 1;
3598         ++ptr;
3599         tp->rx_opt.rcv_tsval = ntohl(*ptr);
3600         ++ptr;
3601         if (*ptr)
3602             tp->rx_opt.rcv_tsecr = ntohl(*ptr) - tp->tsoffset;
3603         else
3604             tp->rx_opt.rcv_tsecr = 0;
3605         return true;
3606     }
3607     return false;
3608 }

  收到SYN或SYN|ACK时如果发现有时间戳选项则会设置tp->opt_rx.tstamp_ok为1。

  收到SYN时:

1465 int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
1466 {
1467     struct tcp_options_received tmp_opt;
...
1517     tcp_parse_options(skb, &tmp_opt, 0, want_cookie ? NULL : &foc);
1518
1519     if (want_cookie && !tmp_opt.saw_tstamp) //需要使用SYN Cookie但时间戳选项未开启
1520         tcp_clear_options(&tmp_opt); //清除选项信息,因为没法保存
1521
1522     tmp_opt.tstamp_ok = tmp_opt.saw_tstamp;
1523     tcp_openreq_init(req, &tmp_opt, skb); //将tmp_opt中的信息转存入request_sock中
...

  tcp_openreq_init:

1075 static inline void tcp_openreq_init(struct request_sock *req,
1076                     struct tcp_options_received *rx_opt,
1077                     struct sk_buff *skb)
1078 {
...
1087     req->ts_recent = rx_opt->saw_tstamp ? rx_opt->rcv_tsval : 0;
1088     ireq->tstamp_ok = rx_opt->tstamp_ok;
...

  收到SYN|ACK时:

5373 static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
5374                      const struct tcphdr *th, unsigned int len)
5375 {
...
5456         if (tp->rx_opt.saw_tstamp) {
5457             tp->rx_opt.tstamp_ok       = 1;
5458             tp->tcp_header_len =
5459                 sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED; //TCP头长度加上时间戳选项长度
5460             tp->advmss      -= TCPOLEN_TSTAMP_ALIGNED; //MSS减去时间戳选项长度
5461             tcp_store_ts_recent(tp); //记录时间戳的值用于回显
...

  tcp_store_ts_recent:

3271 static void tcp_store_ts_recent(struct tcp_sock *tp)
3272 {
3273     tp->rx_opt.ts_recent = tp->rx_opt.rcv_tsval; //用于回显,同时也用于PAWS
3274     tp->rx_opt.ts_recent_stamp = get_seconds(); //保存记录的时间
3275 }

  先来看看Linux TCP是如果利用时间戳解决问题(1)的。TCP接收数据时使用tcp_rcv_rtt_measure_ts函数用来计算RTT:

 459 static void tcp_rcv_rtt_update(struct tcp_sock *tp, u32 sample, int win_dep)
460 {
461     u32 new_sample = tp->rcv_rtt_est.rtt;
462     long m = sample;
463
464     if (m == 0)
465         m = 1;
466
467     if (new_sample != 0) {
468         /* If we sample in larger samples in the non-timestamp
469          * case, we could grossly overestimate the RTT especially
470          * with chatty applications or bulk transfer apps which
471          * are stalled on filesystem I/O.
472          *
473          * Also, since we are only going for a minimum in the
474          * non-timestamp case, we do not smooth things out
475          * else with timestamps disabled convergence takes too
476          * long.
477          */
478         if (!win_dep) {
479             m -= (new_sample >> 3);
480             new_sample += m;
481         } else {
482             m <<= 3;
483             if (m < new_sample)
484                 new_sample = m;
485         }
486     } else {
487         /* No previous measure. */
488         new_sample = m << 3;
489     }
490
491     if (tp->rcv_rtt_est.rtt != new_sample)
492         tp->rcv_rtt_est.rtt = new_sample;
493 }
...
508 static inline void tcp_rcv_rtt_measure_ts(struct sock *sk,509                       const struct sk_buff *skb)510 {511     struct tcp_sock *tp = tcp_sk(sk);512     if (tp->rx_opt.rcv_tsecr && //对端回显的时间戳,可以看做数据发送时间513         (TCP_SKB_CB(skb)->end_seq -514          TCP_SKB_CB(skb)->seq >= inet_csk(sk)->icsk_ack.rcv_mss)) //skb中的数据大于一个MSS,意味着数据稳定传输515         tcp_rcv_rtt_update(tp, tcp_time_stamp - tp->rx_opt.rcv_tsecr, 0); //当前时间可以看做数据接收时间,用当前时间减去数据发送的时间去更新RTT516 }

  收到ACK时在tcp_clean_rtx_queue函数中使用tcp_ack_update_rtt来更新RTT:

3001 static int tcp_clean_rtx_queue(struct sock *sk, int prior_fackets,
3002                    u32 prior_snd_una)
3003 {
...
3095     if (flag & FLAG_ACKED) {
...
3104         tcp_ack_update_rtt(sk, flag, seq_rtt); //用当前时间减去数据发送的时间去更新RTT
...

  tcp_ack_update_rtt用有时间戳和没有时间戳两种方式更新RTT:

2866 static void tcp_ack_saw_tstamp(struct sock *sk, int flag)
...
2883     struct tcp_sock *tp = tcp_sk(sk);
2884
2885     tcp_valid_rtt_meas(sk, tcp_time_stamp - tp->rx_opt.rcv_tsecr);
2886 }
...
2905 static inline void tcp_ack_update_rtt(struct sock *sk, const int flag,
2906                       const s32 seq_rtt)
2907 {
2908     const struct tcp_sock *tp = tcp_sk(sk);
2909     /* Note that peer MAY send zero echo. In this case it is ignored. (rfc1323) */
2910     if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr) //有时间戳选项且回显值非0
2911         tcp_ack_saw_tstamp(sk, flag);
2912     else if (seq_rtt >= 0) 
2913         tcp_ack_no_tstamp(sk, seq_rtt, flag);
2914 }

  再来看问题(2)。收到包时 TCP在tcp_rcv_established函数中会检查时间戳:

 5076 int tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
5077             const struct tcphdr *th, unsigned int len)
5078 {
5079     struct tcp_sock *tp = tcp_sk(sk);
...
5119         /* Check timestamp */
5120         if (tcp_header_len == sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) { //TCP报头中只有时间戳选项
5121             /* No? Slow path! */
5122             if (!tcp_parse_aligned_timestamp(tp, th)) //快速解析时间戳选项失败?
5123                 goto slow_path;
5124
5125             /* If PAWS failed, check it more carefully in slow path */
5126             if ((s32)(tp->rx_opt.rcv_tsval - tp->rx_opt.ts_recent) < 0) //当前包的时间戳小于上次收到的包的时间戳
5127                 goto slow_path; //并不意味着一定是旧包,需仔细检查
5128
5129             /* DO NOT update ts_recent here, if checksum fails
5130              * and timestamp was corrupted part, it will result
5131              * in a hung connection since we will drop all
5132              * future packets due to the PAWS test.
5133              */
5134         }
...
5251 slow_path:
...
5262     if (!tcp_validate_incoming(sk, skb, th, 1))
5263         return 0;
...

   tcp_validate_incoming函数中会仔细检查时间戳:

 3696 static int tcp_disordered_ack(const struct sock *sk, const struct sk_buff *skb)
3697 {
3698     const struct tcp_sock *tp = tcp_sk(sk);
3699     const struct tcphdr *th = tcp_hdr(skb);
3700     u32 seq = TCP_SKB_CB(skb)->seq;
3701     u32 ack = TCP_SKB_CB(skb)->ack_seq;
3702
3703     return (/* 1. Pure ACK with correct sequence number. */
3704         (th->ack && seq == TCP_SKB_CB(skb)->end_seq && seq == tp->rcv_nxt) &&
3705
3706         /* 2. ... and duplicate ACK. */
3707         ack == tp->snd_una &&
3708
3709         /* 3. ... and does not update window. */
3710         !tcp_may_update_window(tp, ack, seq, ntohs(th->window) << tp->rx_opt.snd_wscale) &&
3711
3712         /* 4. ... and sits in replay window. */
3713         (s32)(tp->rx_opt.ts_recent - tp->rx_opt.rcv_tsval) <= (inet_csk(sk)->icsk_rto * 1024) / HZ);
3714 }
3715
3716 static inline bool tcp_paws_discard(const struct sock *sk,
3717                    const struct sk_buff *skb)
3718 {
3719     const struct tcp_sock *tp = tcp_sk(sk);
3720
3721     return !tcp_paws_check(&tp->rx_opt, TCP_PAWS_WINDOW) && //TCP_PAWS_WINDOW的值为1
3722            !tcp_disordered_ack(sk, skb);
3723 }
...
4985 static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb,
4986                   const struct tcphdr *th, int syn_inerr)
4987 {
4988     struct tcp_sock *tp = tcp_sk(sk);
4989
4990     /* RFC1323: H1. Apply PAWS check first. */
4991     if (tcp_fast_parse_options(skb, th, tp) && tp->rx_opt.saw_tstamp &&
4992         tcp_paws_discard(sk, skb)) { //tcp_paws_discard为真意味着PAWS检查失败
4993         if (!th->rst) {
4994             NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSESTABREJECTED);
4995             tcp_send_dupack(sk, skb);
4996             goto discard;
4997         }
4998         /* Reset is accepted even if it did not pass PAWS. */
4999     }
...

   tcp_paws_check函数如果返回true则PAWS通过:

1143 static inline bool tcp_paws_check(const struct tcp_options_received *rx_opt,
1144                   int paws_win)
1145 {
1146     if ((s32)(rx_opt->ts_recent - rx_opt->rcv_tsval) <= paws_win) 
1147         return true; //当前包的时间戳只能比上次收到包的时间戳早少于2个jiffies,再多了就会被认为是旧包
1148     if (unlikely(get_seconds() >= rx_opt->ts_recent_stamp + TCP_PAWS_24DAYS)) //从上次收到包到现在经历的时间多于24天
1149         return true;
1150     /*
1151      * Some OSes send SYN and SYNACK messages with tsval=0 tsecr=0,
1152      * then following tcp messages have valid values. Ignore 0 value,
1153      * or else 'negative' tsval might forbid us to accept their packets.
1154      */
1155     if (!rx_opt->ts_recent)  //没有上次收到包的时间戳的记录
1156         return true; //有些OS会在SYN和SYN|ACK中发送0值的时间戳和回显,但在随后的报文中会携带正常的值。对于这种情况还是先让它通过吧
1157     return false;
1158 }

  看来,如果一个TCP连接连续24天不收发数据则在接收第一个包时基于时间戳的PAWS会失效,否则当前包就会被当做旧包丢弃。

  现在讨论一下基于时间戳的PAWS的潜在问题。如果jiffies 1ms加1,则时间戳回绕需要24.8天;如果jiffies提高到1us加1,则回绕需要约71.58分钟才能回绕,这时问题也不大,因为网络中旧报文几乎不可能生存超过70分钟,只是如果70分钟没有报文收发则会有一个包越过PAWS(这种情况会比较多见,相比之下24天没有数据传输的TCP连接少之又少),但除非这个包碰巧是序列号回绕的旧数据包而被放入接收队列(太巧了吧),否则也不会有问题;但如果主机的时钟频率提高到jiffies每0.1us加1呢?回绕需要7分钟多一点,这时就可能会有问题了:

(1)连接如果7分钟没有数据收发就会有一个报文越过PAWS,对于TCP连接而言这么短的时间内没有数据交互太常见了吧!这样的话会频繁有包越过PAWS检查,从而使得旧包混入数据中的概率大大增加

(2)高时钟频率条件下数据的发送和接收应该都是十分迅速的,TCP序列号的回绕速度应该快于时间戳的回绕速度,而旧报文的网络中的生存时间是否会大于7分钟呢?如果不是,则不会有问题;否则就很危险了。可以考虑以下解决方案:

1)增加时间戳的大小,由32 bit扩大到64bit

这样虽然可以在能够预见的未来解决时间戳回绕的问题,但会导致新旧协议兼容性问题,像现在的IPv4与IPv6一样

2)将一个与时钟频率无关的值作为时间戳,时钟频率可以增加但时间戳的增速不变

随着时钟频率的提高,TCP在相同时间内能够收发的包也会越来越多。如果时间戳的增速不变,则会有越来越多的报文使用相同的时间戳。这种趋势到达一定程度则时间戳就会失去意义,除非在可预见的未来这种情况不会发生。

3)暂时没想到

8.3 时间戳(Time Stamp)选项相关推荐

  1. tcpdump 命令的个常用选项:三

    tcpdump用于捕获和分析网络流量.系统管理员可以使用它来查看实时流量或将输出保存到文件中并在以后进行分析.下面列出6个常用选项 基于 TCP 标志的过滤器 可以根据各种 tcp 标志过滤 TCP ...

  2. tcp时间戳 引起的网站不能访问

    目录 问题现象 TCP时间戳说明 解决方法 什么情况下出现这个问题 问题现象 访问一个我们新接入的业务接口,能ping通他们的站点,第一次telnet对端接口会通,第二次.第三次... 一直做下去就不 ...

  3. 文件管理,文件判断,时间戳,通配符类命令

    Linux文件类型: 线性设备:也称为字符设备,如键盘等.按照顺序来访问. 块设备:随机访问设备,如硬盘.读取磁盘不按照顺序读取,无先后顺序之分. 命名管道文件:负责将一个进程的信息传递给另一个进程, ...

  4. proguard配置选项

    日常开发,打包上线App的时候,混淆几乎不可避免,除非你想裸奔.  混淆的命令不多,但容易弄混.翻译小结一下,方便以后查阅.  官方文档  http://proguard.sourceforge.ne ...

  5. tcpdump 命令的常用选项:三

    tcpdump用于捕获和分析网络流量.系统管理员可以使用它来查看实时流量或将输出保存到文件中并在以后进行分析.下面列出6个常用选项 基于 TCP 标志的过滤器 可以根据各种 tcp 标志过滤 TCP ...

  6. 时间戳、中国标准时间、年月日三种时间格式转换

    以2022年4月9号为例,列出三种时间格式形式: 时间戳-格式: 1649462400000 中国标准时间-格式: Sat Apr 09 2022 08:00:00 GMT+0800 (中国标准时间) ...

  7. Linux系统的常用命令的使用

    常用的命令使用方法: 1.echo echo - display a line of text 经过man手册帮助后获得更精准的命令操作方法 将字符串输出到显示设备上 常用选项: -n:不换行 ,如图 ...

  8. python列表乘数值_《利用Python进行数据分析》十一章· 时间序列·学习笔记(一)...

    一.时间序列 时间序列(time series)数据是一种重要的结构化数据形式,应用于多个领域,包括金融学.经济学.生态学.神经科学.物理学等.在多个时间点观察或测量到的任何事物都可以形成一段时间序列 ...

  9. IP,TCP 和 HTTP

    IP,TCP 和 HTTP  sunset  16 Apr 2014  分享文章 IP属于网络层,TCP属于传输层,HTTP属于应用层 TCP是基于IP的,HTTP是基于TCP的 IP主要是路径分发, ...

  10. 数据分析-numpy-pandas-matplotlib

    NumPy NumPy的ndarray 一种多维度数组对象 创建ndarry 使用array函数.它接受一切序列型的对象(包括其他数组) data2 = [[1, 2, 3, 4], [5, 6, 7 ...

最新文章

  1. assert python 中断_Python这十大装B语法!你不会还没有听过吧?
  2. day01-计算机操作系统java编程入门
  3. 魔幻艰难的2020上半年!
  4. php基础教程 第一步 环境配置及helloworld
  5. linux80端口检查,Linux下基于端口的服务检查脚本
  6. 日常小记录json文件(json.load()、json.loads()、json.dump()、json.dumps())
  7. Django基础-Web框架-URL路由
  8. cad批量偏移_永远都不会卸载的几款CAD插件,月入过万不要太轻松。
  9. seaborn常用速查手册
  10. pytorch---之torch.manual_seed()
  11. BF2 战地风云2常见问题解答
  12. 光山二高2021高考成绩查询,光山县第二高级中学2019高考成绩和历年成绩汇总
  13. Linux中tshark(wireshark)抓包工具使用方法详解
  14. excel常用函数汇总 excel最常用的八个函数 excel自动求减
  15. NDK学习笔记-NDK开发流程
  16. 文件后缀和相应的文件类型,打开方法参考大全
  17. python flag用法_Python之FLAGS用法
  18. Sorry ,中产 -20160929
  19. Android X86更改屏幕分辨率
  20. 如何计算通信中的信噪比SNR

热门文章

  1. 王菲语法11 动词(非谓语动词)
  2. 区块链开发的权威指南
  3. 第5章 以太网与FlexRay
  4. python socket通信 心跳_python socket 编程之三:长连接、短连接以及心跳(转药师Aric的文章)-阿里云开发者社区...
  5. 机器翻译与编码-解码模型 Machine Translation and Encoder-Decoder Models
  6. log4cplus的各种坑
  7. 【Shell 脚本速成】01、编程语言与 Shell 脚本介绍
  8. c51抢答器程序汇编语言,c51单片机汇编语言单片机八位抢答器程序
  9. 网络协议之:基于 UDP 的高速数据传输协议 UDT
  10. 英文版本Ubuntu下添加中文拼音输入法: Chinese (Intelligent Pinyin)