导航

socket connect函数本质含义

Posted on 2012-04-09 08:43  网名还没想好  阅读(3716)  评论(0编辑  收藏  举报

    很多介绍网络编程的书籍中会这样介绍connect系统调用:将本机的一个指定的套接字连接到一个指定地址的服务器套接字上去。下面是connect系统调用的定义:
        int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
    参数sockfd是本地机器上的一个套接字描述符,在内核的系统调用函数中该描述符会被转换成与之绑定的一个struct socket结构,这是真正的一个socket,代表了网络通讯中连接的一端。serv_addr和addrlen则是要连接的服务器的地址和地址长度。
    于是乎,有了这样的理解:connect将在本机和指定服务器间建立一个连接。但实际上,connect操作并不引发网络设备传送任何的数据到对端。它所 做的操作只是通过路由规则和路由表等一些信息,在struct socket结构中填入一些有关对端服务器的信息。这样,以后向对端发送数据报时,就不需要每次进行路由查询等操作以确定对端地址信息和本地发送接口,应 用程序也就不需要每次传入对端地址信息(可以使用send而不使用sendto)。基于这样的理解,我们就不难弄明白,为什么不只是tcp socket可以connect,udp, raw socket也可以通过connect进行连接。它们的本质其实没有多大差别:把通过路由查询得到的对端主机的地址信息缓存到套接字结构struct socket中。
    udp和raw的connect操作其实是完全一致的,都使用了myip4_datagram_connect函数。
    为方便起见,我们再以一个实际的例子来描述该函数所做的事情,我们在主机172.16.48.2上向主机172.16.48.1的端口16000发送一个 udp数据报,172.16.48.2上的udp端口由系统自动选择(为32768)。下面是一个简单的应用程序示例:
        #include <sys/types.h>
        #include <sys/socket.h>
        #include <sys/ioctl.h>
        #include "my_inet.h"
        #include <stdio.h>
        #include <errno.h>

        #include <arpa/inet.h>
        #include <unistd.h>

        int main()
        {
            int i;
            //代表服务器地址的结构。
            struct sockaddr_in dest;
            dest.sin_family = MY_PF_INET;
            dest.sin_port = htons(16000);
            dest.sin_addr.s_addr = 0x013010AC;//172.16.48.1的网络字节序。

            int fd = socket( MY_PF_INET, SOCK_DGRAM, MY_IPPROTO_UDP );
            if( fd < 0 ){
                perror("socket: ");
                return -1;
            }
            //连接操作。
            if( connect( fd, (struct sockaddr*)&dest, sizeof(dest) ) < 0 )
                perror("connect: ");
            //不必通过sendto每次传入对端地址信息了。
            int bwrite = send( fd, "abcdefg", 7, 0 );
            if( bwrite == -1 ){
                perror("send: ");
                return -1;
            }

            printf("sendto: %d\n", bwrite);
            close( fd );
            return 0;
        }
    connect系统调用的执行流在到达myip4_datagram_connect函数之前,已经对本地端口号进行自动选择,并把socket绑定到了 myudp_hash表中。到达myip4_datagram_connect函数之后,第一件事情是建立一个struct flowi:
    struct flowi fl = { .oif = 0,   //输出设备接口未定。
            .nl_u = { .ip4_u = { .daddr = 172.16.48.1       //目的地址。
                                 .saddr = 0.0.0.0           //源地址未定。
                                 .tos   = 0 } },            //一般服务
            .proto = MY_IPPROTO_UDP,        //UDP协议
            .uli_u = { .ports =
                            { .sport = 32768,               //自动选择的第一个源端口
                              .dport = 16000 } } };         //目的端口
    以该结构体为信息查询路由表,结果肯定查到main表,确定saddr为172.16.48.2。并得到一个struct rtable结构作为路由查询结果。
    对于my_inet域的套接字,结构体struct socket有一个成员struct inet_sock sock代表网络层的一个套接字,其成员rcv_saddr(含义尚不明确)和saddr被赋172.16.48.2,daddr, dport被赋于服务器的地址和端口号。而表示连接状态的sk_state成员被赋于TCP_ESTABLISHED,这里需要注意的是 TCP_ESTABLISHED并不专指TCP连接建立状态,所有执行connect成功的套接字,其状态都是TCP_ESTABLISHED。id被赋 于当前时间。成员sk_dst_cache指向路由查询结果rtable的成员u.dst。从而套接字完全缓存路由查询的结果。
    执行了connect后的socket,需要发送数据报时,关于对端的信息全部可以从socket本身得到。但需要重申的一点是:由于路由缓存的存在,在连接的socket上发送数据报并不会比在未连接的socket上发送数据报效率高多少。