网络篇之accept
网络篇之socket
网络篇之bind
网络篇之listen
tcp服务器,在调用listen将状态设置为监听以后,调用accept接收客户端的连接。下面是accept的代码:
SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,int __user *, upeer_addrlen, int, flags)
{struct socket *sock, *newsock;struct file *newfile;int err, len, newfd, fput_needed;struct sockaddr_storage address; // 连接方的地址信息if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))return -EINVAL;if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;// 根据fd查找socketsock = sockfd_lookup_light(fd, &err, &fput_needed);if (!sock)goto out;err = -ENFILE;newsock = sock_alloc();if (!newsock)goto out_put;newsock->type = sock->type;newsock->ops = sock->ops; // 指向 inet_stream_ops/** We don't need try_module_get here, as the listening socket (sock)* has the protocol module (sock->ops->owner) held.*/__module_get(newsock->ops->owner);// 新连接的句柄newfd = get_unused_fd_flags(flags);if (unlikely(newfd < 0)) {err = newfd;sock_release(newsock);goto out_put;}// 申请struct filenewfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);if (IS_ERR(newfile)) {err = PTR_ERR(newfile);put_unused_fd(newfd);goto out_put;}err = security_socket_accept(sock, newsock);if (err)goto out_fd;// 接收新的连接,新的连接保存在newsock中err = sock->ops->accept(sock, newsock, sock->file->f_flags, false); // inet_acceptif (err < 0)goto out_fd;if (upeer_sockaddr) {// 获取连接对端的IP和端口信息,保存到address中if (newsock->ops->getname(newsock, (struct sockaddr *)&address, // inet_getname&len, 2) < 0) {err = -ECONNABORTED;goto out_fd;}// 将数据拷贝到用户空间err = move_addr_to_user(&address,len, upeer_sockaddr, upeer_addrlen);if (err < 0)goto out_fd;}/* File flags are not inherited via accept() unlike another OSes. */// 保存句柄fd_install(newfd, newfile);err = newfd;out_put:fput_light(sock->file, fput_needed);
out:return err;
out_fd:fput(newfile);put_unused_fd(newfd);goto out_put;
}
sockfd_lookup_light根据服务端的句柄fd,查找对应的struct socket;
sock_alloc申请客户端所需的struct socket,;
get_unused_fd_flags 为客户端申请一个未使用的句柄;
sock_alloc_file 申请一个struct file对象;
调用sock->ops->accept 真正执行接收客户端连接的操作,其指向的方法是:inet_accept。
int inet_accept(struct socket *sock, struct socket *newsock, int flags,bool kern)
{struct sock *sk1 = sock->sk;int err = -EINVAL;struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err, kern); // inet_csk_acceptif (!sk2)goto do_err;lock_sock(sk2);sock_rps_record_flow(sk2);WARN_ON(!((1 << sk2->sk_state) &(TCPF_ESTABLISHED | TCPF_SYN_RECV |TCPF_CLOSE_WAIT | TCPF_CLOSE)));sock_graft(sk2, newsock); // 将sk2与newsock进行关联newsock->state = SS_CONNECTED; // 修改为已连接状态err = 0;release_sock(sk2);
do_err:return err;
}
调用sk1->sk_prot->accept(实际为inet_csk_accept),接收客户端的连接。如果没有客户端连接,则返回错误,这种情况,一般是设置了接收超时时间,在超时时间内没有客户端连接;
sock_graft 将struct socket与struct sock进行关联;
newsock->state = SS_CONNECTED,将客户端的连接状态改为已连接;
下面继续看下inet_csk_accept的代码:
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err, bool kern)
{struct inet_connection_sock *icsk = inet_csk(sk);struct request_sock_queue *queue = &icsk->icsk_accept_queue;struct request_sock *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_err;/* Find already established connection */if (reqsk_queue_empty(queue)) {long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); // noblock ? 0 : sk->sk_rcvtimeo;/* If this is a non blocking socket don't sleep */error = -EAGAIN;if (!timeo)goto out_err;// 有新的连接,返回0error = inet_csk_wait_for_connect(sk, timeo);if (error)goto out_err;}// 获取一个连接请求req = reqsk_queue_remove(queue, sk);newsk = req->sk;if (sk->sk_protocol == IPPROTO_TCP &&tcp_rsk(req)->tfo_listener) {spin_lock_bh(&queue->fastopenq.lock);// request_sock和tcp_request_sock可以相互转化// 包含关系,从外向内// tcp_request_sock => inet_request_sock => request_sockif (tcp_rsk(req)->tfo_listener) {/* We are still waiting for the final ACK from 3WHS* so can't free req now. Instead, we set req->sk to* NULL to signify that the child socket is taken* so reqsk_fastopen_remove() will free the req* when 3WHS finishes (or is aborted).*/req->sk = NULL;req = NULL;}spin_unlock_bh(&queue->fastopenq.lock);}mem_cgroup_sk_alloc(newsk);
out:release_sock(sk);if (req)reqsk_put(req);return newsk;
out_err:newsk = NULL;req = NULL;*err = error;goto out;
}
icsk_accept_queue链表,保存了等待连接的客户端,列表为空,表示没有待连接的客户端;列表非空,则有客户端等待连接。
reqsk_queue_empty判断列表是否为空,在列表为空的情况下,调用inet_csk_wait_for_connect进行进程调度,让出cpu,等待客户端的连接。如果设置了接收超时,超时时间已到,或等待失败的情况下,inet_csk_accept返回失败。
有新的客户端连接时,调用reqsk_queue_remove从icsk_accept_queue列表中获取一个连接请求(对应的是struct request_sock),获取到对应的struct sock(request_sock.sk)结构,并返回。
struct request_sock {
struct sock_common __req_common;
#define rsk_refcnt __req_common.skc_refcnt
#define rsk_hash __req_common.skc_hash
#define rsk_listener __req_common.skc_listener
#define rsk_window_clamp __req_common.skc_window_clamp
#define rsk_rcv_wnd __req_common.skc_rcv_wnd
struct request_sock *dl_next;
u16 mss;
u8 num_retrans; /* number of retransmits */
u8 cookie_ts:1; /* syncookie: encode tcpopts in timestamp */
u8 num_timeout:7; /* number of timeouts */
u32 ts_recent;
struct timer_list rsk_timer;
const struct request_sock_ops *rsk_ops;
struct sock *sk;
u32 *saved_syn;
u32 secid;
u32 peer_secid;
};
最后回到文章开始的accept系统调用中,在有新的客户端连接的情况下,upeer_sockaddr非空,表示用户空间需要获取客户端的ip和端口等信息。调用inet_getname获取此信息。
最后将客户端的newfile,保存到current->files->fdt的newfd位置上。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
