加入收藏 | 设为首页 | 会员中心 | 我要投稿 源码门户网 (https://www.92codes.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Linux > 正文

Linux内核分析 - 网络[十六]:TCP三次握手

发布时间:2016-09-28 12:48:07 所属栏目:Linux 来源:站长网
导读:副标题#e# 内核:2.6.34 TCP是应用最广泛的传输层协议,其提供了面向连接的、可靠的字节流服务,但 也正是因为这些特性,使得TCP较之UDP异常复杂,还是分两部分[创建与使用]来进行分析。这篇主要包括TCP的创建及三次握手 的过程。 编程时一般用如下语句创建

下面来看下对每个端口的检查,即//choose a valid port部分的代码。这里要先了解下tcp的内核表组成,udp的表 内核表udptable只是一张hash表,tcp的表则稍复杂,它的名字是tcp_hashinfo,在tcp_init()中被初始化,这个数据结构定义 如下(省略了不相关的数据):

struct inet_hashinfo {     
 struct inet_ehash_bucket *ehash;     
 ……     
 struct inet_bind_hashbucket *bhash;     
 ……     
 struct inet_listen_hashbucket  listening_hash[INET_LHTABLE_SIZE]     
     ____cacheline_aligned_in_smp;     
};

从定义可以看出,tcp表又分成了三张表ehash, bhash, listening_hash,其中ehash, listening_hash对应于 socket处在TCP的ESTABLISHED, LISTEN状态,bhash对应于socket已绑定了本地地址。三者间并不互斥,如一个socket可同时在 bhash和ehash中,由于TIME_WAIT是一个比较特殊的状态,所以ehash又分成了chain和twchain,为TIME_WAIT的socket单独形成 一张表。

回到刚才的代码,现在还只是建立socket连接,使用的就应该是tcp表中的bhash。首先取得内核tcp表的bind表 – bhash,查看是否已有socket占用:

如果没有,则调用inet_bind_bucket_create()创建一个 bind表项tb,并插入到bind表中,跳转至goto ok代码段;

如果有,则跳转至goto ok代码段。

进入ok代码段表明已找到合适的bind表项(无论是创建的还是查找到的),调用inet_bind_hash()赋值源端口inet_num。

for (i = 1; i <= remaining; i++) {     
 port = low + (i + offset) % remaining;     
 head = &hinfo->bhash[inet_bhashfn(net, port, hinfo->bhash_size)];     
 ……     
 inet_bind_bucket_for_each(tb, node, &head->chain) {     
  if (net_eq(ib_net(tb), net) && tb->port == port) {     
   if (tb->fastreuse >= 0)     
    goto next_port;     
   WARN_ON(hlist_empty(&tb->owners));     
   if (!check_established(death_row, sk, port, &tw))     
    goto ok;     
   goto next_port;     
  }     
 }     
         
 tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep, net, head, port);     
 ……     
 next_port:     
  spin_unlock(&head->lock);     
}     
         
ok:     
 ……     
inet_bind_hash(sk, tb, port);     
 ……     
 goto out;

在获取到合适的源端口号后,会重建路由项来进行更新:

err = ip_route_newports(&rt, IPPROTO_TCP, inet->inet_sport, inet->inet_dport, sk);

函数比较简单,在获 取sport前已经查找过一次路由表,并插入了key=[saddr=0, sport=0, daddr, dport, oif=0]的路由缓存项;现在获取到了 sport,调用ip_route_output_flow()再次更新路由缓存表,它会添加key=[saddr=0, sport, daddr, dport, oif=0]的路由缓存 项。这里可以看出一个策略选择,查询路由表->获取sport->查询路由表,为什么不是获取sport->查询路由表的原因 可能是效率的问题。

if (sport != (*rp)->fl.fl_ip_sport ||     
    dport != (*rp)->fl.fl_ip_dport) {     
 struct flowi fl;     
         
 memcpy(&fl, &(*rp)->fl, sizeof(fl));     
 fl.fl_ip_sport = sport;     
 fl.fl_ip_dport = dport;     
 fl.proto = protocol;     
 ip_rt_put(*rp);     
 *rp = NULL;     
 security_sk_classify_flow(sk, &fl);     
 return ip_route_output_flow(sock_net(sk), rp, &fl, sk, 0);     
}

(编辑:源码门户网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读