浅析TCP协议报文生成过程
背景
继上篇 : 图解Java服务端Socket建立原理 后,我们继续介绍 TCP协议报文的生成过程
报文与协议关系
上图展示了osi七层与tcp/ip四层的对应关系,以及展示协议报文封装过程以及与tcp/ip四层的对应关系
报文内容
报文格式与其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);
...
}
作者: 吴炼钿
winding1993: 增值税专用发票(电子)应该是20
Ewann: 遇到好评论,我就是要评论,嘿,就是玩
2201_75413102: 点哪个文件运行啊,太多了实在不知道是哪个
luoyaorenjson: 怎么说都是都是ofd格式啊 PDF、XML、OFD三类都属于源文件吗?
普通网友: 写的很好,细节很到位!【我也写了一些相关领域的文章,希望能够得到博主的指导,共同进步!】