百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

深入源码理解TCP建立连接过程(3次握手)

bigegpt 2024-08-28 12:26 5 浏览

从TCP源码上深入了解三次握手过程

应用程序的基本框架

//server
int main()
{
    int fd = socket(AF_INET,SOCK_STREAM,0);
    bind(fd,...);
    listen(fd,256);
    accept(fd,...);
 
}
 
//client
int main()
{
    fd = connect(AF_INET,SOCK_STREAM,0);
    connect(fd,...);
}

三次握手概括

分析开始

客户端connect

int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
    //设置socket状态TCP_SYN_SENT
	tcp_set_state(sk, TCP_SYN_SENT);
 
	//动态选择一个端口
	err = inet_hash_connect(tcp_death_row, sk);
    
	//根据sk中的信息,构建一个SYNC的报文,并将它发送出去
	err = tcp_connect(sk);
}
 
 
int tcp_connect(struct sock *sk)
{
	//申请skb
	buff = sk_stream_alloc_skb(sk, 0, sk->sk_allocation, true);
 
 
	//添加到发送队列sk_write_queue
	tcp_connect_queue_skb(sk, buff);
	tcp_ecn_send_syn(sk, buff);
	tcp_rbtree_insert(&sk->tcp_rtx_queue, buff);
 
	//发送syn  tcp_transmit_skb
	err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
	      tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
 
	//重启定时器
	/* Timer for repeating the SYN until an answer. */
	inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
				  inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
	return 0;
}

connect 作用把本地socket状态设置成TCP_SYN_SENT;

选择一个可用的端口,发出SYN握手请求并重置定时器;

服务端响应SYN

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
	//服务端收到SYN或者第三步ACK都会走到这里
	if (sk->sk_state == TCP_LISTEN) {
		struct sock *nsk = tcp_v4_cookie_check(sk, skb);//查看半连接队列
 
 
	} else
		sock_rps_save_rxhash(sk, skb);
    //不同的状态处理
	if (tcp_rcv_state_process(sk, skb)) {
		rsk = sk;
		goto reset;
	}
}



//服务端处理syn连接请求
int tcp_conn_request(struct request_sock_ops *rsk_ops,
		     const struct tcp_request_sock_ops *af_ops,
		     struct sock *sk, struct sk_buff *skb)
{
 
	//查看半连接队列是否满了,则直接丢弃
	if ((net->ipv4.sysctl_tcp_syncookies == 2 ||
	     inet_csk_reqsk_queue_is_full(sk)) && !isn) {
		want_cookie = tcp_syn_flood_action(sk, rsk_ops->slab_name);
		if (!want_cookie)
			goto drop;
	}
	//查看全连接队列,如果满了则直接丢弃
	if (sk_acceptq_is_full(sk)) {
		NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
		goto drop;
	}
	//分配request_sock内核对象
	req = inet_reqsk_alloc(rsk_ops, sk, !want_cookie);
 
 
	if (fastopen_sk) {
		af_ops->send_synack(fastopen_sk, dst, &fl, req,
				    &foc, TCP_SYNACK_FASTOPEN);
		/* Add the child socket directly into the accept queue */
	} else {
		tcp_rsk(req)->tfo_listener = false;
		if (!want_cookie)//添加到半连接队列,并开启定时器
			inet_csk_reqsk_queue_hash_add(sk, req,
				tcp_timeout_init((struct sock *)req));
		//构造synack包
		af_ops->send_synack(sk, dst, &fl, req, &foc,
				    !want_cookie ? TCP_SYNACK_NORMAL :
						   TCP_SYNACK_COOKIE);
 
	}

.send_synack就是tcp_v4_send_synack


static int tcp_v4_send_synack(const struct sock *sk, struct dst_entry *dst,
			      struct flowi *fl,
			      struct request_sock *req,
			      struct tcp_fastopen_cookie *foc,
			      enum tcp_synack_type synack_type)
{
 
	if (!dst && (dst = inet_csk_route_req(sk, &fl4, req)) == NULL)
		return -1;
	//构造syn+ack包
	skb = tcp_make_synack(sk, dst, req, foc, synack_type);
 
	if (skb) {
		__tcp_v4_send_check(skb, ireq->ir_loc_addr, ireq->ir_rmt_addr);
 
		rcu_read_lock();
		//将synack包发送出去
		err = ip_build_and_send_pkt(skb, sk, ireq->ir_loc_addr,
					    ireq->ir_rmt_addr,
					    rcu_dereference(ireq->ireq_opt));
		rcu_read_unlock();
		err = net_xmit_eval(err);
	}
 
	return err;
}

服务端响应SYN

  1. 检查半连接与全连接队列是否满,满握手直接丢弃
  2. 申请request_sock,并添加到半连接队列中
  3. 构造syn+ack包,通过ip_build_and_send_pkt发送
  4. 重启定时器tcp_timeout_init

相关视频推荐

Tcp/ip协议栈技术专题训练营(1)

Tcp/ip协议栈技术专题训练营(2)

Linux内核源码分析之网络协议栈架构

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

客户端响应SYN+ACK

客户端收到服务端发来的syn+ack包的时候,进入tcp_rcv_state_process

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
	switch (sk->sk_state) {
	case TCP_CLOSE:
		goto discard;
	//第一次握手 服务器收到
	case TCP_LISTEN:
 
		goto discard;
	//客户端第二次握手处理
	case TCP_SYN_SENT:
		//synack包
		queued = tcp_rcv_synsent_state_process(sk, skb, th);
		return 0;
	}
}



static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
					 const struct tcphdr *th)
{
        //step1:
		tcp_ack(sk, skb, FLAG_SLOWPATH);
 
		//step2:tcp 建立完成
		tcp_finish_connect(sk, skb);
 
 
		if (sk->sk_write_pending ||
		    icsk->icsk_accept_queue.rskq_defer_accept ||
		    inet_csk_in_pingpong_mode(sk)) {
 
			return 0;
		} else {
            //step3:发送确认
			tcp_send_ack(sk);
		}
}

step1:

/* This routine deals with incoming acks, but not outgoing ones. */
static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)
{
    //删除发送队列
   flag |= tcp_clean_rtx_queue(sk, prior_fack, prior_snd_una, &sack_state);
 
    //重置定时器
	if (flag & FLAG_SET_XMIT_TIMER)
		tcp_set_xmit_timer(sk);
}

step2

void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
{
	//修改socket状态
	tcp_set_state(sk, TCP_ESTABLISHED);
	icsk->icsk_ack.lrcvtime = tcp_jiffies32;
 
	//拥塞控制
	tcp_init_transfer(sk, BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB);
	tp->lsndtime = tcp_jiffies32;
 
	//打开保活计时器
	if (sock_flag(sk, SOCK_KEEPOPEN))
		inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tp));
 
}

step3:

void __tcp_send_ack(struct sock *sk, u32 rcv_nxt)
{
	if (sk->sk_state == TCP_CLOSE)
		return;
 
	//申请和构造ack包
	buff = alloc_skb(MAX_TCP_HEADER,
			 sk_gfp_mask(sk, GFP_ATOMIC | __GFP_NOWARN));
 
	//发送出去
	__tcp_transmit_skb(sk, buff, 0, (__force gfp_t)0, rcv_nxt);
}

总结

  1. 客户端响应synack,清除重传定时器
  2. 设置当前状态为ESTABLISHED
  3. 开启拥塞控制,保活机制
  4. 发送ack包

服务器端响应ACK

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
 
    //服务端收到SYN或者第三步ACK都会走到这里
	if (sk->sk_state == TCP_LISTEN) {
		struct sock *nsk = tcp_v4_cookie_check(sk, skb);//step1:查看半连接队列,新创建线程
 
 
    //step2:
	if (tcp_rcv_state_process(sk, skb)) 
}

step1:创建子socket;添加全连接队列


//tcp_v4_cookie_check->cookie_v4_check->tcp_get_cookie_sock
struct sock *tcp_get_cookie_sock(struct sock *sk, struct sk_buff *skb,
				 struct request_sock *req,
				 struct dst_entry *dst, u32 tsoff)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct sock *child;
	bool own_req;
 
	//创建子sock 回调函数在下面tcp_v4_syn_recv_sock
	child = icsk->icsk_af_ops->syn_recv_sock(sk, skb, req, dst,
						 NULL, &own_req);
	if (child) {
		//添加全连接队列
		if (inet_csk_reqsk_queue_add(sk, req, child))
			return child;
		bh_unlock_sock(child);
		sock_put(child);
	}
	return NULL;
}
 
 
//添加到全连接队列
struct sock *inet_csk_reqsk_queue_add(struct sock *sk,
				      struct request_sock *req,
				      struct sock *child)
{
	struct request_sock_queue *queue = &inet_csk(sk)->icsk_accept_queue;
		req->sk = child;
		req->dl_next = NULL;
		if (queue->rskq_accept_head == NULL)
			WRITE_ONCE(queue->rskq_accept_head, req);
}
 
 
 
const struct inet_connection_sock_af_ops ipv4_specific = {
	.syn_recv_sock	   = tcp_v4_syn_recv_sock,
}
//子socket的创建过程
struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb,
				  struct request_sock *req,
				  struct dst_entry *dst,
				  struct request_sock *req_unhash,
				  bool *own_req)
{
	struct ip_options_rcu *inet_opt;
 
	//判断队列是否满了
	if (sk_acceptq_is_full(sk))
		goto exit_overflow;
		
	//创建socket
	newsk = tcp_create_openreq_child(sk, req, skb);
	if (!newsk)
		goto exit_nonewsk;
}

step2:设置连接状态TCP_ESTABLISHED

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
 
	switch (sk->sk_state) {
	case TCP_CLOSE:
		goto discard;
	//第一次握手
	case TCP_LISTEN:
	//客户端第二次握手处理
	case TCP_SYN_SENT:
	switch (sk->sk_state) {
	//服务器第三次握手
	case TCP_SYN_RECV:
 
		//改变连接状态
		tcp_set_state(sk, TCP_ESTABLISHED);
}

总结

  1. 服务端将状态设置为ESTABLISHED;
  2. 创建新sock加入全连接队列中

最后accept过程

struct sock *inet_csk_accept(struct sock *sk, int flags, int *err, bool kern)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	//全连接队列获取元素
	struct request_sock_queue *queue = &icsk->icsk_accept_queue;
 
	//取出一个给用户使用
	req = reqsk_queue_remove(queue, sk);
	newsk = req->sk;
}

从全连接队列中取出一个给用户使用

相关推荐

得物可观测平台架构升级:基于GreptimeDB的全新监控体系实践

一、摘要在前端可观测分析场景中,需要实时观测并处理多地、多环境的运行情况,以保障Web应用和移动端的可用性与性能。传统方案往往依赖代理Agent→消息队列→流计算引擎→OLAP存储...

warm-flow新春版:网关直连和流程图重构

本期主要解决了网关直连和流程图重构,可以自此之后可支持各种复杂的网关混合、多网关直连使用。-新增Ruoyi-Vue-Plus优秀开源集成案例更新日志[feat]导入、导出和保存等新增json格式支持...

扣子空间体验报告

在数字化时代,智能工具的应用正不断拓展到我们工作和生活的各个角落。从任务规划到项目执行,再到任务管理,作者深入探讨了这款工具在不同场景下的表现和潜力。通过具体的应用实例,文章展示了扣子空间如何帮助用户...

spider-flow:开源的可视化方式定义爬虫方案

spider-flow简介spider-flow是一个爬虫平台,以可视化推拽方式定义爬取流程,无需代码即可实现一个爬虫服务。spider-flow特性支持css选择器、正则提取支持JSON/XML格式...

solon-flow 你好世界!

solon-flow是一个基础级的流处理引擎(可用于业务规则、决策处理、计算编排、流程审批等......)。提供有“开放式”驱动定制支持,像jdbc有mysql或pgsql等驱动,可...

新一代开源爬虫平台:SpiderFlow

SpiderFlow:新一代爬虫平台,以图形化方式定义爬虫流程,不写代码即可完成爬虫。-精选真开源,释放新价值。概览Spider-Flow是一个开源的、面向所有用户的Web端爬虫构建平台,它使用Ja...

通过 SQL 训练机器学习模型的引擎

关注薪资待遇的同学应该知道,机器学习相关的岗位工资普遍偏高啊。同时随着各种通用机器学习框架的出现,机器学习的门槛也在逐渐降低,训练一个简单的机器学习模型变得不那么难。但是不得不承认对于一些数据相关的工...

鼠须管输入法rime for Mac

鼠须管输入法forMac是一款十分新颖的跨平台输入法软件,全名是中州韵输入法引擎,鼠须管输入法mac版不仅仅是一个输入法,而是一个输入法算法框架。Rime的基础架构十分精良,一套算法支持了拼音、...

Go语言 1.20 版本正式发布:新版详细介绍

Go1.20简介最新的Go版本1.20在Go1.19发布六个月后发布。它的大部分更改都在工具链、运行时和库的实现中。一如既往,该版本保持了Go1的兼容性承诺。我们期望几乎所...

iOS 10平台SpriteKit新特性之Tile Maps(上)

简介苹果公司在WWDC2016大会上向人们展示了一大批新的好东西。其中之一就是SpriteKitTileEditor。这款工具易于上手,而且看起来速度特别快。在本教程中,你将了解关于TileE...

程序员简历例句—范例Java、Python、C++模板

个人简介通用简介:有良好的代码风格,通过添加注释提高代码可读性,注重代码质量,研读过XXX,XXX等多个开源项目源码从而学习增强代码的健壮性与扩展性。具备良好的代码编程习惯及文档编写能力,参与多个高...

Telerik UI for iOS Q3 2015正式发布

近日,TelerikUIforiOS正式发布了Q32015。新版本新增对XCode7、Swift2.0和iOS9的支持,同时还新增了对数轴、不连续的日期时间轴等;改进TKDataPoin...

ios使用ijkplayer+nginx进行视频直播

上两节,我们讲到使用nginx和ngixn的rtmp模块搭建直播的服务器,接着我们讲解了在Android使用ijkplayer来作为我们的视频直播播放器,整个过程中,需要注意的就是ijlplayer编...

IOS技术分享|iOS快速生成开发文档(一)

前言对于开发人员而言,文档的作用不言而喻。文档不仅可以提高软件开发效率,还能便于以后的软件开发、使用和维护。本文主要讲述Objective-C快速生成开发文档工具appledoc。简介apple...

macOS下配置VS Code C++开发环境

本文介绍在苹果macOS操作系统下,配置VisualStudioCode的C/C++开发环境的过程,本环境使用Clang/LLVM编译器和调试器。一、前置条件本文默认前置条件是,您的开发设备已...