tcp客户端与服务器端建立连接需要经过三次握手过程,本文主要分析客户端主动打开中的第一次握手部分,即客户端发送syn段到服务器端;

tcp_v4_connect为发起连接主流程,首先对必要参数进行检查,获取路由信息,改变连接状态成SYN_SENT,再调用inet_hash_connect将控制块加入到ehash,最后调用tcp_connect发送syn;

 /* This will initiate an outgoing connection. */
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
struct inet_sock *inet = inet_sk(sk);
struct tcp_sock *tp = tcp_sk(sk);
__be16 orig_sport, orig_dport;
__be32 daddr, nexthop;
struct flowi4 *fl4;
struct rtable *rt;
int err;
struct ip_options_rcu *inet_opt; /* timewait控制块结构 */
struct inet_timewait_death_row *tcp_death_row = &sock_net(sk)->ipv4.tcp_death_row; /* 地址长度不合法 */
if (addr_len < sizeof(struct sockaddr_in))
return -EINVAL; /* 地址族不合法 */
if (usin->sin_family != AF_INET)
return -EAFNOSUPPORT; /* 设置下一跳和目的地址 */
nexthop = daddr = usin->sin_addr.s_addr; /* 获取ip选项 */
inet_opt = rcu_dereference_protected(inet->inet_opt,
lockdep_sock_is_held(sk)); /* 使用了源路由选项 */
if (inet_opt && inet_opt->opt.srr) {
if (!daddr)
return -EINVAL;
/* 下一跳地址设置为选项中的地址 */
nexthop = inet_opt->opt.faddr;
} /* 获取源端口目的端口 */
orig_sport = inet->inet_sport;
orig_dport = usin->sin_port; /* 查找路由 */
fl4 = &inet->cork.fl.u.ip4;
rt = ip_route_connect(fl4, nexthop, inet->inet_saddr,
RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,
IPPROTO_TCP,
orig_sport, orig_dport, sk);
/* 查找失败 */
if (IS_ERR(rt)) {
err = PTR_ERR(rt);
if (err == -ENETUNREACH)
IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
return err;
} /* 查找成功 */ /* 路由是组播或者广播 */
if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) {
ip_rt_put(rt);
return -ENETUNREACH;
} /* 选项为空或者未启用源路由选项 */
/* 设置目的地址为路由缓存中地址 */
if (!inet_opt || !inet_opt->opt.srr)
daddr = fl4->daddr; /* 源地址为空 */
/* 使用路由缓存中的源地址 */
if (!inet->inet_saddr)
inet->inet_saddr = fl4->saddr;
/* 设置接收地址为源地址 */
sk_rcv_saddr_set(sk, inet->inet_saddr); /* 控制块中的时间戳存在&& 目的地址不是当前地址 */
/* 控制块被使用过,重新初始化 */
if (tp->rx_opt.ts_recent_stamp && inet->inet_daddr != daddr) {
/* Reset inherited state */
tp->rx_opt.ts_recent = ;
tp->rx_opt.ts_recent_stamp = ;
if (likely(!tp->repair))
tp->write_seq = ;
} /* 设置目的端口 */
inet->inet_dport = usin->sin_port;
/* 设置目的地址 */
sk_daddr_set(sk, daddr); /* 获取ip选项长度 */
inet_csk(sk)->icsk_ext_hdr_len = ;
if (inet_opt)
inet_csk(sk)->icsk_ext_hdr_len = inet_opt->opt.optlen; /* 设置mss */
tp->rx_opt.mss_clamp = TCP_MSS_DEFAULT; /* Socket identity is still unknown (sport may be zero).
* However we set state to SYN-SENT and not releasing socket
* lock select source port, enter ourselves into the hash tables and
* complete initialization after this.
*/
/* 设置连接状态为TCP_SYN_SENT */
tcp_set_state(sk, TCP_SYN_SENT); /* 端口绑定,加入ehash */
err = inet_hash_connect(tcp_death_row, sk);
if (err)
goto failure; /* 设置hash值 */
sk_set_txhash(sk); /*
如果源端口或者目的端口发生变化,
重新获取路由,并更新sk的路由缓存
*/
rt = ip_route_newports(fl4, rt, orig_sport, orig_dport,
inet->inet_sport, inet->inet_dport, sk);
if (IS_ERR(rt)) {
err = PTR_ERR(rt);
rt = NULL;
goto failure;
}
/* OK, now commit destination to socket. */
sk->sk_gso_type = SKB_GSO_TCPV4; /* 存储目的路由缓存和网络设备特性到控制块 */
sk_setup_caps(sk, &rt->dst);
rt = NULL; if (likely(!tp->repair)) {
/* 获取发送序号 */
if (!tp->write_seq)
tp->write_seq = secure_tcp_seq(inet->inet_saddr,
inet->inet_daddr,
inet->inet_sport,
usin->sin_port);
/* 时间戳偏移 */
tp->tsoffset = secure_tcp_ts_off(inet->inet_saddr,
inet->inet_daddr);
} /* 设置ip首部的id */
inet->inet_id = tp->write_seq ^ jiffies; /* fastopen */
if (tcp_fastopen_defer_connect(sk, &err))
return err;
if (err)
goto failure; /* 发送syn */
err = tcp_connect(sk); if (err)
goto failure; return ; failure:
/*
* This unhashes the socket and releases the local port,
* if necessary.
*/
tcp_set_state(sk, TCP_CLOSE);
ip_rt_put(rt);
sk->sk_route_caps = ;
inet->inet_dport = ;
return err;
}

__inet_hash_connect将端口检查通过的控制块加入到ehash;函数对是否设置端口进行了不同处理,若未设置端口,则需要查找一个端口;函数还调用check_established检查是否可以复用处在TIME_WAIT的控制块,以及调用inet_ehash_nolisten将端口对应的控制块加入的ehash;

 int __inet_hash_connect(struct inet_timewait_death_row *death_row,
struct sock *sk, u32 port_offset,
int (*check_established)(struct inet_timewait_death_row *,
struct sock *, __u16, struct inet_timewait_sock **))
{
struct inet_hashinfo *hinfo = death_row->hashinfo;
struct inet_timewait_sock *tw = NULL;
struct inet_bind_hashbucket *head;
int port = inet_sk(sk)->inet_num;
struct net *net = sock_net(sk);
struct inet_bind_bucket *tb;
u32 remaining, offset;
int ret, i, low, high;
static u32 hint; /* 存在端口 */
if (port) {
head = &hinfo->bhash[inet_bhashfn(net, port,
hinfo->bhash_size)]; /* 找到端口绑定信息 */
tb = inet_csk(sk)->icsk_bind_hash;
spin_lock_bh(&head->lock); /* 当前端口绑定的只有当前控制块 */
if (sk_head(&tb->owners) == sk && !sk->sk_bind_node.next) {
/* 将控制块加入只ehash */
inet_ehash_nolisten(sk, NULL);
spin_unlock_bh(&head->lock);
return ;
}
spin_unlock(&head->lock);
/* No definite answer... Walk to established hash table */
/* 检查复用情况 */
ret = check_established(death_row, sk, port, NULL);
local_bh_enable();
return ret;
} /* 没有确定端口,则随机端口 */ inet_get_local_port_range(net, &low, &high);
high++; /* [32768, 60999] -> [32768, 61000[ */
remaining = high - low;
if (likely(remaining > ))
remaining &= ~1U; offset = (hint + port_offset) % remaining;
/* In first pass we try ports of @low parity.
* inet_csk_get_port() does the opposite choice.
*/
offset &= ~1U;
other_parity_scan:
port = low + offset; /* 遍历端口 */
for (i = ; i < remaining; i += , port += ) {
if (unlikely(port >= high))
port -= remaining;
/* 保留端口 */
if (inet_is_local_reserved_port(net, port))
continue; /* 找到端口对应的绑定hash桶 */
head = &hinfo->bhash[inet_bhashfn(net, port,
hinfo->bhash_size)];
spin_lock_bh(&head->lock); /* Does not bother with rcv_saddr checks, because
* the established check is already unique enough.
*/
/* 遍历绑定的链表中的节点 */
inet_bind_bucket_for_each(tb, &head->chain) { /* 找到端口相同节点 */
if (net_eq(ib_net(tb), net) && tb->port == port) { /* 设置被重用了,继续找,随机端口不能重用 */
if (tb->fastreuse >= ||
tb->fastreuseport >= )
goto next_port;
WARN_ON(hlist_empty(&tb->owners)); /* 检查timewait复用情况 */
if (!check_established(death_row, sk,
port, &tw))
goto ok;
goto next_port;
}
} /* 遍历没有重复 */ /* 创建该端口的绑定信息节点,加入绑定hash */
tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep,
net, head, port);
if (!tb) {
spin_unlock_bh(&head->lock);
return -ENOMEM;
} /* 设置默认重用标记 */
tb->fastreuse = -;
tb->fastreuseport = -;
goto ok;
next_port:
spin_unlock_bh(&head->lock);
cond_resched();
} /* 继续从下一半端口中找 */
offset++;
if ((offset & ) && remaining > )
goto other_parity_scan; return -EADDRNOTAVAIL; ok:
hint += i + ; /* Head lock still held and bh's disabled */ /* 控制块加入该端口的使用者列表 */
inet_bind_hash(sk, tb, port); /* 初始化源端口,加入到ehash */
if (sk_unhashed(sk)) {
inet_sk(sk)->inet_sport = htons(port);
inet_ehash_nolisten(sk, (struct sock *)tw);
}
/*有timewait控制块则从bind列表中移除 */
if (tw)
inet_twsk_bind_unhash(tw, hinfo);
spin_unlock(&head->lock); /* 调度销毁timewait控制块 */
if (tw)
inet_twsk_deschedule_put(tw);
local_bh_enable();
return ;
}

__inet_check_established用于检查与相同端口中处于TIME_WAIT状态的控制块是否可以复用;

 /* called with local bh disabled */
static int __inet_check_established(struct inet_timewait_death_row *death_row,
struct sock *sk, __u16 lport,
struct inet_timewait_sock **twp)
{
struct inet_hashinfo *hinfo = death_row->hashinfo;
struct inet_sock *inet = inet_sk(sk);
__be32 daddr = inet->inet_rcv_saddr;
__be32 saddr = inet->inet_daddr;
int dif = sk->sk_bound_dev_if;
INET_ADDR_COOKIE(acookie, saddr, daddr);
const __portpair ports = INET_COMBINED_PORTS(inet->inet_dport, lport);
struct net *net = sock_net(sk);
unsigned int hash = inet_ehashfn(net, daddr, lport,
saddr, inet->inet_dport);
struct inet_ehash_bucket *head = inet_ehash_bucket(hinfo, hash);
spinlock_t *lock = inet_ehash_lockp(hinfo, hash);
struct sock *sk2;
const struct hlist_nulls_node *node;
struct inet_timewait_sock *tw = NULL; spin_lock(lock); /* 遍历链表 */
sk_nulls_for_each(sk2, node, &head->chain) { /* hash不等 */
if (sk2->sk_hash != hash)
continue; /* 找到节点 */
if (likely(INET_MATCH(sk2, net, acookie,
saddr, daddr, ports, dif))) {
/* 节点连接处于timewait状态 */
if (sk2->sk_state == TCP_TIME_WAIT) {
tw = inet_twsk(sk2); /* 可以复用 */
if (twsk_unique(sk, sk2, twp))
break;
} /* 不处于tw,或者不能复用 */
goto not_unique;
}
} /* Must record num and sport now. Otherwise we will see
* in hash table socket with a funny identity.
*/
/* 设置端口和hash */
inet->inet_num = lport;
inet->inet_sport = htons(lport);
sk->sk_hash = hash;
WARN_ON(!sk_unhashed(sk)); /* 节点加入ehash */
__sk_nulls_add_node_rcu(sk, &head->chain);
if (tw) {
/* 删除tw节点 */
sk_nulls_del_node_init_rcu((struct sock *)tw);
__NET_INC_STATS(net, LINUX_MIB_TIMEWAITRECYCLED);
}
spin_unlock(lock); /* 增加使用计数 */
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, ); /* 设置能复用的控制块 */
if (twp) {
*twp = tw;
} else if (tw) {
/* Silly. Should hash-dance instead... */
inet_twsk_deschedule_put(tw);
}
return ; not_unique:
spin_unlock(lock);
return -EADDRNOTAVAIL;
}

inet_ehash_nolisten用于将控制块加入ehash,并根据结果做不同处理;

 /* 添加到ehash中 */
bool inet_ehash_nolisten(struct sock *sk, struct sock *osk)
{
/* 添加到ehash中 */
bool ok = inet_ehash_insert(sk, osk); if (ok) {
/* 成功增加计数 */
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, );
} else {
/* 增加孤儿数量 */
percpu_counter_inc(sk->sk_prot->orphan_count);
/* 标识连接关闭状态 */
sk->sk_state = TCP_CLOSE;
/* 设置销毁标记 */
sock_set_flag(sk, SOCK_DEAD);
/* 销毁控制块 */
inet_csk_destroy_sock(sk);
}
return ok;
}

tcp_connect用于构造syn包并发送之,发送之后需要设置syn包的重传定时器;

 /* Build a SYN and send it off. */
int tcp_connect(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *buff;
int err; /* 检查重建路由 */
if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk))
return -EHOSTUNREACH; /* Routing failure or similar. */ /* 初始化控制块中与连接相关的成员 */
tcp_connect_init(sk); if (unlikely(tp->repair)) {
tcp_finish_connect(sk, NULL);
return ;
} /* 分配skb */
buff = sk_stream_alloc_skb(sk, , sk->sk_allocation, true);
if (unlikely(!buff))
return -ENOBUFS; /* 无数据的skb相关控制信息初始化 */
tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN); /* 设置发送syn的时间 */
tp->retrans_stamp = tcp_time_stamp; /* 加入发送队列 */
tcp_connect_queue_skb(sk, buff); /* enc拥塞通告支持 */
tcp_ecn_send_syn(sk, buff); /* Send off SYN; include data in Fast Open. */
/* 发送syn */
err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
tcp_transmit_skb(sk, buff, , sk->sk_allocation);
if (err == -ECONNREFUSED)
return err; /* We change tp->snd_nxt after the tcp_transmit_skb() call
* in order to make this packet get counted in tcpOutSegs.
*/
/* 设置序号信息 */
tp->snd_nxt = tp->write_seq;
tp->pushed_seq = tp->write_seq;
TCP_INC_STATS(sock_net(sk), TCP_MIB_ACTIVEOPENS); /* 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 ;
}

TCP主动打开 之 第一次握手-发送SYN的更多相关文章

  1. TCP被动打开 之 第一次握手-接收SYN

    假定客户端执行主动打开,服务器执行被动打开,客户端发送syn包到服务器,服务器接收该包,进行建立连接请求的相关处理,即第一次握手:本文主要分析第一次握手中被动打开端的处理流程,主动打开端的处理请查阅本 ...

  2. TCP被动打开 之 第二次握手-发送SYN+ACK

    假定客户端执行主动打开,发送syn包到服务器,服务器执行完该包的第一次握手操作后,调用af_ops->send_synack向客户端发送syn+ack包,该回调实际调用tcp_v4_send_s ...

  3. TCP主动打开 之 第二次握手-接收SYN+ACK

    假设客户端执行主动打开,已经经过第一次握手,即发送SYN包到服务器,状态变为SYN_SENT,服务器收到该包后,回复SYN+ACK包,客户端收到该包,进行主动打开端的第二次握手部分:流程中涉及到的函数 ...

  4. TCP主动打开 之 第三次握手-发送ACK

    假定客户端执行主动打开,并且已经收到服务器发送的第二次握手包SYN+ACK,在经过一系列处理之后,客户端发送第三次握手包ACK到服务器:其流程比较简单,主要是分配skb,初始化ack包并发送:需要注意 ...

  5. TCP被动打开 之 第三次握手-接收ACK

    假定客户端主动打开,发送syn包到服务器,服务器创建连接请求控制块加入到队列,进入TCP_NEW_SYN_RECV 状态,发送syn+ack给客户端,并启动定时器,等待客户端回复最后一个握手ack: ...

  6. tcp 客户端 发送syn

    简介 sys_connect->inet_stream_connect->inet_stream_connect->tcp_v4_connect->tcp_connect对于t ...

  7. TCP连接建立系列 — 客户端发送SYN段

    主要内容:客户端调用connect()时的TCP层实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd connect的TCP层实现 SOCK_STRE ...

  8. TCP/IP的三次握手和四次放手

    一开始个人对于三次握手和四次挥手这个东西还是有时候会忘记,可能理解的不是非常深刻,所以今天就自己动手来记录一下这个知识点,方便以后查看.总结完之后发现总结的还是可以的哈哈. 三次握手建立连接 第一次: ...

  9. IP封包协议头/TCP协议头/TCP3次握手/TCP4次挥手/UDP协议头/ICMP协议头/HTTP协议(请求报文和响应报文)/IP地址/子网掩码(划分子网)/路由概念/MAC封包格式

    IP协议头IP包头格式: 1.版本号:4个bit,用来标识IP版本号.这个4位字段的值设置为二进制的0100表示IPv4,设置为0110表示IPv6.目前使用的IP协议版本号是4. 2.首部长度:4个 ...

随机推荐

  1. js之数据类型(对象类型——构造器对象——函数1)

    函数它只定义一次,但可能被多次的执行和调用.JavaScript函数是参数化的,函数的定义会包括形参和实参.形参相当于函数中定义的变量,实参是在运行函数调用时传入的参数. 一.函数定义 函数使用fun ...

  2. MySQL的左连接查询,只取出最大的一条数据

    今天有个需求,是通过两张表进行查询.一对多的关系.通过一个主键,取出其中的一条.开始以为还好,直接用用了left join on进行查询.却发现了问题所在.其他的好弄.开始的写法借鉴这篇博客:http ...

  3. Hacklab WebIDE在线调试ESP32笔记

    目录 1.什么是Hacklab WebIDE 1.1 优势 1.2 趋势 2. 使用方法 2.1 功能介绍 2.2 编译第一个程序 2.3 搭建esp32的开发环境 2.4 建立开发板与云平台的连接 ...

  4. Spinner simpleAdapte适配器 下拉列表

    public class MainActivity extends AppCompatActivity { private TextView text; private Spinner spinner ...

  5. onItemSelected 获取选中的 信息 3种方法

    @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) ...

  6. C++堆排序算法的实现

    堆排序(Heap sort)是指利用堆这种数据结构所设计的一种排序算法.堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点.堆排序可以用到上一次的 ...

  7. 关于first-class object的解释

    关于first-class object的解释 定义,什么是编程语言的第一等公民? In computer science, a programming language is said to hav ...

  8. tbdr+mrt

    有关mrt的在tbdr的架构下的内存排布 system memory肯定是dither 我对这里把握比较大 rt0 rgba8 rt1 r8 这样像素排列是rgba8r8rgba8r8rgba8r8. ...

  9. HDU-2072-单词数(字典树)

    链接: https://vjudge.net/problem/HDU-2072 题意: lily的好朋友xiaoou333最近很空,他想了一件没有什么意义的事情,就是统计一篇文章里不同单词的总数.下面 ...

  10. 炫酷CSS3垂直时间轴特效

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...