(本文部分参考了《Linux内核源代码情景分析》)
操作SYS_CONNECT请求连接由sys_connect()完成。
有连接”模式的插口与“无连接”模式的插口都可以调用库函数 connect(),但是意义却不同。内核中的函数 sys_connect()是二者公用的。
sys_connect()代码如下:
/* connet系统调用 */
asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr, int
addrlen)
{
struct socket *sock;
char address[MAX_SOCK_ADDR];
int err;
sock = sockfd_lookup(fd, &err);/* 查找文件句柄对应的socket */
if (!sock)
goto out;
/* 从用户态复制地址参数到内核中 */
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;
/* 调用传输层的connet方法inet_stream_connect或inet_dgram_connect */
err = sock->ops->connect(sock, (struct sockaddr *) address, addrlen,
sock->file->f_flags);
out_put:
sockfd_put(sock);
out:
return err;
}
本文只看“有连接”模式的插口。如前所述,只有 client 插口才可以(并且一定要)通过 connect()向一个 server 插口提出连接请求,在请求被 server 插口接受而建立起连接之前是不能在两个插口之间传递数据报文的。
inet_stream_connect()代码如下:
/* 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;
/* Fall out of switch with err, set for this state */
break;
case SS_UNCONNECTED:/* 只有此状态才能调用connect */
err = -EISCONN;
if (sk->sk_state != TCP_CLOSE)/* 如果不是TCP_CLOSE状态,说明已经连接了 */
goto out;
/* 调用传输层接口tcp_v4_connect建立与服务器连接,并发送SYN段 */
err = sk->sk_prot->connect(sk, uaddr, addr_len);
if (err < 0)
goto out;
/* 发送SYN段后,设置状态为SS_CONNECTING */
sock->state = SS_CONNECTING;
err = -EINPROGRESS;/* 如果是以非阻塞方式进行连接,则默认的返回值为
EINPROGRESS,表示正在连接 */
break;
}
/* 获取连接超时时间,如果指定非阻塞方式,则不等待直接返回 */
timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
/* 发送完SYN
后,连接状态一般为这两种状态,但是如果连接建立非常快,则可能越过这两种状态 */
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()函数主要功能如下:
(1)调用tcp_v4_connect函数建立与服务器联系并发送SYN段;
(2)获取连接超时时间timeo,如果timeo不为0,则会调用inet_wait_for_connect一直等待到连接成功或超时;
tcp_v4_connect函数代码如下,比较长,分段来看:
/* 建立与服务器连接,发送SYN段 */
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
struct inet_sock *inet = inet_sk(sk);
struct tcp_sock *tp = tcp_sk(sk);
struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
struct rtable *rt;
u32 daddr, nexthop;
int tmp;
int err;
/* 校验目的地址的长度及地址族的有效性 */
if (addr_len < sizeof(struct sockaddr_in))
return -EINVAL;
if (usin->sin_family != AF_INET)
return -EAFNOSUPPORT;
/* 将下一跳和目的地址都设置为源地址 */