zoukankan      html  css  js  c++  java
  • 【Linux 内核网络协议栈源码剖析】connect 函数剖析(一)


    http://blog.csdn.net/wenqian1991/article/details/46713505


    TCP客户用 connect 函数来建立与 TCP 服务器的连接,其实是客户利用 connect 函数向服务器端发出连接请求。

    1、应用层——connect 函数

    1. #include <sys/socket.h>  
    2. int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);  
    3. /*sockfd是由socket函数返回的套接口描述字,第二、第三个参数分别是一个指向套接口地址结构的指针和该结构的大小。套接口地址结构必须含有服务器的IP地址和端口号*/  
    上面的 sockfd 套接字描述符是客户端的套接字。

    2、BSD Socket 层——sock_connect 函数

    1. /* 
    2.  *  首先将要连接的源端地址从用户缓冲区复制到内核缓冲区,之后根据套接字目前所处状态 
    3.  *  采取对应措施,如果状态有效,转调用connect函数 
    4.  */  
    5.  //这是客户端,表示客户端向服务器端发送连接请求  
    6. static int sock_connect(int fd, struct sockaddr *uservaddr, int addrlen)  
    7. {  
    8.     struct socket *sock;  
    9.     struct file *file;  
    10.     int i;  
    11.     char address[MAX_SOCK_ADDR];  
    12.     int err;  
    13.     //参数有效性检查  
    14.     if (fd < 0 || fd >= NR_OPEN || (file=current->files->fd[fd]) == NULL)  
    15.         return(-EBADF);  
    16.     //给定文件描述符返回socket结构以及file结构指针  
    17.     if (!(sock = sockfd_lookup(fd, &file)))  
    18.         return(-ENOTSOCK);  
    19.     //用户地址空间数据拷贝到内核地址空间  
    20.     if((err=move_addr_to_kernel(uservaddr,addrlen,address))<0)  
    21.         return err;  
    22.       
    23.     //根据状态采取对应措施  
    24.     switch(sock->state)   
    25.     {  
    26.         case SS_UNCONNECTED:  
    27.             /* This is ok... continue with connect */  
    28.             break;  
    29.         case SS_CONNECTED:  
    30.             /* Socket is already connected */  
    31.             if(sock->type == SOCK_DGRAM) /* Hack for now - move this all into the protocol */  
    32.                 break;  
    33.             return -EISCONN;  
    34.         case SS_CONNECTING:  
    35.             /* Not yet connected... we will check this. */  
    36.           
    37.             /* 
    38.              *  FIXME:  for all protocols what happens if you start 
    39.              *  an async connect fork and both children connect. Clean 
    40.              *  this up in the protocols! 
    41.              */  
    42.             break;  
    43.         default:  
    44.             return(-EINVAL);  
    45.     }  
    46.     //调用下层函数(inet_connect())  
    47.     i = sock->ops->connect(sock, (struct sockaddr *)address, addrlen, file->f_flags);  
    48.     if (i < 0)   
    49.     {  
    50.         return(i);  
    51.     }  
    52.     return(0);  
    53. }  
    该函数比较简单,主要是调用下层函数来实现,该函数则负责下层函数调用前的准备工作。

    3、INET Socket 层——inet_connect 函数

    客户端套接字的端口号是在这个函数中绑定的。

    1. /* 
    2.  *  Connect to a remote host. There is regrettably still a little 
    3.  *  TCP 'magic' in here. 
    4.  */  
    5.  //完成套接字的连接请求操作,这是客户端主动向服务器端发送请求  
    6.  //sock是客户端套接字,后面的uaddr,addr_len则是对端服务器端的地址信息  
    7. static int inet_connect(struct socket *sock, struct sockaddr * uaddr,  
    8.           int addr_len, int flags)  
    9. {  
    10.     struct sock *sk=(struct sock *)sock->data;  
    11.     int err;  
    12.     sock->conn = NULL;  
    13.     //正在与远端取得连接,且tcp对应的状态  
    14.     if (sock->state == SS_CONNECTING && tcp_connected(sk->state))  
    15.     {  
    16.         sock->state = SS_CONNECTED;//直接设置字段为已经连接  
    17.         /* Connection completing after a connect/EINPROGRESS/select/connect */  
    18.         return 0;   /* Rock and roll */  
    19.     }  
    20.     //正在取得连接,且是tcp协议,非阻塞  
    21.     if (sock->state == SS_CONNECTING && sk->protocol == IPPROTO_TCP && (flags & O_NONBLOCK)) {  
    22.         if (sk->err != 0)  
    23.         {  
    24.             err=sk->err;  
    25.             sk->err=0;  
    26.             return -err;  
    27.         }  
    28.         //返回正在进行状态  
    29.         return -EALREADY;   /* Connecting is currently in progress */  
    30.     }  
    31.     //不是处于正在连接处理状态(现在进行时态)  
    32.     if (sock->state != SS_CONNECTING)   
    33.     {  
    34.         /* We may need to bind the socket. */  
    35.         //自动绑定一个端口号,客户端自动绑定端口号是在connect函数中实现的  
    36.         if(inet_autobind(sk)!=0)  
    37.             return(-EAGAIN);  
    38.         if (sk->prot->connect == NULL) //不支持该项操作,没有指定操作函数  
    39.             return(-EOPNOTSUPP);  
    40.         //转调用connect函数(传输层 tcp_connect函数)  
    41.         err = sk->prot->connect(sk, (struct sockaddr_in *)uaddr, addr_len);  
    42.         if (err < 0)   
    43.             return(err);  
    44.         sock->state = SS_CONNECTING;//设置状态字段,表示正在连接过程中  
    45.     }  
    46.     //这个状态下,这是关闭信号。各个状态描述,参考下面链接  
    47.     http://blog.csdn.net/wenqian1991/article/details/40110703  
    48.     //清楚,这里有两个state,一个是socket的state(套接字所处的连接状态),一个是sock的state(涉及到协议,比如tcp的状态)  
    49.     //上面调用下层connect函数,会更新sk->state,如果出现>TCP_FIN_WAIT2,表明连接过程出现了异常  
    50.     if (sk->state > TCP_FIN_WAIT2 && sock->state==SS_CONNECTING)  
    51.     {  
    52.         sock->state=SS_UNCONNECTED;//连接未建立  
    53.         cli();  
    54.         err=sk->err;  
    55.         sk->err=0;  
    56.         sti();  
    57.         return -err;  
    58.     }  
    59.     //没有建立,就是在正在建立的路上  
    60.     if (sk->state != TCP_ESTABLISHED &&(flags & O_NONBLOCK))   
    61.         return(-EINPROGRESS);//过程正在处理  
    62.   
    63.     cli(); /* avoid the race condition */  
    64.     //这里的while实则是等待下层函数(前面的connect调用)的返回  
    65.     //正常退出while循环,表示连接成功  
    66.     while(sk->state == TCP_SYN_SENT || sk->state == TCP_SYN_RECV)   
    67.     {  
    68.         interruptible_sleep_on(sk->sleep);//添加到sk中的等待队列中,直到资源可用被唤醒  
    69.         if (current->signal & ~current->blocked)   
    70.         {  
    71.             sti();  
    72.             return(-ERESTARTSYS);  
    73.         }  
    74.         /* This fixes a nasty in the tcp/ip code. There is a hideous hassle with 
    75.            icmp error packets wanting to close a tcp or udp socket. */  
    76.         if(sk->err && sk->protocol == IPPROTO_TCP)  
    77.         {  
    78.             sti();  
    79.             sock->state = SS_UNCONNECTED;  
    80.             err = -sk->err;  
    81.             sk->err=0;  
    82.             return err; /* set by tcp_err() */  
    83.         }  
    84.     }  
    85.     sti();  
    86.     sock->state = SS_CONNECTED;//成功建立连接  
    87.     if (sk->state != TCP_ESTABLISHED && sk->err) //出错处理  
    88.     {  
    89.         sock->state = SS_UNCONNECTED;  
    90.         err=sk->err;  
    91.         sk->err=0;  
    92.         return(-err);  
    93.     }  
    94.     return(0);  
    95. }  
    实质操作落到了下一层函数(tcp_connect函数)

    4、传输层——tcp_connect 函数

    tcp_connect 函数是由客户端调用的,客户端通过这个函数获得对端的地址信息(ip地址和端口号),另外本地ip地址也是在这个函数中指定的。三次握手阶段起于 connect 函数,自然地,在该函数指定目的地址,以及设置标志字段,定时器以后,就需要向服务器端发送连接请求数据包,对应操作在该函数最后。

    1. /* 
    2.  *  This will initiate an outgoing connection.  
    3.  */  
    4.  //同accept; connect->sock_connect->inet_connect->tcp_connect  
    5.  //connect就是客户端向服务器端发出连接请求  
    6.  //参数:sk:客户端套接字;usin和addrlen分别是一个指向服务器端套接口地址结构的指针和该结构的大小  
    7. static int tcp_connect(struct sock *sk, struct sockaddr_in *usin, int addr_len)  
    8. {  
    9.     struct sk_buff *buff;  
    10.     struct device *dev=NULL;  
    11.     unsigned char *ptr;  
    12.     int tmp;  
    13.     int atype;  
    14.     struct tcphdr *t1;//tcp首部  
    15.     struct rtable *rt;//ip路由表  
    16.   
    17.     if (sk->state != TCP_CLOSE) //不是关闭状态就是表示已经建立连接了  
    18.     {  
    19.         return(-EISCONN);//连接已建立  
    20.     }  
    21.   
    22.     //地址结构大小检查  
    23.     if (addr_len < 8)   
    24.         return(-EINVAL);  
    25.   
    26.     //地址簇检查,INET域  
    27.     if (usin->sin_family && usin->sin_family != AF_INET)   
    28.         return(-EAFNOSUPPORT);  
    29.   
    30.     /* 
    31.      *  connect() to INADDR_ANY means loopback (BSD'ism). 
    32.      */  
    33.       
    34.     if(usin->sin_addr.s_addr==INADDR_ANY)//指定一个通配地址  
    35.         usin->sin_addr.s_addr=ip_my_addr();//本地ip地址(dev_base设备)  
    36.             
    37.     /* 
    38.      *  Don't want a TCP connection going to a broadcast address  
    39.      */  
    40.      //检查ip传播地址方式。广播、多播均不可行  
    41.     if ((atype=ip_chk_addr(usin->sin_addr.s_addr)) == IS_BROADCAST || atype==IS_MULTICAST)   
    42.         return -ENETUNREACH;  
    43.   
    44.   //sk已经具备本地地址信息,这里在赋值目的地址信息,这样sock套接字就具备了本地与对端两者的地址信息  
    45.   //知道住哪了,就知道怎么去了  
    46.     sk->inuse = 1;//加锁  
    47.     sk->daddr = usin->sin_addr.s_addr;//远端地址,即要请求连接的对端服务器地址  
    48.     sk->write_seq = tcp_init_seq();//初始化一个序列号,跟当前时间挂钩的序列号  
    49.     sk->window_seq = sk->write_seq;//窗口大小,用write_seq初始化  
    50.     sk->rcv_ack_seq = sk->write_seq -1;//目前本地接收到的对本地发送数据的应答序列号,表示此序号之前的数据已接收  
    51.     sk->err = 0;//错误标志清除  
    52.     sk->dummy_th.dest = usin->sin_port;//端口号赋值给tcp首部目的地址  
    53.     release_sock(sk);//重新接收暂存的数据包  
    54.   
    55.     buff = sk->prot->wmalloc(sk,MAX_SYN_SIZE,0, GFP_KERNEL);//分配一个网络数据包结构  
    56.     if (buff == NULL)   
    57.     {  
    58.         return(-ENOMEM);  
    59.     }  
    60.     sk->inuse = 1;  
    61.     buff->len = 24;//指定数据部分长度(头+数据)  
    62.     buff->sk = sk;//绑定套接字  
    63.     buff->free = 0;//发送完数据包后,不立即清除,先缓存起来  
    64.     buff->localroute = sk->localroute;//路由类型  
    65.   
    66.     //buff->data 是指向数据部分的首地址(包括首部),这里是传输层,对应的数据部分为  
    67.     // TCP Hearder | data;buff->data则是指向其首地址  
    68.     t1 = (struct tcphdr *) buff->data;//tcp首部数据  
    69.     //buff->data中保存的是数据包的首部地址,在各个层对应不同的首部  
    70.       
    71.     /* 
    72.      *  Put in the IP header and routing stuff.  
    73.      */  
    74.      //查找合适的路由表项  
    75.     rt=ip_rt_route(sk->daddr, NULL, NULL);  
    76.       
    77.   
    78.     /* 
    79.      *  We need to build the routing stuff from the things saved in skb.  
    80.      */  
    81.     //这里是调用ip_build_header(ip.c),结合前面可以看出prot操作函数调用的一般都是下一层的函数  
    82.     //build mac header 然后 build ip header,该函数返回时,buff的data部分已经添加了ip 首部和以太网首部  
    83.     //返回这两个首部大小之和  
    84.     tmp = sk->prot->build_header(buff, sk->saddr, sk->daddr, &dev,  
    85.                     IPPROTO_TCP, NULL, MAX_SYN_SIZE,sk->ip_tos,sk->ip_ttl);  
    86.     if (tmp < 0)   
    87.     {  
    88.         sk->prot->wfree(sk, buff->mem_addr, buff->mem_len);  
    89.         release_sock(sk);  
    90.         return(-ENETUNREACH);  
    91.     }  
    92.     //connect 函数是向指定地址的网络端发送连接请求数据包,最终数据包要被对端的硬件设备接收  
    93.     //所以需要对端的ip地址 mac地址。  
    94.       
    95.     buff->len += tmp;//数据帧长度更新,即加上创建的这两个首部长度  
    96.     t1 = (struct tcphdr *)((char *)t1 +tmp);//得到tcp首部  
    97.     //t1指针结构中对应的内存布局为:mac首部+ip首部+tcp首部+数据部分  
    98.     //t1是该结构的首地址,然后偏移mac首部和ip首部大小位置,定位到tcp首部  
    99.   
    100.     memcpy(t1,(void *)&(sk->dummy_th), sizeof(*t1));//拷贝缓存的tcp首部  
    101.     t1->seq = ntohl(sk->write_seq++);//32位序列号,序列号字节序转换  
    102.     //下面为tcp保证可靠数据传输使用的序列号  
    103.     sk->sent_seq = sk->write_seq;//将要发送的数据包的第一个字节的序列号  
    104.     buff->h.seq = sk->write_seq;//该数据包的ack值,针对tcp协议而言  
    105.     //tcp首部控制字设置  
    106.     t1->ack = 0;  
    107.     t1->window = 2;//窗口大小  
    108.     t1->res1=0;//首部长度  
    109.     t1->res2=0;  
    110.     t1->rst = 0;  
    111.     t1->urg = 0;  
    112.     t1->psh = 0;  
    113.     t1->syn = 1;//同步控制位  
    114.     t1->urg_ptr = 0;  
    115.     t1->doff = 6;  
    116.     /* use 512 or whatever user asked for */  
    117.   
    118.     //窗口大小,最大传输单元设置  
    119.     if(rt!=NULL && (rt->rt_flags&RTF_WINDOW))  
    120.         sk->window_clamp=rt->rt_window;//窗口大小钳制值  
    121.     else  
    122.         sk->window_clamp=0;  
    123.   
    124.     if (sk->user_mss)  
    125.         sk->mtu = sk->user_mss;//mtu最大传输单元  
    126.     else if(rt!=NULL && (rt->rt_flags&RTF_MTU))  
    127.         sk->mtu = rt->rt_mss;  
    128.     else   
    129.     {  
    130. #ifdef CONFIG_INET_SNARL  
    131.         if ((sk->saddr ^ sk->daddr) & default_mask(sk->saddr))  
    132. #else  
    133.         if ((sk->saddr ^ sk->daddr) & dev->pa_mask)  
    134. #endif  
    135.             sk->mtu = 576 - HEADER_SIZE;  
    136.         else  
    137.             sk->mtu = MAX_WINDOW;  
    138.     }  
    139.     /* 
    140.      *  but not bigger than device MTU  
    141.      */  
    142.   
    143.     if(sk->mtu <32)  
    144.         sk->mtu = 32;    /* Sanity limit */  
    145.           
    146.     sk->mtu = min(sk->mtu, dev->mtu - HEADER_SIZE);//mtu取允许值  
    147.       
    148.     /* 
    149.      *  Put in the TCP options to say MTU.  
    150.      */  
    151.     //这里不是很清楚  
    152.     ptr = (unsigned char *)(t1+1);  
    153.     ptr[0] = 2;  
    154.     ptr[1] = 4;  
    155.     ptr[2] = (sk->mtu) >> 8;  
    156.     ptr[3] = (sk->mtu) & 0xff;  
    157.     //计算tcp校验和  
    158.     tcp_send_check(t1, sk->saddr, sk->daddr,sizeof(struct tcphdr) + 4, sk);  
    159.   
    160.     /* 
    161.      *  This must go first otherwise a really quick response will get reset.  
    162.      */  
    163.     //connect发起连接请求时,开始tcp的三次握手,这是第一个状态  
    164.     tcp_set_state(sk,TCP_SYN_SENT);//设置tcp状态  
    165.     sk->rto = TCP_TIMEOUT_INIT;//延迟时间值  
    166. #if 0 /* we already did this */  
    167.     init_timer(&sk->retransmit_timer);   
    168. #endif  
    169.     //重发定时器设置  
    170.     sk->retransmit_timer.function=&retransmit_timer;  
    171.     sk->retransmit_timer.data = (unsigned long)sk;  
    172.     reset_xmit_timer(sk, TIME_WRITE, sk->rto);   /* Timer for repeating the SYN until an answer */  
    173.     sk->retransmits = TCP_SYN_RETRIES;  
    174.       
    175.     //前面地址信息,标识字段,查询路由表项等事务都已经完成了,那么就是发送连接请求数据包的时候了  
    176.     //下面这个函数将转调用ip_queue_xmit 函数(ip层),这是个数据包发送函数  
    177.     sk->prot->queue_xmit(sk, dev, buff, 0);    
    178.     reset_xmit_timer(sk, TIME_WRITE, sk->rto);  
    179.     tcp_statistics.TcpActiveOpens++;  
    180.     tcp_statistics.TcpOutSegs++;  
    181.   
    182.     //那么下面就是一个数据包接收函数了,(可能有的名字已经占用了,就勉强用这个不相关的名字)  
    183.     //这个函数将内部调用 tcp_rcv 函数  
    184.     release_sock(sk);//重新接收数据包  
    185.     return(0);  
    186. }  

    上面函数最后调用了queue_xmit 函数(ip_queue_xmit 函数)和 release_sock 函数,进行数据包的发送和接收。

    另外,在inet_connect 函数中调用了 build_header 函数(ip层的 ip_build_header 函数),考虑到篇幅问题,我们将在下篇继续剖析。



  • 相关阅读:
    Python3---常见函数---super()
    Python3---常见函数---type()
    Python3---面对对象
    Python3---BeautifulSoup---节点选择器
    Python3---Beautiful Soup
    0X01应用程序黑客技术
    Python3---标准库---re
    (trie) UVA
    (trie)UVALive
    (平方分割)POJ 2104 K-th Number
  • 原文地址:https://www.cnblogs.com/ztguang/p/12645493.html
Copyright © 2011-2022 走看看