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發過來的連接請求.