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

e46226e4fb3cb892fe1861023aa1dd05.png

点击蓝字关注我哦

86998c9b56a262359b64d26c179390e3.png 以下是本期干货视频 视频后还附有文字版本哦 bfeac4a3258ada3ddc34323fbe776110.png 72b3058f4903a664cb10863b7bf6b2ae.png

▼《面试官: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是个不可靠的协议。

ed380b9c45978e7a889ec9414ec31526.png

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

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

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

82af7b7bdb6fdd68261aaca231d37e6e.png

这里设计成客户端和服务器有两条通道,主要的原因是想让发送数据模块和接收网络数据模块都能够获取网络中对端的状态,而不是将状态混在一条通道上。 这里给大家实现向外发送数据模块。实现思路是:发送一条消息后,在定时器到之前必须接收对等实体发过来的应用层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放弃连接之前,就重启了的情况。

作者:李涛

审稿:王海斌

编辑:小丸子

e9458368cdee02fe5ed5792d533544b8.png

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

e8f23f2ec6c0d49f8a209fe98fd06ff8.gif

                             原创不易,点个赞吧5e475dc4f82e8e92eb4bf13a4e088ae4.png~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值