Linux tcp/ip 源码分析 - connect

connect方法对应的内核源码为

// net/socket.c SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr, int, addrlen) { struct socket *sock; struct sockaddr_storage address; ... sock = sockfd_lookup_light(fd, &err, &fput_needed); ... err = move_addr_to_kernel(uservaddr, addrlen, &address); ... err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen, sock->file->f_flags); ... return err; }

方法描述

1. 根据fd找到socket。

2. 拷贝用户提供的目标地址到address变量。

3. 调用sock->ops->connect方法继续执行connect操作。

由第一篇文章可以知道,sock->ops->connect指向的方法为inet_stream_connect。

// net/ipv4/af_inet.c int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags) { ... err = __inet_stream_connect(sock, uaddr, addr_len, flags, 0); ... return err; } EXPORT_SYMBOL(inet_stream_connect);

该方法又调用了__inet_stream_connect方法。

// net/ipv4/af_inet.c int __inet_stream_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags, int is_sendmsg) { struct sock *sk = sock->sk; int err; long timeo; ... switch (sock->state) { ... case SS_UNCONNECTED: ... err = sk->sk_prot->connect(sk, uaddr, addr_len); ... sock->state = SS_CONNECTING; ... err = -EINPROGRESS; break; } timeo = sock_sndtimeo(sk, flags & O_NONBLOCK); if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) { ... if (!timeo || !inet_wait_for_connect(sk, timeo, writebias)) goto out; ... } /* Connection was closed by RST, timeout, ICMP error * or another process disconnected us. */ if (sk->sk_state == TCP_CLOSE) goto sock_error; ... sock->state = SS_CONNECTED; err = 0; out: return err; sock_error: err = sock_error(sk) ? : -ECONNABORTED; sock->state = SS_UNCONNECTED; ... goto out; } EXPORT_SYMBOL(__inet_stream_connect);

方法描述

1. 当sock->state为SS_UNCONNECTED时,调用sk->sk_prot->connect方法向目标地址发送SYN消息,同时将sk->sk_state设置为TCP_SYN_SENT状态。

2. 设置sock->state为SS_CONNECTING状态。

3. 设置err为-EINPROGRESS,供后面使用。

4. 根据flags是否有nonblock标志,设置timeo的值,如果有,则设置为0,如果没有,则设置为连接超时时间。

5. 如果timeo等于0,则直接返回当前err给用户,即-EINPROGRESS。

6. 如果timeo不等于0,则调用inet_wait_for_connect方法堵塞线程,直至连接状态发生变化或超时。

当连接建立成功后,sk->sk_state的值会被设置成TCP_ESTABLISHED,同时堵塞的线程会被唤醒,使其从inet_wait_for_connect方法中返回,返回值大于0。

线程从inet_wait_for_connect方法返回后,会设置sock->state的值为SS_CONNECTED,然后返回0给用户,表示连接建立成功。

当连接失败后,会先把错误码赋值到sk->sk_err,然后设置sk->sk_state的值为TCP_CLOSE,之后再唤醒阻塞线程,使其从inet_wait_for_connect方法中返回,返回值大于0。

线程从inet_wait_for_connect方法返回后,会调用sock_error方法,将sk->err的值取出并返回给用户。

当用户设置了连接超时时间,且等待了这么长时间连接状态都没法发生变化,则超时定时器会唤醒阻塞线程,使其从inet_wait_for_connect方法中返回,返回值等于0,之后执行return语句,将err的值-EINPROGRESS返回给用户。

我们再来看下sk->sk_prot->connect方法。

根据第一篇文章我们可以知道,该方法为tcp_v4_connect。

// net/ipv4/tcp_ipv4.c int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) { ... tcp_set_state(sk, TCP_SYN_SENT); err = inet_hash_connect(tcp_death_row, sk); ... err = tcp_connect(sk); ... return 0; } EXPORT_SYMBOL(tcp_v4_connect);

方法描述

1. 设置sk->sk_state值为TCP_SYN_SENT。

2. 调用inet_hash_connect方法将sk放入到全局的hashtable中,这样当它有返回消息时,可以从hashtable中再找到这个sk。

3. 调用tcp_connect方法发送SYN消息,及启动SYN消息的重传定时。

// net/ipv4/tcp_output.c int tcp_connect(struct sock *sk) { struct tcp_sock *tp = tcp_sk(sk); struct sk_buff *buff; int err; ... buff = sk_stream_alloc_skb(sk, 0, sk->sk_allocation, true); ... tcp_rbtree_insert(&sk->tcp_rtx_queue, buff); ... 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; } EXPORT_SYMBOL(tcp_connect);

在该方法中,tcp_send_syn_data/tcp_transmit_skb方法是用来发送buff,inet_csk_reset_xmit_timer方法是用来启动重传定时。

完。

本文分享自微信公众号 - Linux内核及JVM底层相关技术研究(ytcode)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-02-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券