3.7 Accept系统调用

  在完成三次握手后,server端TCP会创建一个sock结构来与client端的scoket进行一对一的数据传递。但这个sock进存在于内核中,server端用户进程还无法使用。进程要想使用这个新的连接,必须调用accept系统调用生成一个与sock关联的socket,然后进程通过对这个socket进行recv、send等操作来实现与client端的数据传递。连接建立完成后,服务器端的应用进程会从listening socket上得到可读事件通告,这时应用进程就可以调用accept系统调用。Accept系统调用的原型:

 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  其中,sockfd是listening socket的描述符,addr是客户端主机的地址,addrlen是addr指向的结构体的大小。
     对应的内核函数为:

1640 SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,
1641         int __user *, upeer_addrlen)
1642 {
1643     return sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);
1644 }
  sys_accept4函数:

1559 SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,
1560         int __user *, upeer_addrlen, int, flags)
1561 {
1562     struct socket *sock, *newsock;
1563     struct file *newfile;
1564     int err, len, newfd, fput_needed;
1565     struct sockaddr_storage address;
1566
1567     if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
1568         return -EINVAL;
1569
1570     if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
1571         flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
1572
1573     sock = sockfd_lookup_light(fd, &err, &fput_needed);//找到listening socket
1574     if (!sock)
1575         goto out;
1576
1577     err = -ENFILE;
1578     newsock = sock_alloc();//申请一个子socket结构体
1579     if (!newsock)
1580         goto out_put;
1581
1582     newsock->type = sock->type;
1583     newsock->ops = sock->ops;
1584
1585     /*
1586      * We don't need try_module_get here, as the listening socket (sock)
1587      * has the protocol module (sock->ops->owner) held.
1588      */
1589     __module_get(newsock->ops->owner);
1590
1591     newfd = get_unused_fd_flags(flags);  //生成文件描述符
1592     if (unlikely(newfd < 0)) {
1593         err = newfd;
1594         sock_release(newsock);
1595         goto out_put;
1596     }
1597     newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);//生成一个与socket相关联的file结构
...
1608
1609     err = sock->ops->accept(sock, newsock, sock->file->f_flags);//调用inet_accept关联socket与sock
1610     if (err < 0)
1611         goto out_fd;
1612
1613     if (upeer_sockaddr) {
1614         if (newsock->ops->getname(newsock, (struct sockaddr *)&address,
1615                       &len, 2) < 0) {//调用inet_getname或inet6_getname获取对端地址信息
1616             err = -ECONNABORTED;
1617             goto out_fd;
1618         }
1619         err = move_addr_to_user(&address,
1620                     len, upeer_sockaddr, upeer_addrlen);//将地址信息传递给用户态空间
1621         if (err < 0)
1622             goto out_fd;
1623     }
1624
1625     /* File flags are not inherited via accept() unlike another OSes. */
1626
1627     fd_install(newfd, newfile);  //关联文件描述符与file
1628     err = newfd;
1629
1630 out_put:
1631     fput_light(sock->file, fput_needed);
1632 out:
1633     return err;
1634 out_fd:
1635     fput(newfile);
1636     put_unused_fd(newfd);
1637     goto out_put;
1638 }
  可见,accept系统调用的主要功能为:

1、申请一个socket,将其与listen socket中的一个sock关联

2、申请一个file结构,将其与socket关联

3、分配一个文件描述符,将其与file结构关联,并作为accept系统调用的返回值返回

4、将sock中的地址信息copy到accept系统调用中指定的空间

  与socket关联的sock是哪里的呢?来看inet_accept函数:

702 int inet_accept(struct socket *sock, struct socket *newsock, int flags)
 703 {   
 704     struct sock *sk1 = sock->sk;
 705     int err = -EINVAL;
 706     struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err);//对应inet_csk_accept函数
 707     
 708     if (!sk2)
...
 718     sock_graft(sk2, newsock);  //关联sk2与newsock
 719 
 720     newsock->state = SS_CONNECTED; 
...
  inet_csk_accept函数:

304 struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
305 {
306     struct inet_connection_sock *icsk = inet_csk(sk);
307     struct request_sock_queue *queue = &icsk->icsk_accept_queue;
308     struct sock *newsk;
309     struct request_sock *req;
310     int error;
311
312     lock_sock(sk);
313
314     /* We need to make sure that this socket is listening,
315      * and that it has something pending.
316      */
317     error = -EINVAL;
318     if (sk->sk_state != TCP_LISTEN)
319         goto out_err;
320
321     /* Find already established connection */
322     if (reqsk_queue_empty(queue)) {//没有sock等待被accept
323         long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
324
325         /* If this is a non blocking socket don't sleep */
326         error = -EAGAIN;
327         if (!timeo)  
328             goto out_err;
329
330         error = inet_csk_wait_for_connect(sk, timeo);//等待连接到来
331         if (error)   
332             goto out_err;
333     }
334     req = reqsk_queue_remove(queue);  //从accept queue队列头中摘出第一个request_sock
335     newsk = req->sk;   //得到request_sock中的sock指针
336
337     sk_acceptq_removed(sk);  //队列成员数减一
338     if (sk->sk_protocol == IPPROTO_TCP && queue->fastopenq != NULL) {
339         spin_lock_bh(&queue->fastopenq->lock);
340         if (tcp_rsk(req)->listener) {//启用了TFO功能并且等待客户端发送ACK以完成连接建立
341             /* We are still waiting for the final ACK from 3WHS
342              * so can't free req now. Instead, we set req->sk to
343              * NULL to signify that the child socket is taken
344              * so reqsk_fastopen_remove() will free the req
345              * when 3WHS finishes (or is aborted).
346              */      
347             req->sk = NULL;
348             req = NULL;
349         }
350         spin_unlock_bh(&queue->fastopenq->lock);
351     }
352 out:
353     release_sock(sk);
354     if (req)
355         __reqsk_free(req);
356     return newsk;
357 out_err:
358     newsk = NULL;
359     req = NULL;
360     *err = error;
361     goto out;
362 }

  由前几节的内容可知,每当一个三次握手完成后,都会有一个携带sock结构的request_sock被加入到listen socket的accept queue中。再接合这段代码,我们明白了与socket相关联的sock是来自listen socket的accept queue中的第一个request_sock。如果在使用accpet系统调用时accept queue为空,且listen socket不是非阻塞的,则进程会睡眠,直到有三次握手完成才能被唤醒。

        为什么要“先申请socket,后关联accept queue”呢?应该是出于性能考虑:如果accept queue不为空,则先申请socket还是先检查accept queue在性能没有什么差异;但如果accept queue为空,而且socket需要等待连接到来,这种情况下先申请socket就有优势了:连接到来之后可以直接使用申请好的socket结构体,这 样减小了应用进程响应连接的延迟。

  完成accept系统调用后,server端就可以使用一个新的socket与client端进行数据交互了。client端和server端都有可能首先发送数据。数据发送的流程是怎样的呢?TCP如何保证数据的顺序性和完整性的呢?请看下一章!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值