“有连接”模式的socket一旦通过listen()设置成server socket 以后,就只能被动地通过accept() 接受来自client socket 连接请求。进程对accept() 的调用是阻塞性的。就是说如果没有连接请求就会进入睡眠等待,直到有连接请求到来,接受了请求以后(或者超过了预定的待时间)才会返回。所以,在已经有连接请求的情况下是“接受连接请求”, 而在尚无连接请求的情况下是“等待连接请求”。 在内核中, accept()是通过 net/socket.c中的函数sys_accept() 实现的,其代码在net/socket.c中:
/*
* For accept, we attempt to create a new socket, set up the link
* with the client, wake up the client, then return the new
* connected fd. We collect the address of the connector in kernel
* space and move it to user at the very end. This is unclean because
* we open the socket then return an error.
*
* 1003.1g adds the ability to recvmsg() to query connection pending
* status to recvmsg. We need to add that support in a way thats
* clean when we restucture accept also.
*/
asmlinkage long sys_accept(int fd, struct sockaddr *upeer_sockaddr, int *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 (upeer_sockaddr) {
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;
}
/* File flags are not inherited via accept() unlike another OSes. */
if ((err = sock_map_fd(newsock)) < 0)
goto out_release;
out_put:
sockfd_put(sock);
out:
return err;
out_release:
sock_release(newsock);
goto out_put;
}
说来也许奇怪,实际上一个socket 经listen() 设置成server socket以后永远不会与任何socket 建立起连接。因为一旦接受了一个连接请求之后,就会创建出另一个socket, 连接就建立在这个新的socket 与请求连接的那个socket之间,而原先的server socket则并无改变,并且还可再次通过accept() 接受下一个连接请求,就好像母鸡下蛋一样。就这样“只取蛋,不杀鸡”, server socket 永远保持接受新的连接请求的能力,但是其本身从来不成为某个连接的一端,正因为这样,sys_accept() 返回的是新socket 的打开文件号,同时还通过作为参数的指针upper_sockaddr返回对方的socket 地址。
代码中先通过sockfd_lookup() 找到server socket 的socket结构,然后通过sock_alloc() 分配一个新的socket结构。为“下蛋"作好准备;再通过相应proto_ops数据结构(对于Unix 域是unix_stream_ops 或者unix_dgram_ops)中的函数指针accept 调用具体的函数。"有连接" socket 的accept指针则指向unix_accept(). 此函数在文件net/unix/af_unix.c 中:
static int unix_accept(struct socket *sock, struct socket *newsock, int flags)
{
unix_socket *sk = sock->sk;
unix_socket *tsk;
struct sk_buff *skb;
int err;
err = -EOPNOTSUPP;
if (sock->type!=SOCK_STREAM)
goto out;
err = -EINVAL;
if (sk->state!=TCP_LISTEN)
goto out;
/* If socket state is TCP_LISTEN it cannot change (for now...),
* so that no locks are necessary.
*/
skb = skb_recv_datagram(sk, 0, flags&O_NONBLOCK, &err);
if (!skb)
goto out;
tsk = skb->sk;
skb_free_datagram(sk, skb);
wake_up_interruptible(&sk->protinfo.af_unix.peer_wait);
/* attach accepted sock to socket */
unix_state_wlock(tsk);
newsock->state = SS_CONNECTED;
sock_graft(tsk, newsock);
unix_state_wunlock(tsk);
return 0;
out:
return err;
}
以前我们在讲到socket创建时曾经提到,在sock结构中有几个重要的队列,其中之一是到达报文的队列receive_queue. 到达的报文(更确切的说是packet)"载运"在sk_buff数据结构中。而receive_queue等待队列就是sk_buff结构的队列。连接请求也是以报文的形式到达的,不过不是一般的数据报文,而属于控制报文。所以,所谓等待请求的到来就是等待这种控制报文的到来。另一方面,虽然这里所说的socket都是"有连接"模式的,但那只是对数据(报文)而言,而控制报文实际上都是"无连接"的,否则,靠什么手段来建立最初的连接呢?) 所以,这里通过skb_recv_datagram() 从receive_queue 队列中接收代表着请求的控制报文。