对于面向连接的协议,如 TCP, connect() 建立一条与指定的外部地址的连接。若在connect调用之前没有绑定地址和端口,则会自动绑定一个地址和端口号套接口。
asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr,
int addrlen)
{
struct socket *sock;
char address[MAX_SOCK_ADDR];
int err, fput_needed;
//根据文件描述符获取套接口指针,并且返回是否需要减少对文件引用计数标志。
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (!sock)
goto out;
//将用户空间的uservaddr数据复制到内核空间的address
err = move_addr_to_kernel(uservaddr, addrlen, address);
if (err < 0)
goto out_put;
err =
security_socket_connect(sock, (struct sockaddr *)address, addrlen);
if (err)
goto out_put;
//通过套接口系统调用的跳转表proto_ops,调用connect操作。TCP 中为 inet_stream_connect(), UDP 为 inet_dgram_connect()
err = sock->ops->connect(sock, (struct sockaddr *)address, addrlen,
sock->file->f_flags);
out_put:
// 根据fput_needed标志,调用fput_light减少对文件引用计数操作
fput_light(sock->file, fput_needed);
out:
return err;
}
通过套接口系统调用的跳转表 proto_ops ,调用 inet_stream_connect。
int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
int addr_len, int flags)
{
struct sock *sk = sock->sk;
int err;
long timeo;
lock_sock(sk);
if (uaddr->sa_family == AF_UNSPEC) {
err = sk->sk_prot->disconnect(sk, flags);
sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
goto out;
}
switch (sock->state) {
default:
err = -EINVAL;
goto out;
case SS_CONNECTED:
err = -EISCONN;
goto out;
case SS_CONNECTING:
err = -EALREADY;
break;
case SS_UNCONNECTED:
err = -EISCONN;
if (sk->sk_state != TCP_CLOSE)
goto out;
err = sk->sk_prot->connect(sk, uaddr, addr_len);
if (err < 0)
goto out;
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))
goto out;
err = sock_intr_errno(timeo);
if (signal_pending(current))
goto out;
}
if (sk->sk_state == TCP_CLOSE)
goto sock_error;
sock->state = SS_CONNECTED;
err = 0;
out:
release_sock(sk);
return err;
sock_error:
err = sock_error(sk) ? : -ECONNABORTED;
sock->state = SS_UNCONNECTED;
if (sk->sk_prot->disconnect(sk, flags))
sock->state = SS_DISCONNECTING;
goto out;
}
inet_stream_connect() 主要做了以下事情:
对协议族进行检查。
此时套接口状态为 SS_UNCONNECTED, 调用 tcp_v4_connect() 来发送SYN包。
等待后续握手的完成:
1、如果socket是非阻塞的,那么就直接返回错误码 -EINPROGRESS。
2、如果socket为阻塞的,就调用 inet_wait_for_connect(),通过睡眠来等待。在以下三种情况下会被唤醒:
- 使用 SO_SNDTIMEO 选项时,睡眠时间超过设定值,返回 0。connect()返回错误码 -EINPROGRESS。
- 收到信号,返回剩余的等待时间。connect()返回错误码 -ERESTARTSYS 或 -EINTR。
- 三次握手成功,sock的状态从 TCP_SYN_SENT 或 TCP_SYN_RECV 变为TCP_ESTABLISHED,sock I/O事件的状态变化处理函数sock_def_wakeup() 就会唤醒进程。connect() 返回0。
客户端调用tcp_v4_connect 发送SYN包时,设置客户端状态为 TCP_SYN_SENT。
进程休眠
static long inet_wait_for_connect(struct sock *sk, long timeo)
{
DEFINE_WAIT(wait);
prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
while ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
release_sock(sk);
timeo = schedule_timeout(timeo);
lock_sock(sk);
if (signal_pending(current) || !timeo)
break;
prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
}
finish_wait(sk->sk_sleep, &wait);
return timeo;
}
当前进程加入到 socket 的等待队列 sk_sleep 中,然后进入休眠,直到超时或接收到信号。
进程被唤醒
在三次握手中,当客户端收到 SYN+ACK、发出ACK后,连接就成功建立了。此时连接的状态从TCP_SYN_SENT或TCP_SYN_RECV变成了 TCP_ESTABLISHED,表示连接建立成功。最终会调用 sock_def_wakeup() 来处理连接状态变化事件,唤醒进程,connect()成功返回。
调用过程如下
tcp_v4_rcv
-> tcp_v4_do_rcv
-> tcp_rcv_state_process
-> tcp_rcv_synsent_state_process
-> sk_wake_async(sk, 0, POLL_OUT);
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
struct tcphdr *th, unsigned len)
{
...
if (!sock_flag(sk, SOCK_DEAD)) {
sk->sk_state_change(sk);
sk_wake_async(sk, 0, POLL_OUT);
}
...
}
当链路建立成功后异步发送SIGIO信号,唤醒阻塞的进程并通知 socket 可写,这也就是为什么非阻塞调用 connect 时检查 socket 是否可写事件的原因。
static void sock_def_wakeup(struct sock *sk)
{
read_lock(&sk->sk_callback_lock);
if (sk->sk_sleep && waitqueue_active(sk->sk_sleep))
wake_up_interruptible_all(sk->sk_sleep);
read_unlock(&sk->sk_callback_lock);
}
最终调用 __wake_up_common(),由于nr_exclusive 为 0,因此会把此socket 上所有的等待进程都唤醒。
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341