您的位置:首页 > 理论基础 > 计算机网络

TCP/IP工作流5 connect开始

2017-01-13 21:32 477 查看

TCP/IP工作流5 connect

引子

沿上一篇的思路,当socket创建完成了。就要调用connect函数来连接到远程的服务器了。

int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen)


这个函数原型中,sockfd是就用socket调用得到一个socket的文件ID。addr参数是,远程要连接到的服务器的地址了。addrlen是这个地址的描述符的长度。这里,我就有了一个疑问了,struct sockaddr的类型是确定的,为什么这里要要求一个长度呢?看源码当然能找到答案,我们先猜测下,看代码的不多的乐趣中的一个。

* 猜想1:难道我们可以连接到远程多个服务器,或者有多个备选的服务器可供使用。从系统可靠性的角度来说,这个猜测是合理的,为了可靠的服务,我提供给用户一个可选的服务器列表,让用户一个个都去尝试下,选择最优的,或者当前的服务器宕掉了,选择下一个备用的。这里的实现就是建立一个列表,把服务器的地址都写进去,然后把列表的总长度作为参数传递进去。但这个猜测也有不合理的地方。

* 猜想2:如果是一个列表的话,可以直接把列表中的元素的数量作为参数传递进去。何必传递一个地址长度,还要让函数自己计算数量。那么有了第二个猜测,就是struct sockaddr是一个变长的结构体,变长的结构体应该怎么实现呢?C语言里,一般把结构体里的最后一个成员设置为一个指针,用来指向一个数组。这个也不合理,因为如果是一个指针的话,就是一个地址长度。32位OS是4字节,也是一个定值,所以如何实现变长,还不明所以,这个推断也不合理。

* 猜想3:sockaddr会在编译时根据一些配置选项,来确定要不要包含一些成员。这个猜测也不合理。针对一个编译后的变量,用一个sizeof也可以求出它的长度。

综合以上3种猜测,现在看来,第一种最合理。去源码中看下。

开始源码

connect调用还是开始于sys_sockcall函数。

sys_socketcall(call, args) net/socket.c 2004

然后调用sys_connect。

2032 sys_connect(a0, (struct sockaddr __user *)a0, a[2]);


sys_connect函数定义为于net/socket.c中。

1479 asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr,
1480                int addrlen)
1481 {
1482    struct socket *sock;
1483    char address[MAX_SOCK_ADDR];
1484    int err, fput_needed;
1486    sock = sockfd_lookup_light(fd, &err, &fput_needed);
1487    if (!sock)
1488        goto out;
1489    err = move_addr_to_kernel(uservaddr, addrlen, address);
1490    if (err < 0)
1491        goto out_put;
1493    err =
1494        security_socket_connect(sock, (struct sockaddr *)address, addrlen);
1495    if (err)
1496        goto out_put;
1498    err = sock->ops->connect(sock, (struct sockaddr *)address, addrlen,
1499                 sock->file->f_flags);
1500 out_put:
1501    fput_light(sock->file, fput_needed);
1502 out:
1503    return err;
1504 }


从函数的声明看到,从connect函数调用传递过来的两个参数还没有得到处理。

1482 就不用再说了。是用户层的socket的表达。

1483 定义了一个最大地址数的字符数组。C语言里没有单字节数据类型,都是用char类型来代替的。这里定义的MAX_SOCK_ADDR为128。我们假设这里存的是IP地址,我们知道IPV4里一个地址要用4个字节表示,那么这里能存贮的地址的最大数量为128/4 = 32。

1484 定义两个临时变量。

1486 根据文件ID fd去得到其sock的地址。这里恰好是之前socket创建时的反过程上。从创建时,不难看出,大概的流程是根据fd找到struct file,struct file中的private变量就是指向struct sock的地址。感兴趣的可以去看下。

1489 把用户空间的地址,转换到内核空间地址。因为系统调用目前还是在内核空间里操作,想要处理用户传递来的数据就需要用到move_addr_to_kernel的函数调用。

1494 安全检查,有以前的经验知道这里面跟协议相关的东西没有多少,我们先不考虑。

1498 这里是真正执行调用的地方。第一篇 TCP/IP工作流的开始 socket创建 1 中在socket创建过程中我们知道,这里sock->ops为inet_stream_ops函数集。根据它的定义,最终执行的函数是inet_stream_connect。

546 int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
547             int addr_len, int flags)
548 {
549     struct sock *sk = sock->sk;
550     int err;
551     long timeo;
553     lock_sock(sk);
555     if (uaddr->sa_family == AF_UNSPEC) {
556         err = sk->sk_prot->disconnect(sk, flags);
557         sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
558         goto out;
559     }
561     switch (sock->state) {
562     default:
563         err = -EINVAL;
564         goto out;
565     case SS_CONNECTED:
566         err = -EISCONN;
567         goto out;
568     case SS_CONNECTING:
569         err = -EALREADY;
571         break;
572     case SS_UNCONNECTED:
573         err = -EISCONN;
574         if (sk->sk_state != TCP_CLOSE)
575             goto out;
577         err = sk->sk_prot->connect(sk, uaddr, addr_len);
578         if (err < 0)
579             goto out;
581         sock->state = SS_CONNECTING;
587         err = -EINPROGRESS;
588         break;
589     }
591     timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
593     if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
595         if (!timeo || !inet_wait_for_connect(sk, timeo))
596             goto out;
598         err = sock_intr_errno(timeo);
599         if (signal_pending(current))
600             goto out;
601     }
606     if (sk->sk_state == TCP_CLOSE)
607         goto sock_error;
614     sock->state = SS_CONNECTED;
615     err = 0;
616 out:
617     release_sock(sk);
618     return err;
620 sock_error:
621     err = sock_error(sk) ? : -ECONNABORTED;
622     sock->state = SS_UNCONNECTED;
623     if (sk->sk_prot->disconnect(sk, flags))
624         sock->state = SS_DISCONNECTING;
625     goto out;
626 }


经过以上源码,我们发现,最终执行的是sk->sk_prot->connect函数。通过之前的TCP/IP工作流的开始 socket创建2 中的分析,这里最终执行的是tcp_v4_connect。OK,下一篇就开始真正的TCP协议中与连接相关的部分了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  socket TCP-IP linux源码