#TCP你学得会# 之 accpet的谜题
2016-03-04 00:00
585 查看
摘要: 在TCP/IP网络编程中使用accept函数接收一个新的连接算是常识了,但其中还隐藏了哪些小秘密,一起来看下吧。
假设我们现在有这样一个场景,在一个多线程程序中,主线程创建监听套接字并阻塞在accept函数上等待新连接到来,另外一个线程则分别在如下两个时间点open两个外部文件:
1) socket创建成功后,调用accept之前;
2) 调用accept之后(但没有任何外部连接);
按照预期两次open函数返回的fd应该是多少呢?
0,1,2是默认保留的,创建的socket fd为3, accept又没有新的连接可以接收,那么我们两次open操作的返回值当然应该是4和5咯。
这是事实吗?
实际的结果是这样滴:
也就是说,accept之后的open操作返回的不是5,而是6。
有没有很诧异,描述符5去哪里了?
要解答这个问题还是得去到内核代码中的syscall accept中一探究竟。幸运的是,一打开这个函数就能看到线索了。
从源码中可以看到,一进入accept函数就会分配新的socket结构和文件描述符了,也就是说,在实际的连接到来之前相关的资源已经被预留出来了,因此上述实验结果中第二个open返回了6而不是5,因为5已经作为预留分配出去了。
那么当我们在非阻塞的socket上使用accept函数也会是这个结果吗?
先从源码入手分析,accept系统调用中会进一步调用sock->ops->accept,与sock->ops->accept对应的函数是inet_stream_ops.accept = inet_accept:
与sk1->sk_prot->accept对应的是tcp_prot.accept = inet_csk_accept:
看起来在非阻塞的socket上调用accept且当前没有新的连接可返回时,会在系统调用返回前将申请到的资源释放掉,这时前后两次open的返回值应该就是4和5了。
希望下次遇到这个问题时你能很快的想起其中的前因后果,而不再困惑哦。
假设我们现在有这样一个场景,在一个多线程程序中,主线程创建监听套接字并阻塞在accept函数上等待新连接到来,另外一个线程则分别在如下两个时间点open两个外部文件:
1) socket创建成功后,调用accept之前;
2) 调用accept之后(但没有任何外部连接);
按照预期两次open函数返回的fd应该是多少呢?
0,1,2是默认保留的,创建的socket fd为3, accept又没有新的连接可以接收,那么我们两次open操作的返回值当然应该是4和5咯。
这是事实吗?
实际的结果是这样滴:
$ ls -l /proc/3216/fd total 0 lrwx------ 1 yyy yyy 64 Mar 3 10:05 0 -> /dev/pts/3 lrwx------ 1 yyy yyy 64 Mar 3 10:05 1 -> /dev/pts/3 lrwx------ 1 yyy yyy 64 Mar 3 10:05 2 -> /dev/pts/3 lrwx------ 1 yyy yyy 64 Mar 3 10:05 3 -> socket:[17906] lr-x------ 1 yyy yyy 64 Mar 3 10:05 4 -> /home/yyy/project/tcpudp/tcp/tcp_client.c lr-x------ 1 yyy yyy 64 Mar 3 10:05 6 -> /home/yyy/project/tcpudp/tcp/tcp_server.c
也就是说,accept之后的open操作返回的不是5,而是6。
有没有很诧异,描述符5去哪里了?
要解答这个问题还是得去到内核代码中的syscall accept中一探究竟。幸运的是,一打开这个函数就能看到线索了。
SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr, int __user *, upeer_addrlen, int, flags) { ... sock = sockfd_lookup_light(fd, &err, &fput_needed); if (!sock) goto out; err = -ENFILE; newsock = sock_alloc(); if (!newsock) goto out_put; newsock->type = sock->type; newsock->ops = sock->ops; /* * We don't need try_module_get here, as the listening socket (sock) * has the protocol module (sock->ops->owner) held. */ __module_get(newsock->ops->owner); newfd = get_unused_fd_flags(flags); if (unlikely(newfd < 0)) { err = newfd; sock_release(newsock); goto out_put; } newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name); if (unlikely(IS_ERR(newfile))) { err = PTR_ERR(newfile); put_unused_fd(newfd); sock_release(newsock); goto out_put; } ... ... err = sock->ops->accept(sock, newsock, sock->file->f_flags); if (err < 0) goto out_fd; ... ... out_put: fput_light(sock->file, fput_needed); out: return err; out_fd: fput(newfile); put_unused_fd(newfd); goto out_put; }
从源码中可以看到,一进入accept函数就会分配新的socket结构和文件描述符了,也就是说,在实际的连接到来之前相关的资源已经被预留出来了,因此上述实验结果中第二个open返回了6而不是5,因为5已经作为预留分配出去了。
那么当我们在非阻塞的socket上使用accept函数也会是这个结果吗?
先从源码入手分析,accept系统调用中会进一步调用sock->ops->accept,与sock->ops->accept对应的函数是inet_stream_ops.accept = inet_accept:
int inet_accept(struct socket *sock, struct socket *newsock, int flags) { struct sock *sk1 = sock->sk; int err = -EINVAL; struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err); if (!sk2) goto do_err; ... do_err: return err; }
与sk1->sk_prot->accept对应的是tcp_prot.accept = inet_csk_accept:
/* * This will accept the next outstanding connection. */ struct sock *inet_csk_accept(struct sock *sk, int flags, int *err) { ... /* We need to make sure that this socket is listening, * and that it has something pending. */ error = -EINVAL; if (sk->sk_state != TCP_LISTEN) goto out_err; /* Find already established connection */ if (reqsk_queue_empty(&icsk->icsk_accept_queue)) { long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); /* If this is a non blocking socket don't sleep */ error = -EAGAIN; if (!timeo) goto out_err; error = inet_csk_wait_for_connect(sk, timeo); if (error) goto out_err; } newsk = reqsk_queue_get_child(&icsk->icsk_accept_queue, sk); WARN_ON(newsk->sk_state == TCP_SYN_RECV); out: release_sock(sk); return newsk; out_err: newsk = NULL; *err = error; goto out; } EXPORT_SYMBOL(inet_csk_accept); static inline long sock_rcvtimeo(const struct sock *sk, bool noblock) { return noblock ? 0 : sk->sk_rcvtimeo; }
看起来在非阻塞的socket上调用accept且当前没有新的连接可返回时,会在系统调用返回前将申请到的资源释放掉,这时前后两次open的返回值应该就是4和5了。
$ ls -l /proc/5764/fd total 0 lrwx------ 1 yyy yyy 64 Mar 3 10:55 0 -> /dev/pts/3 lrwx------ 1 yyy yyy 64 Mar 3 10:55 1 -> /dev/pts/3 lrwx------ 1 yyy yyy 64 Mar 3 10:55 2 -> /dev/pts/3 lrwx------ 1 yyy yyy 64 Mar 3 10:55 3 -> socket:[20249] lr-x------ 1 yyy yyy 64 Mar 3 10:55 4 -> /home/yyy/project/tcpudp/tcp/tcp_client.c lr-x------ 1 yyy yyy 64 Mar 3 10:55 5 -> /home/yyy/project/tcpudp/tcp/tcp_server.c
希望下次遇到这个问题时你能很快的想起其中的前因后果,而不再困惑哦。
相关文章推荐
- Linux socket 初步
- Linux Kernel 4.0 RC5 发布!
- linux lsof详解
- linux 文件权限
- Linux 执行数学运算
- 10 篇对初学者和专家都有用的 Linux 命令教程
- Linux 与 Windows 对UNICODE 的处理方式
- Ubuntu12.04下QQ完美走起啊!走起啊!有木有啊!
- 解決Linux下Android开发真机调试设备不被识别问题
- 运维入门
- 运维提升
- Linux 自检和 SystemTap
- Ubuntu Linux使用体验
- c语言实现hashmap(转载)
- Linux 信号signal处理机制
- linux下mysql添加用户
- Scientific Linux 5.5 图形安装教程
- Linux 下无损图片压缩小工具介绍