(本文部分参考《Linux源代码情景分析》)
操作SYS_ACCEPT接受连接请求,通过sys_accept()实现。
“有连接”模式的插口一旦通过 listen()设置成 server 插口以后,就只能被动地通过 accept()接受来自client 插口的连接请求。进程对 accept()的调用是阻塞性的,就是说如果没有连接请求就会进入睡眠等待,直到有连接请求到来,接受了请求以后(或者超过了预定的等待时间)才会返回。所以,在已经有连接请求的情况下是“接受连接请求”,而在尚无连接请求的情况下是“等待连接请求”。在内核中,accept()是通过 net/socket.c 中的函数 sys_accept()实现的。
函数整个调用过程如下:
下面是完整代码:
函数 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);/* 获得侦听端口的socket */
if (!sock)
goto out;
err = -ENFILE;
if (!(newsock = sock_alloc()))/* 分配一个新的套接口,用来处理与客户端的连接 */
goto out_put;
/* 根据侦听套接口来初始化新连接的类型和回调表 */
newsock->type = sock->type;
newsock->ops = sock->ops;
...
/* accept系统调用在套接口层的实现,对TCP来说,是inet_accept */
err = sock->ops->accept(sock, newsock, sock->file->f_flags);
if (err < 0)
goto out_release;
if (upeer_sockaddr) {/* 调用者需要获取对方套接口地址和端口 */
/*
*调用传输层回调获得对方的地址和端口
*这个函数既可以用来获取对方插口的地址,也可以用来获取本插口的地址,具体由参数 peer 决定。在这里由于调用时将参数的值设成 2,所以取的是对方地址。
*/
if(newsock->ops->getname(newsock, (struct sockaddr *)address, &len, 2)<0) {
err = -ECONNABORTED;
goto out_release;
}
/* 成功后复制到用户态 */
err = move_addr_to_user(address, len, upeer_sockaddr, upeer_addrlen);
if (err < 0)
goto out_release;
}
if ((err = sock_map_fd(newsock)) < 0)/* 为新连接分配文件描述符并返回*/
goto out_release;
...
}
代码中先通过 sockfd_lookup()找到 server 插口的 socket 结构,然后通过 sock_alloc()分配一个新的socket 结构,再通过相应函数指针调用具体的回调函数inet_accept()。
值得注意的是:一个插口经 listen()设置成 server 插口以后永远不会与任何插口建立起连接。因为一旦接受了一个连接请求之后就会创建出另一个插口,连接就建立在这个新的插口与请求连接的那个插口之间,而原先的 server 插口则并无改变,并且还可再次通过 accept()接受下一个连接请求,server 插口永远保持接受新的连接请求的能力,但是其本身从来不成为某个连接的一端。正因为这样, sys_accept()返回的是新插口的打开文件号,同时会把对方(cli)的插口地址从参数中带出来。
inet_accept()代码如下:
/* accept系统调用在套接口层的实现 */
int inet_accept(struct socket *sock, struct socket *newsock, int flags)
{
struct sock *sk1 = sock->sk;/* 根据套口获得传输层控制块 */
int err = -EINVAL;
/* 调用传输层的函数tcp_accept获得已经完成的连接的传输控制块,即子传输控制块 */
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_accept()函数代码如下:
/* accept调用的传输层实现 */
struct sock *tcp_accept(struct sock *sk, int flags, int *err)
{
struct tcp_sock *tp = tcp_sk(sk);
struct open_request *req;
struct sock *newsk;
int error;
lock_sock(sk);
error = -EINVAL;
if (sk->sk_state != TCP_LISTEN)/* 本调用仅仅针对侦听套口,不是此状态的套口则退出 */
goto out;
if (!tp->accept_queue) {/* accept队列为空,说明还没有收到新连接 */
long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);/* 如果套口是非阻塞的,或者在一定时间内没有新连接,则返回 */
/*标志位 O_NONBLOCK在文件操作中常用,表示如果有报文就接收,但是若没有也得马上返回,而不是睡眠等待,这个标志位也可用于 accept()。*/
error = -EAGAIN;
if (!timeo)/* 超时时间到,没有新连接,退出 */
goto out;
/* 运行到这里,说明有新连接到来(返回值为0),则等待新的传输控制块 */
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;
}
wait_for_connect()函数代码如下;
static int wait_for_connect(struct sock *sk, long timeo)
{
struct tcp_sock *tp = tcp_sk(sk);
DEFINE_WAIT(wait);
int err;
for (;;) {
prepare_to_wait_exclusive(sk->sk_sleep, &wait,
TASK_INTERRUPTIBLE);
release_sock(sk);
if (!tp->accept_queue)
timeo = schedule_timeout(timeo);
lock_sock(sk);
err = 0;
if (tp->accept_queue)
break;
err = -EINVAL;
if (sk->sk_state != TCP_LISTEN)
break;
err = sock_intr_errno(timeo);
if (signal_pending(current))
break;
err = -EAGAIN;
if (!timeo)
break;
}
finish_wait(sk->sk_sleep, &wait);
return err;
}
void fastcall
prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
unsigned long flags;
/*参数 WQ_FLAG_EXCLUSIVE 表示如果有多个进程在睡眠等待就只唤醒其中一个*/
wait->flags |= WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
if (list_empty(&wait->task_list))
__add_wait_queue_tail(q, wait);
if (is_sync_wait(wait))
set_current_state(state);
spin_unlock_irqrestore(&q->lock, flags);
}
当中一些重要的数据结构的创建过程如下:
注:
1、
server 插口必须有个地址,这样 client 一方才能通过地址来找到 server 插口的数据结构,所以在创建了插口以后一定要调用 bind()将其“捆绑”到一个地址上。可是,对于 client 一方的插口来说,它只能主动地去寻找某个 server 插口,别的插口是不会来寻找它的。所以实际上没有必要让 client 插口也有个地址,当然有也无妨。
2、
在 sys_accept()中已经调用了 sock_alloc(),分配了一个新的 socket 结构(也就是inode 结构)。但 sock_alloc(),它并不分配与 socket 结构配对的 sock结构,从这个意义上讲,这个新的插口还不完整。那么什么时候才分配所需的 sock 数据结构呢?这是由client 一方在 connect()的过程中分配,并且将其指针通过用作连接请求报文的 sk_buff 结构带过来的,通过 sock_graft()将 client 一方提供的 sock结构 与 server 一方提供的 newsock(socket结构) 挂上钩。
3、
简单介绍一下sock_map_fd()函数(将套接口与文件描述符绑定):