点击蓝字关注我哦

以下是本期干货视频视频后还附有文字版本哦

▼《面试官:TCP真的可靠吗》▼

ps:请在WiFi环境下打开,如果有钱任性请随意

TCP真的可靠吗

面试官经常会问的一个问题是,如果TCP服务器宕机了,会发生什么?换句话说,TCP真的可靠吗?这个问题要从两个方面来回答:1.TCP是个可靠的协议,怎么保证它可靠的。2.TCP并不能保证它所发送数据的可靠传输。

TCP如何保证可靠性?

首先,我们看看数据报不可靠有哪些问题,以及TCP是怎么解决的?1.差错。TCP通过首部的校验和,可以校验首部和和数据。这是一种端到端的校验,目的是检测数据在传输过程中的任何变化,如果收到对端的校验和有差错,TCP将这个包丢弃并且不确认。2.丢包。TCP发出一个数据包后,启动一个定时器,等待对端确认收到这个数据包,如果不能及时收到这个确认,将重发这个报文。3.失序。TCP承载于IP数据包来传输,IP包的到达可能会失序,因此TCP数据包的到达也可能失序,TCP对收到的数据包按照首部的序列号进行重新排序。4.重复。IP数据包会发生重复,TCP接收端根据TCP首部的序列号将重复的数据丢弃。此外,确认数据包,也不能是确认了一个数据包再发送下一个数据包,这不利于并行的批量发送,我们可以批量的发送,再批量的确认。这里有两个问题需要考虑:1.接收方的处理能力,2.网络的处理能力。1.首先来看看接收方的处理能力。当接收方的硬件能力不如发送方,或者是系统繁忙,那发送过去的报文只能丢弃。要限制发送方的发送速度,接收方就要告诉发送方它的处理能力,好让发送发方限制它的发送速度就可以了,这就是滑动窗口的由来。2.下面来看看网络处理能力。如果发送TCP数据包的速度快于中间某个路由器的发速度,路由器就开始丢包。导致较高的丢包率,如果TCP继续保持这个速度送数据,那么网络的性能就会极大的降低。这就需要拥塞控制算法。它分为两分,一个是慢启动,一个是拥塞避免。慢启动指的就是TCP在一开始发送数据的时候以低速传输,只要能够得到对应报文的ACK,就以指数级的速度提高速率。当增长到一个阈值时,增长速度就变成线性增长,而不是指数级的。或者是丢包严重了,说明网络出现拥塞,要降低发送速率,进入拥塞避免阶段。

TCP并不能保证它所发送数据的可靠传输

可靠指的是什么,不可靠指的是什么?

上面我们讨论了TCP通过很多机制保证可靠,这种可靠只是在端到端的通信上。假设数据从A进程送到B进程,数据从A进程通过它所在主机TCP/IP协议栈向下传输,经过若干台路由器,通过进程B所在主机的TCP/IP协议栈向上传输,最后到达B进程。这些路由器没有TCP层,只是转发IP数据报,IP是个不可靠的协议。

TCP能够向进程B保证所有到达的数据是按序且未受损的。但有个问题, TCP已经ACK的数据包实际上不一定会抵达应用进程。比如,接收端TCP刚对数据进行ACK,但应用程序还没有读走,就崩溃了。

针对TCP的ACK的数据报不能抵达目的应用程序的解决方案

我们的解决方案是应用层ACK。下面给一个简单的实现,我们采用停等的方式来实现回射客户服务器。

这里设计成客户端和服务器有两条通道,主要的原因是想让发送数据模块和接收网络数据模块都能够获取网络中对端的状态,而不是将状态混在一条通道上。这里给大家实现向外发送数据模块。实现思路是:发送一条消息后,在定时器到之前必须接收对等实体发过来的应用层ACK,如果定时器时间到,我们就终止程序,当然,你可以实现的更复杂,比如重传。

#define APP_ACK 0x01int main( void ) {    fd_set allset;    fd_set readset;    fd_set sockonly;    struct timeval tv;    struct timeval *tvp = NULL;    const static struct timeval timeout = {2,0};    char buf[1024] = NULL;    int sfd = tcp_client(9999);    FD_ZERO(&allset);    FD_ZERO(&readset);    FD_ZERO(&sockonly);    FD_SET(sfd, &allset);    sockonly = allset;    FD_SET(fileno(stdin), &allset);    readset = allset;    for ( ; ; ) {        memset(buf, 0x00, sizeof(buf));        /*            select分两种情况:                1.接收来自键盘和网络的事件。                2.当向网络发送数据后,只能接收网络事件,不能接收键盘事件,这时想接收网络的对端应用ACK。        */        int ready = select(sfd+1, &readset, NULL, NULL, tvp);        if ( ready < 0 ) perror("select"),exit(1);        // 超时,不能及时获取对端的应用ACK,打印超时并退出程序        if ( ready == 0 ) printf("message time out\n"),exit(1);        // 接收网络数据        if ( FD_ISSET(sfd, &readset) ) {            int r = read(sfd, buf, sizeof(buf));            // 如果是错误或者EOF,终止程序            if ( r == 0 ) {                printf("server close\n");                break;            } else if ( r == -1 ) {                perror("read"),exit(1);            } else if ( r==1 && buf[0] != APP_ACK ) {                printf("没有收到对方的回应报文\n");            }            // 收到确认报文,关闭定时器            tvp = NULL;            // 允许接收来自网络和键盘事件            readset = allset;        }        if ( FD_ISSET(fileno(stdin), &readset) ) {            // 获取键盘事件,发送网络,启动定时器,只接受网络事件            int r = read(filene(stdin), buf, sizeof(buf));            if ( r == -1 ) perror("read"),exit(1);            write(sfd, buf, r);            tv = timeout;            tvp = &tv;            readset = sockonly;        }    }}

当然这里设计的是停等协议,如果需要做的更好,像TCP内核协议栈一样,可以考虑把应用程序做成事件驱动的,这是一个软件设计的问题,就不在这里讨论。

故障类型

通过前面的讨论我们可以看到网络程序员不能认为TCP为我们做好了一切。我们可以把故障分为两类:1.收不到FIN的故障,比如网络掉线,或者主机崩溃都是这种情况。2.能收到FIN的故障,比如对方应用程序崩溃。

收不到FIN的故障

先来说说没有FIN的故障,分成四种情况:1.如果刚好阻塞在read函数上,这时没法恢复。可以通过设置读超时来解决。

struct timeval tv;tv.tv_sec = 2;tv.tv_usec = 0;setsockopt(cfd, SOL_SOCKET, SO_RCVTIMEO, (const char *) &tv, sizeof tv);

2.如果是先write,再read,协议栈会持续重传。经过多次重传不成功,协议栈会标记连接异常,阻塞的read就会得到TIMEOUT错误。3.如果是阻塞在select或epoll上,建议做心跳包。下面是一个有心跳功能的回射客户服务器程序客户端程序。十秒中如果没有数据通信,就心跳,执行三次如果没有应答,就退出。

int main( void ) {    int heartbeat = 0;    fd_set allfd;    fd_set readfd;    struct timeval tv;    msg_t msg;    char buf[1024];    int cfd = tcp_client();    FD_ZERO(&allfd);    FD_SET(cfd, &allfd);    FD_SET(fileno(stdin), &allfd);    tv.tv_sec  = 10;    tv.tv_usec = 0;    for ( ; ; ) {        readfd = allfd;        memset(&msg, 0x00, sizeof(msg));        int ready = select(cfd+1, &readfd, NULL, NULL, &tv);        if ( ready == -1 ) perror("select"),exit(1);        if ( ready == 0 ) {            printf("timeout %d\n", heartbeat);            if ( ++heartbeat > 3 ) {                printf("connection dead\n");                exit(0);            }            msg.type = htonl(MSG_HEARTBEAT);            if ( write(cfd, (char*)&msg, sizeof(msg)) == -1 )                perror("write"),exit(1);            tv.tv_sec = 2;            continue;        }        if ( FD_ISSET(cfd, &readfd) ) {            int ret = read(cfd, (char*)&msg, sizeof(msg));            if ( ret == 0 ) {                printf("server close\n");                break;            }            if ( ntohl(msg.type) == MSG_ECHO ) {                printf("=> %s\n", msg.data);                heartbeat = 0;                tv.tv_sec = 10;                continue;            }        }        if ( FD_ISSET(fileno(stdin), &readfd) ) {            msg.type = htonl(MSG_ECHO);            if ( fgets(msg.data, 100, stdin) == NULL )                break;            if ( write(cfd, (char*)&msg, sizeof(msg)) == -1 ){                perror("write");                break;            }        }    }    close(cfd);}

4.还有一种特殊情况就是,如果是主机崩溃又重启了,这时对端主机得到RST错误。

能收到FIN的故障

再来看看能收到FIN的故障,这里要意识到,从一个用程序角度,对端进程崩溃还是调用了close以及exit是无法区分的,在这两种情况下TCP都会向我们发送一个FIN。1.如果是read,直接得到FIN信息,返回0。2.如果是write,则第一次调用会得到RST。3.收到RST,再多次调用write就得到SIGPIPE信号。

总结

1.TCP通过序号和超时重传保证了端到端的可靠。2.TCP并不能保证应用层的可靠。3.异常的情况分为,网络故障,主机崩溃和进程崩溃。网络故障和主机故障可以看作是一类故障,当然是指除了主机崩溃并在TCP放弃连接之前,就重启了的情况。

作者:李涛

审稿:王海斌

编辑:小丸子

点亮"在看",点亮"offer"

原创不易,点个赞吧~

qt定时器是阻塞的吗_吊打面试官 | 面试官:TCP真的可靠吗相关推荐

  1. qt定时器暂停与重新开始_手把手教你写个小程序定时器管理库

    背景 凹凸曼是个小程序开发者,他要在小程序实现秒杀倒计时.于是他不假思索,写了以下代码: Page({init: function () { clearInterval(this.timer)this ...

  2. 怎么判断tcp重组完成_网络工程师(8):TCP为什么可靠

    我们知道IP只是"尽力",并不保证可靠性,所以在把数据交给IP传输之前,要考虑好怎么保证数据可靠地传输,而这一点,由传输层的TCP来完成. TCP怎么保证可靠呢?它提供了乱序重排. ...

  3. 如何查找历史线程阻塞原因_吊打面试官!Java多线程并发 108 道题,你能答对多少?...

    多线程并发108题 1.Java中实现多线程有几种方法 2.继承Thread 类 3.实现Runnable 接口. 4.ExecutorService. Callable. Future 有返回值线程 ...

  4. iOS程序UI主线程和定时器相互阻塞的问题

    解决iOS程序UI主线程和定时器相互阻塞的问题 转载于:https://www.cnblogs.com/hl666/p/3784838.html

  5. 使用QT定时器遇到的问题

    问题描述: 程序上有槽函数触发频率变化的需求.在使用Qt定时器触发槽函数的过程中发现,每秒钟1000次没有问题,在每秒钟30~50次的情况下出现了延迟. 主体代码如下: std::chrono::ti ...

  6. QT子线程实现串口通信_学习记录

    QT子线程实现串口通信_学习记录 1 新的改变 功能快捷键 合理的创建标题,有助于目录的生成 如何改变文本的样式 插入链接与图片 如何插入一段漂亮的代码片 生成一个适合你的列表 创建一个表格 设定内容 ...

  7. 重复订单号校验_吊打面试官系列重复消费、顺序消费、分布式事务

    你知道的越多,你不知道的越多 前言 消息队列在互联网技术存储方面使用如此广泛,几乎所有的后端技术面试官都要在消息队列的使用和原理方面对小伙伴们进行360°的刁难. 作为一个在互联网公司面一次拿一次Of ...

  8. demo 网络运维_【运维面试】面试官:你们公司的docker主要用来做啥?

    文章来源: [运维面试]面试官:你们公司的docker主要用来做啥?_涅槃重生-CSDN博客​blog.csdn.net 前言 关于docker的面试题,很多面试官会问,你们公司的docker主要用来 ...

  9. java定时器只执行一次_面试阿里P6,却被MySQL难倒,二战阿里,挤进天猫团队(Java岗)...

    爱因斯坦说过"耐心和恒心总会得到报酬的",我也一直把这句话当做自己的座右铭,这句箴言在今年也彻底在我身上实现了. 每一个程序员都拥有一座大厂梦,我也不例外,去年面试阿里P6,竟然被 ...

最新文章

  1. linux数据库创建score表,MySQL数据库学习笔记
  2. android ble从设备,从Android设备发送命令到蓝牙
  3. web前端数组处理之数组去重
  4. oracle u01清理,Oracleの/u01/11g/diag/rdbms/orcl/orcl/incident 的清理
  5. 操作系统:虚拟页式存储管理(缺页中断、页面置换算法)
  6. 我们为什么这样选择损失函数
  7. C# 控制台如何播放音频文件
  8. Vue/vant——使用阿里巴巴矢量图引入图标
  9. 复化科特斯公式matlab_牛顿科特斯公式要点分析.ppt
  10. nginx 二 配置conf
  11. link和import的区别,src和href的区别,css hark 以及HTML5及css3的新增特性
  12. 【ReID】ABD-Net: Attentive but Diverse Person Re-Identification
  13. 数据仓库(3)数仓建模之星型模型与维度建模
  14. Maven的安装配置及IDEA配置(图文教程)
  15. Sublime 使用总结
  16. 二、buildroot菜单配置
  17. 膏肓痛的自疗法-------为久坐者
  18. 华为od机考真题-高矮个子排队,算法第九讲-高频真题解析 II
  19. (讲解)用java实现简单选择排序
  20. unity重置天下霸图之进度汇总

热门文章

  1. linux route命令详解
  2. 数字图像处理实验(14):PROJECT 06-01,Web-Safe Colors
  3. ViewPager+TabLayout
  4. PHP开发一个简单的成绩录入系统
  5. 因为 Java 和 Php 在获取客户端 cookie 方式不同引发的 bug
  6. Vue + VueRouter + Vuex + Axios 抓取 GitHub 上的 Issues 来搭建个人博客站点
  7. C#中const和static readonly 的区别
  8. 实现一个多线程循环的类
  9. Oracle常用知识
  10. 什么是 dolby AC-3