Linux Kernel 2.6.9源码分析 – accept
先来看一下原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
sockfd:这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,它只知道一个地址和一个端口号。
addr:这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。
len:如同大家所认为的,它也是结果的参数,用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。
下面来看看系统调用sys_accept
asmlinkage long sys_accept(int fd, struct sockaddr __user *upeer_sockaddr, int __user *upeer_addrlen)
{
struct socket *sock, *newsock;
int err, len;
char address[MAX_SOCK_ADDR];
sock = sockfd_lookup(fd, &err);
if (!sock)
goto out;
err = -EMFILE;
if (!(newsock = sock_alloc()))
goto out_put;
newsock->type = sock->type;
newsock->ops = sock->ops;
............................
err = sock->ops->accept(sock, newsock, sock->file->f_flags);
if (err < 0)
goto out_release;
..................
if ((err = sock_map_fd(newsock)) < 0)
goto out_release;
.............................
}
从上面的代码来看,
- 通过用户传入的fd找到之前通过sys_socket创建的struct socket结构
- 分配一个新的struct socket结构,然后将旧socket的type 和ops赋值给新socket
- sock->ops->accept挂上一个客户端请求对应的struct sock,后面来看详细的实现
- 通过sock_map_fd,分配struct file并挂上新socket,然后在挂载在当前进程描述符中.
- 通过参数upeer_sockaddr 返回client的地址信息给用户,以及通过返回值返回新socket对应的文件描述符给用户.
sock->ops->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;
lock_sock(sk2);
BUG_TRAP((1 << sk2->sk_state) &
(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT | TCPF_CLOSE));
sock_graft(sk2, newsock);
newsock->state = SS_CONNECTED;
err = 0;
release_sock(sk2);
do_err:
return err;
}
TCP类型的socket最终会调用到:
struct sock *tcp_accept(struct sock *sk, int flags, int *err)
{
struct tcp_opt *tp = tcp_sk(sk);
struct open_request *req;
struct sock *newsk;
int error;
lock_sock(sk);
/* 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;
/* Find already established connection */
if (!tp->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;
error = wait_for_connect(sk, timeo);
if (error)
goto out;
}
req = tp->accept_queue;
if ((tp->accept_queue = req->dl_next) == NULL)
tp->accept_queue_tail = NULL;
newsk = req->sk;
sk_acceptq_removed(sk);
tcp_openreq_fastfree(req);
BUG_TRAP(newsk->sk_state != TCP_SYN_RECV);
release_sock(sk);
return newsk;
out:
release_sock(sk);
*err = error;
return NULL;
}
1.如果tp->accept_queue队列为空,就会调用wait_for_connect将当前进程进入睡眠状态,当有连接到来时,当前进程会被唤醒.
2.如果tp->accept_queue队列不为空,取出tp->accept_queue队列头部元素即struct open_request,返回其中的struct sock结构
3. 将上步骤中的struct sock结构绑定到新分配的struct socket结构中.
至于tp->accept_queue队列中的元素是怎么来的,流程相当复杂,后续有时间再详细酌…
综合sys_create/sys_bind/sys_connec/sys_accept可总结出如下简要流程图.
可以看出server主套接字每当接收到一个client的连接请求就会new 一个struct sock到accept_queue中,当用户调用sys_accept的时候回new一个新的socket出来并挂街上队列头部的一个sock,然后再新创建一个struct file,将其fd返回给sys_accept的用户.而主socket继续接收其他client发过来的连接请求.
来源:CSDN
作者:年轻态程序猿
链接:https://blog.csdn.net/weixin_38537730/article/details/104114521