對于面向連接的協議,如 TCP, connect() 建立一條與指定的外部地址的連接。若在connect調用之前沒有綁定地址和端口,則會自動綁定一個地址和端口號套接口。

對于面向連接的協議,如 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);
/* socket的協議族錯誤 */
if (uaddr->sa_family == AF_UNSPEC) {
/* 如果使用的是TCP,則sk_prot為tcp_prot,disconnect為tcp_disconnect() */
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;
/* Fall out of switch with err, set for this state */
break;
case SS_UNCONNECTED: /* 此套接口尚未連接對端的套接口,即連接尚未建立 */
err = -EISCONN;
if (sk->sk_state != TCP_CLOSE)
goto out;
/* 如果使用的是TCP,則sk_prot為tcp_prot,connect為tcp_v4_connect() */
err = sk->sk_prot->connect(sk, uaddr, addr_len); /* 發送SYN包 */
if (err < 0)
goto out;
/* 發出SYN包后socket狀態設為正在連接 */
sock->state = SS_CONNECTING;
/* Just entered SS_CONNECTING state; the only
* difference is that return value in non-blocking
* case is EINPROGRESS, rather than EALREADY.
*/
err = -EINPROGRESS;
break;
}
/* sock的發送超時時間,非阻塞則為0 */
timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
/* 發出SYN包后,等待后續握手的完成 */
if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
/* Error code is set above */
/* 如果是非阻塞的,那么就直接返回錯誤碼-EINPROGRESS。
* socket為阻塞時,使用inet_wait_for_connect()來等待協議棧的處理:
* 1. 使用SO_SNDTIMEO,睡眠時間超過timeo就返回0,之后返回錯誤碼-EINPROGRESS。
* 2. 收到信號,就返回剩余的等待時間。之后會返回錯誤碼-ERESTARTSYS或-EINTR。
* 3. 三次握手成功,被sock I/O事件處理函數喚醒,之后會返回0。
*/
if (!timeo || !inet_wait_for_connect(sk, timeo))
goto out;
err = sock_intr_errno(timeo);
/* 進程收到信號,如果err為-ERESTARTSYS,接下來庫函數會重新調用connect() */
if (signal_pending(current))
goto out;
}
/* Connection was closed by RST, timeout, ICMP error
* or another process disconnected us.
*/
if (sk->sk_state == TCP_CLOSE)
goto sock_error;
/* sk->sk_err may be not zero now, if RECVERR was ordered by user
* and error was received after socket entered established state.
* Hence, it is handled normally after connect() return successfully.
*/
/* 更新socket狀態為連接已建立 */
sock->state = SS_CONNECTED;
/* 清除錯誤碼 */
err = 0;
out:
release_sock(sk);
return err;
sock_error:
err = sock_error(sk) ? : -ECONNABORTED;
sock->state = SS_UNCONNECTED;
/* 如果使用的是TCP,則sk_prot為tcp_prot,disconnect為tcp_disconnect() */
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);
/* 把等待任務加入到socket的等待隊列頭部,把進程的狀態設為TASK_INTERRUPTIBLE */
prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
/* Basic assumption: if someone sets sk->sk_err, he _must_
* change state of the socket from TCP_SYN_*.
* Connect() does not allow to get error notifications
* without closing the socket.
*/
/* 完成三次握手后,狀態就會變為TCP_ESTABLISHED,從而退出循環 */
while ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
release_sock(sk);
/* 進入睡眠,直到超時或收到信號,或者被I/O事件處理函數喚醒。
* 1. 如果是收到信號退出的,timeo為剩余的jiffies。
* 2. 如果使用了SO_SNDTIMEO選項,超時退出后,timeo為0。
* 3. 如果沒有使用SO_SNDTIMEO選項,timeo為無窮大,即MAX_SCHEDULE_TIMEOUT,
* 那么返回值也是這個,而超時時間不定。為了無限阻塞,需要上面的while循環。
*/
timeo = schedule_timeout(timeo);
/* 被喚醒后重新上鎖 */
lock_sock(sk);
/* 如果進程有待處理的信號,或者睡眠超時了,退出循環,之后會返回錯誤碼 */
if (signal_pending(current) || !timeo)
break;
/* 繼續睡眠 */
prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
}
/* 等待結束時,把等待進程從等待隊列中刪除,把當前進程的狀態設為TASK_RUNNING */
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)) {
/* 指向sock_def_wakeup,會喚醒調用connect()的進程,完成連接的建立 */
sk->sk_state_change(sk);
/* 若采用異步通知,則發送SIGIO通知進程可寫 */
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);
/* 有進程阻塞在此socket上 */
if (sk->sk_sleep && waitqueue_active(sk->sk_sleep))
/* 喚醒此socket上的所有睡眠進程 */
wake_up_interruptible_all(sk->sk_sleep);
read_unlock(&sk->sk_callback_lock);
}
最終調用 __wake_up_common(),由于nr_exclusive 為 0,因此會把此socket 上所有的等待進程都喚醒。