热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

一道腾讯面试题目:没有listen,能否建立TCP连接

TCP与UDP最大的不同,就是有连接的概念,而连接的建立是由内核完成的。系统调用listen,就是为了告诉内核,它要处理发给

TCP与UDP最大的不同,就是有连接的概念,而连接的建立是由内核完成的。系统调用listen,就是为了告诉内核,它要处理发给这个TCP端口的连接请求。所以对于这个题目,最直接的想法就是由应用层自己负责TCP的连接。为了能够收到TCP的握手数据包,可以尝试使用原始套接字来接收IP报文,这样就可以在应用层替代内核做TCP的三次握手了。这个想法不错,可惜现实比较残酷。

当没有对于TCP 套接字处于listen状态时,使用raw socket处理握手报文时,即使收到了syn报文并给对端发送了syn+ack报文,也无法完成连接。因为内核一般会提前发送RST中断该连接。七年前,我当时只知道这个结果。现在呢,可以明确的知道为什么内核会发送RST报文,中断连接。内核在ip_local_deliver_finish先将报文复制一份给原始套接字,然后会继续后面的处理,进入tcp的接收函数tcp_v4_rcv。在这个函数中,要进行套接字的查找。
在这里插入图片描述

因为没有监听的tcp套接字,自然无法找到对应的套接字。于是跳转到no_tcp_socket。
在这里插入图片描述

在这个错误处理中,只要数据包skb的校验和没错,内核就会调用tcp_v4_send_reset发送RST中止这个连接。因此,这个单独使用raw socket的方案是行不通的。我们需要找到一种方法,禁止内核处理该TCP报文。这时,可以使用iptables的NFQUEUE,在网络层将数据包取到应用层,并回复syn+ack,同时在reinject时通知内核该数据包被“偷”了,即NF_STOLEN。这样内核就不会继续处理该数据包skb了。虽然当时我没有继续实验尝试,但理论上,通过这个IPtables的NFQUEUE+NFSTOLEN的方案,是可以实现“没有listen,建立TCP连接”的。

【文章福利】需要各一线互联网大厂面试题及C/C++ Linux服务器架构师学习资料加群812855908(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等)
在这里插入图片描述

在这里插入图片描述

可惜,在与那位同学的讨论中,腾讯面试题目的本意不是这个意思,而是对于普通的TCP套接字来说,如果没有listen调用,是否可以创建连接。即使限定了条件,答案依然是肯定的。只不过限定了条件之后,我们需要确定2个事情:

  1. 与前面类似,如何避免内核发送RST。在不能使用iptable的前提下,这意味着在tcp_v4_rcv中,要能够找到对应的套接字。
  2. 没有listen状态的套接字,内核是否能够完成TCP的三次握手呢?

确定第一个问题,比较简单。只需要对三次握手深入的思考一下,就可以得到答案。在正常的三次握手中,当服务端回复syn+ack时,客户端实际上也没有处于listen状态的套接字,但却可以完成三次握手。这意味着,客户端进行connect调用后,该套接字一定被加入到某个表中,并可以被匹配到。跟踪内核源码tcp_v4_connect->inet_hash_connect->__inet_check_established,可以看到当调用connect时,对应的套接字就被加入了全局的tcp已连接的表中,即tcp_hashinfo.ehash中。对应的匹配TCP套接字过程,如下__inet_lookup_skb->__inet_lookup
在这里插入图片描述

内核是先在已经连接的表中查找,再进行listen表的查找。对于客户端来说,syn+ack报文必然可以在已连接表中匹配上对应的套接字。那么,对于本题目来说,要想两端都可以找到套接字,就要求在报文到达前,两端都调用了connect。也就是说,当两端同时调用connect时,两端的syn包就都可以匹配上本地的套接字。

接下来只需要确定对于客户端套接字来说,收到syn报文,是否可以正常处理。tcp_rcv_synsent_state_process函数是TCP套接字处于synsent状态(即调用了connect)的处理函数。下面是其一部分实现代码:
在这里插入图片描述
在这里插入图片描述

从上,可以得出,对于处于synsent状态的套接字来说,如果收到了syn报文,则会正常回复synack,完成TCP三次握手。

对于腾讯的这道面试题目来说,其答案就是当两端同时发起connect调用时,即使没有listen调用,也可以成功创建TCP连接。

如果去掉“两端”的限制,还有一个答案就是,TCP套接字可以connect它本身bind的地址和端口,也可以达成要求。下面是测试代码,实现了一个TCP套接字成功连接自己,并发送消息。

#include /* See NOTES */
#include
#include
#include
#include
#include
#include
#include #define LOCAL_IP_ADDR (0x7F000001)
#define LOCAL_TCP_PORT (34567)int main(void)
{struct sockaddr_in local, peer;int ret;char buf[128];int sock = socket(AF_INET, SOCK_STREAM, 0);memset(&local, 0, sizeof(local));memset(&peer, 0, sizeof(peer));local.sin_family = AF_INET;local.sin_port = htons(LOCAL_TCP_PORT);local.sin_addr.s_addr = htonl(LOCAL_IP_ADDR);peer = local; int flag = 1;ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));if (ret == -1) {printf("Fail to setsocket SO_REUSEADDR: %s\n", strerror(errno));exit(1);}ret = bind(sock, (const struct sockaddr *)&local, sizeof(local));ret = connect(sock, (const struct sockaddr *)&peer, sizeof(peer));if (ret) {printf("Fail to connect myself: %s\n", strerror(errno));exit(1);}printf("Connect to myself successfully\n");strcpy(buf, "Hello, myself~");send(sock, buf, strlen(buf), 0);memset(buf, 0, sizeof(buf));recv(sock, buf, sizeof(buf), 0);printf("Recv the msg: %s\n", buf);close(sock);return 0;
}

其连接截图如下:
在这里插入图片描述

从截图中,可以看到TCP套接字成功的“连接”了自己,并发送和接收了数据包围。netstat的输出更证明了TCP的两端地址和端口是完全相同的。


推荐阅读
  • 小王详解:内部网络中最易理解的NAT原理剖析,挑战你的认知极限
    小王详解:内部网络中最易理解的NAT原理剖析,挑战你的认知极限 ... [详细]
  • 字节跳动深圳研发中心安全业务团队正在火热招募人才! ... [详细]
  • 手指触控|Android电容屏幕驱动调试指南
    手指触控|Android电容屏幕驱动调试指南 ... [详细]
  • 本文源自极分享,详细内容请参阅原文。技术债务如同信用卡负债,随着时间推移,修复成本会越来越高,因此程序员必须对此有深刻认识。此外,团队应致力于培养一种持续维护和优化代码的文化,以减少技术债务的累积。 ... [详细]
  • 本书详细介绍了在最新Linux 4.0内核环境下进行Java与Linux设备驱动开发的全面指南。内容涵盖设备驱动的基本概念、开发环境的搭建、操作系统对设备驱动的影响以及具体开发步骤和技巧。通过丰富的实例和深入的技术解析,帮助读者掌握设备驱动开发的核心技术和最佳实践。 ... [详细]
  • 浏览器作为我们日常不可或缺的软件工具,其背后的运作机制却鲜为人知。本文将深入探讨浏览器内核及其版本的演变历程,帮助读者更好地理解这一关键技术组件,揭示其内部运作的奥秘。 ... [详细]
  • 帝国CMS中的信息归档功能详解及其重要性
    本文详细解析了帝国CMS中的信息归档功能,并探讨了其在内容管理中的重要性。通过归档功能,用户可以有效地管理和组织大量内容,提高网站的运行效率和用户体验。此外,文章还介绍了如何利用该功能进行数据备份和恢复,确保网站数据的安全性和完整性。 ... [详细]
  • 本文深入探讨了NoSQL数据库的四大主要类型:键值对存储、文档存储、列式存储和图数据库。NoSQL(Not Only SQL)是指一系列非关系型数据库系统,它们不依赖于固定模式的数据存储方式,能够灵活处理大规模、高并发的数据需求。键值对存储适用于简单的数据结构;文档存储支持复杂的数据对象;列式存储优化了大数据量的读写性能;而图数据库则擅长处理复杂的关系网络。每种类型的NoSQL数据库都有其独特的优势和应用场景,本文将详细分析它们的特点及应用实例。 ... [详细]
  • Redis哈希数据结构入门指南
    Redis的哈希数据结构与Java中的HashMap类似,采用数组加链表的方式实现。数组用于存储哈希值的位置,而链表则用于处理哈希冲突的情况。此外,Redis的哈希数据结构还支持高效的字段操作和内存优化,适用于多种应用场景,如缓存和会话管理。 ... [详细]
  • 每日精选Codeforces训练题:1119E(贪心算法)、821C(栈模拟)和645D(拓扑排序)
    题目涉及三种不同类型的算法问题:1119E(贪心算法)、821C(栈模拟)和645D(拓扑排序)。其中,1119E的问题背景是有n种不同长度的棍子,长度分别为2^0, 2^1, …, 2^(n-1),每种棍子的数量为a[i]。任务是计算可以组成的三角形数量。根据三角形的性质,任意两边之和必须大于第三边。该问题可以通过贪心算法高效解决,通过合理选择棍子组合来最大化三角形的数量。 ... [详细]
  • 深入解析Netty:基础理论与IO模型概述
    深入解析Netty:基础理论与IO模型概述 ... [详细]
  • 如何在Oracle ASM_Diskgroup中重命名现有磁盘
    如何在Oracle ASM_Diskgroup中重命名现有磁盘 ... [详细]
  • 从无到有,构建个人专属的操作系统解决方案
    操作系统(OS)被誉为程序员的三大浪漫之一,常被比喻为计算机的灵魂、大脑、内核和基石,其重要性不言而喻。本文将详细介绍如何从零开始构建个人专属的操作系统解决方案,涵盖从需求分析到系统设计、开发与测试的全过程,帮助读者深入理解操作系统的本质与实现方法。 ... [详细]
  • 掌握PHP框架开发与应用的核心知识点:构建高效PHP框架所需的技术与能力综述
    掌握PHP框架开发与应用的核心知识点对于构建高效PHP框架至关重要。本文综述了开发PHP框架所需的关键技术和能力,包括但不限于对PHP语言的深入理解、设计模式的应用、数据库操作、安全性措施以及性能优化等方面。对于初学者而言,熟悉主流框架如Laravel、Symfony等的实际应用场景,有助于更好地理解和掌握自定义框架开发的精髓。 ... [详细]
  • Go语言实现Redis客户端与服务器的交互机制深入解析
    在前文对Godis v1.0版本的基础功能进行了详细介绍后,本文将重点探讨如何实现客户端与服务器之间的交互机制。通过具体代码实现,使客户端与服务器能够顺利通信,赋予项目实际运行的能力。本文将详细解析Go语言在实现这一过程中的关键技术和实现细节,帮助读者深入了解Redis客户端与服务器的交互原理。 ... [详细]
author-avatar
200355人
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有