在完成三次握手后,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如何保证数据的顺序性和完整性的呢?请看下一章!