龙空技术网

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

linux技术栈 498

前言:

今天朋友们对“nginx配置tcp链接”可能比较关切,大家都想要分析一些“nginx配置tcp链接”的相关文章。那么小编在网上网罗了一些关于“nginx配置tcp链接””的相关文章,希望小伙伴们能喜欢,我们一起来了解一下吧!

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

应用程序的基本框架

//serverint main(){    int fd = socket(AF_INET,SOCK_STREAM,0);    bind(fd,...);    listen(fd,256);    accept(fd,...); } //clientint 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

检查半连接与全连接队列是否满,满握手直接丢弃申请request_sock,并添加到半连接队列中构造syn+ack包,通过ip_build_and_send_pkt发送重启定时器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);}

总结

客户端响应synack,清除重传定时器设置当前状态为ESTABLISHED开启拥塞控制,保活机制发送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_sockstruct 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);}

总结

服务端将状态设置为ESTABLISHED;创建新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;}

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

标签: #nginx配置tcp链接