函数 sys_accept() 接受连接请求

本文深入探讨了socket在“有连接”模式下的工作原理,特别是serversocket如何通过accept()函数接受客户端连接请求的过程。揭示了serversocket如何保持接受新连接的能力,同时详细分析了内核中的accept()函数实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

“有连接”模式的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 队列中接收代表着请求的控制报文。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值