浅析TCP协议报文生成过程

本文深入解析TCP协议报文的生成过程,从应用层的函数调用开始,逐步介绍如何封装TCP头部,设置源端口、目的端口、序列号等关键字段,直至在网络层设置IP头部,最终通过邻居子系统输出至网络介质层。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

继上篇 :图解Java服务端Socket建立原理 后,我们继续介绍 TCP协议报文的生成过程

报文与协议关系

在这里插入图片描述
上图展示了osi七层与tcp/ip四层的对应关系,以及展示协议报文封装过程以及与tcp/ip四层的对应关系

报文内容

tcp协议包
报文格式与其tcp各层关系如上所示

主要的数据结构

sk_bufff结构是Linux网络模块中最重要的数据结构之一,用以描述已接收或待发送的数据报文信息.
sk_buff负责描述数据报文,tcp/ip各层都是针对sk_buff结构进行处理,并传递给下一层的.下图描述了发送报文的sk_buff与其它相关数据结构的关系
在这里插入图片描述
struct sock是套接字在传输层的结构.
报文的真正内容是在sk_buff数据区
sk_buff是用来描述管理报文sk_buff数据区

TCP报文包的生成

报文生成流程

TCP报文生成代码

传输层(tcp层)

网络数据包的发送过程起始于应用层的函数调用(如上图的send函数),随后会调用tcp_sendmsg函数,如上图所示,层层调用到tcp_transmit_skb函数完成TCP协议处理,封闭tcp包头,最后调用这里最终调用的ip层的ip_queue_xmit方法

  //tcp_sendmsg函数的主要逻辑是在tcp_sendmsg_locked函数
  int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
{
    ...
    //得到当前mss长度和数据包最大长度
    mss_now = tcp_send_mss(sk, &size_goal, flags);
    ...
	while (msg_data_left(msg)) {
    	...
    	//从tcp写入队列sk_write_queue中拿出最后一个struct sk_buff
    	skb = tcp_write_queue_tail(sk);
    	//copy<=0表示不能合并到之前skb做GSO,或者被设置了eor标记不能合并
    	if (copy <= 0 || !tcp_skb_can_collapse_to(skb)) {
    	    //在TCP的重传队列和发送队列都为空的情况下
    	    first_skb = tcp_rtx_and_write_queues_empty(sk);
    	    //重新分配skb结构
		    skb = sk_stream_alloc_skb(sk,
						  select_size(sk, sg, first_skb),
						  sk->sk_allocation,
						  first_skb);
			// 加入发送队列
			skb_entail(sk, skb);	
    	}
    }
    ...
    //发送,会调用__tcp_push_pending_frames方法,再调用tcp_write_xmit方法,再调tcp_transmit_skb
    tcp_push(sk, flags, mss_now, tp->nonagle, size_goal);
    ...
}
static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,
			    gfp_t gfp_mask)
{
   ...
   	th = (struct tcphdr *)skb->data;
   	//本例子中source端口内存值为0xd204,因为tcp是大端模式,实际值应该是0x04d2,即是10进制的1234,即为本服务器的端口
	th->source		= inet->inet_sport;
	//本例子desc为0x1c90,实际就是0x901c,即10进制的36892,刚好是客户端的端口
	th->dest		= inet->inet_dport;
	th->seq			= htonl(tcb->seq);
	th->ack_seq		= htonl(tp->rcv_nxt);
	*(((__be16 *)th) + 6)	= htons(((tcp_header_size >> 2) << 12) |
					tcb->tcp_flags);
	th->check		= 0;
	th->urg_ptr		= 0;
   ...
   //这里最终调用的是ip_queue_xmit
    err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);
}

网络层(ip层)

tcp层最后会调用ip层的ip_queue_xmit方法,在这方法会,会对报文的ip包头进行设置,随后经层层调用经过邻居子系统后,再调用dev_queue_xmit函数,进入网络介质层

int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl)
{
    ...
    //设置ip包头
    iph = ip_hdr(skb);
	*((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
	if (ip_dont_fragment(sk, &rt->dst) && !skb->ignore_df)
		iph->frag_off = htons(IP_DF);
	else
		iph->frag_off = 0;
	iph->ttl      = ip_select_ttl(inet, &rt->dst);
	iph->protocol = sk->sk_protocol;
	ip_copy_addrs(iph, fl4);

	/* Transport layer set skb->h.foo itself. */

	if (inet_opt && inet_opt->opt.optlen) {
		iph->ihl += inet_opt->opt.optlen >> 2;
		ip_options_build(skb, &inet_opt->opt, inet->inet_daddr, rt, 0);
	}
    ...
    //会调用:__ip_local_out
    res = ip_local_out(net, sk, skb);
    ...
}
int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
{
    ...
    //截取数据包,对数据包干预
	return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT,
		       net, sk, skb, NULL, skb_dst(skb)->dev,
		       dst_output);
}
static int ip_finish_output2(struct net *net, struct sock *sk, struct sk_buff *skb)
{
    ...
    //获取下一跳
    nexthop = (__force u32) rt_nexthop(rt, ip_hdr(skb)->daddr);
    //获取邻居子系统
	neigh = __ipv4_neigh_lookup_noref(dev, nexthop);
	...
    //通过邻居子系统输出,neigh_output调用了neigh_hh_output
    res = neigh_output(neigh, skb);
    ...
}

作者: 吴炼钿

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值