tcp connection setup 의 실현 (3)
사실 accept 의 역할 은 간단 합 니 다. accept 대기 열 에서 악수 로 완 성 된 socket 을 세 번 꺼 내 vfs 에 연결 하 는 것 입 니 다. (사실 sys socket 을 호출 할 때 socket 을 새로 만 드 는 것 과 유사 합 니 다) 그리고 돌아 갑 니 다. 여기 서 주의해 야 할 것 이 있 습 니 다. accept 에 전 달 된 socket 이 차단 되 지 않 으 면 accept 대기 열 이 비어 도 바로 돌아 갑 니 다. 막 히 면 휴면 합 니 다.accept 대기 열 에 데이터 가 있 을 때 까지 기 다 렸 다가 깨 워 주세요.
다음은 그 실현 을 살 펴 보 겠 습 니 다. accept 에 대응 하 는 시스템 호출 은 sys 입 니 다.accept, 그 는 do 를 호출 합 니 다.accept, 그래서 우 리 는 직접 do 를 보 러 왔 다.accept:
long do_accept(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;
.............................................
/// , fd, socket.
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (!sock)
goto out;
err = -ENFILE;
/// socket, socket. socket, sock. .
if (!(newsock = sock_alloc()))
goto out_put;
newsock->type = sock->type;
newsock->ops = sock->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);
/// , file . socket .
newfd = sock_alloc_fd(&newfile, flags & O_CLOEXEC);
if (unlikely(newfd < 0)) {
err = newfd;
sock_release(newsock);
goto out_put;
}
/// socket file .( socket , , blog
err = sock_attach_fd(newsock, newfile, flags & O_NONBLOCK);
if (err < 0)
goto out_fd_simple;
err = security_socket_accept(sock, newsock);
if (err)
goto out_fd;
/// inet_accept
err = sock->ops->accept(sock, newsock, sock->file->f_flags);
if (err < 0)
goto out_fd;
/// accept . upeer_sockaddr.
if (upeer_sockaddr) {
if (newsock->ops->getname(newsock, (struct sockaddr *)&address,
&len, 2) < 0) {
err = -ECONNABORTED;
goto out_fd;
}
err = move_addr_to_user((struct sockaddr *)&address,
len, upeer_sockaddr, upeer_addrlen);
if (err < 0)
goto out_fd;
}
/* File flags are not inherited via accept() unlike another OSes. */
/// file fd , fd files .
fd_install(newfd, newfile);
err = newfd;
security_socket_post_accept(sock, newsock);
out_put:
/// .
fput_light(sock->file, fput_needed);
out:
/// .
return err;
.......................................
}
절차 가 간단 하고 최종 실현 은 inet 에 집중 되 어 있 음 을 알 수 있 습 니 다.accept 에 당 첨 되 었 습 니 다. inetaccept 가 주로 하 는 게...
1 inet 호출csk_accept 에서 accept 대기 열 을 조작 합 니 다. 받 은 sock 을 되 돌려 줍 니 다.
2. inet 에서csk_accept 가 되 돌아 온 sock 은 전 달 된 (즉 do accept 에서 new socket) 에 연결 되 어 있 습 니 다. 여기 서 우리 위 에 왜 sock 이 아 닌 new socket 만 필요 한 지 알 수 있 습 니 다. sock 은 accept 대기 열 에서 직접 얻 었 기 때 문 입 니 다.
3 새로운 socket 의 상 태 를 SS 로 설정CONNECTED.
int inet_accept(struct socket *sock, struct socket *newsock, int flags)
{
struct sock *sk1 = sock->sk;
int err = -EINVAL;
/// inet_csk_accept.
struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err);
if (!sk2)
goto do_err;
lock_sock(sk2);
/// tcp .
WARN_ON(!((1 << sk2->sk_state) &
(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT | TCPF_CLOSE)));
/// sock socket.
sock_graft(sk2, newsock);
/// .
newsock->state = SS_CONNECTED;
err = 0;
release_sock(sk2);
do_err:
return err;
}
inet_csk_accept 는 accept 대기 열 에서 sock 을 꺼 내 서 돌아 오 는 것 입 니 다.
그의 소스 코드 를 보기 전에 관련 함수 의 실현 을 먼저 살 펴 보 자.
우선 reqskqueue_empty, accept 대기 열 이 비어 있 는 지 판단 하 는 데 사 용 됩 니 다.
static inline int reqsk_queue_empty(struct request_sock_queue *queue)
{
return queue->rskq_accept_head == NULL;
}
그리고 reqskqueue_get_child, 그 는 주로 accept 대기 열 에서 sock 을 얻 었 습 니 다.
static inline struct sock *reqsk_queue_get_child(struct request_sock_queue *queue,
struct sock *parent)
{
/// accept remove socket .
struct request_sock *req = reqsk_queue_remove(queue);
/// socket.
struct sock *child = req->sk;
WARN_ON(child == NULL);
/// sk_ack_backlog , accept .
sk_acceptq_removed(parent);
__reqsk_free(req);
return child;
}
여기 또 하나의 inetcsk_wait_for_connect, accept 대기 열 이 비어 있 는 상태 에서 한동안 휴면 하 는 데 사 용 됩 니 다.
static int inet_csk_wait_for_connect(struct sock *sk, long timeo)
{
struct inet_connection_sock *icsk = inet_csk(sk);
/// waitqueue.
DEFINE_WAIT(wait);
int err;
..................................................
for (;;) {
/// sk sk_sleep ,sk socket.
prepare_to_wait_exclusive(sk->sk_sleep, &wait,
TASK_INTERRUPTIBLE);
release_sock(sk);
/// .
if (reqsk_queue_empty(&icsk->icsk_accept_queue))
/// timeo ( schedule cpu), accept ( ) .,
timeo = schedule_timeout(timeo);
lock_sock(sk);
err = 0;
/// .
if (!reqsk_queue_empty(&icsk->icsk_accept_queue))
break;
err = -EINVAL;
if (sk->sk_state != TCP_LISTEN)
break;
err = sock_intr_errno(timeo);
if (signal_pending(current))
break;
/// .
err = -EAGAIN;
/// 0 .
if (!timeo)
break;
}
/// sk_sleep remove wait .
finish_wait(sk->sk_sleep, &wait);
return err;
}
그리고 inet csk accept 의 원본 코드 를 살 펴 보 겠 습 니 다. 차단 되 지 않 으 면 바로 돌아 갑 니 다. accept 대기 열 이 비어 있어 도 errno 를 - EAGAIN 으로 설정 합 니 다.
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
{
struct inet_connection_sock *icsk = inet_csk(sk);
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;
///sk socket, TCP_LISTEN.
if (sk->sk_state != TCP_LISTEN)
goto out_err;
/// accept
if (reqsk_queue_empty(&icsk->icsk_accept_queue)) {
/// O_NONBLOCK, 0, inet_csk_wait_for_connect .
long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
/* If this is a non blocking socket don't sleep */
error = -EAGAIN;
if (!timeo)
goto out_err;
/// .
error = inet_csk_wait_for_connect(sk, timeo);
if (error)
goto out_err;
}
/// sock accept remove.
newsk = reqsk_queue_get_child(&icsk->icsk_accept_queue, sk);
WARN_ON(newsk->sk_state == TCP_SYN_RECV);
out:
release_sock(sk);
return newsk;
out_err:
newsk = NULL;
*err = error;
goto out;
}
마지막 으로 connect 의 실현 을 대충 분석 해 보 자. 그 구체 적 인 절 차 는:
1. fd 에서 socket 을 얻 고 주 소 를 커 널 공간 으로 복사 합 니 다.
2 inet stream connect 를 호출 하여 주요 처 리 를 합 니 다.
여기 서 connect 도 차단 과 비 차단 의 차이 가 있 습 니 다. 차단 하면 inet wait for connect 휴면 을 호출 하여 악수 가 완 료 될 때 까지 기 다 려 야 합 니 다. 그렇지 않 으 면 바로 돌아 갑 니 다.
asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr,
int addrlen)
{
struct socket *sock;
struct sockaddr_storage address;
int err, fput_needed;
/// socket.
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (!sock)
goto out;
/// .
err = move_addr_to_kernel(uservaddr, addrlen, (struct sockaddr *)&address);
if (err < 0)
goto out_put;
err =
security_socket_connect(sock, (struct sockaddr *)&address, addrlen);
if (err)
goto out_put;
/// .
err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,
sock->file->f_flags);
out_put:
fput_light(sock->file, fput_needed);
out:
return err;
}
그리고 inet stream connect 를 보 세 요. 그의 주요 업 무 는:
1 socket 의 상 태 를 판단 합 니 다. SS UNCONNECTED 즉 비 접속 상태 일 때 만 tcp v4 connect 를 호출 하여 연결 처 리 를 합 니 다.
2 tcp 의 상 태 를 판단 하 는 sk state 는 TCPF SYN SENT 또는 TCPF SYN RECV 만 해당 처리 에 들 어 갈 수 있 습 니 다.
3. 상태 가 적당 하고 socket 이 차단 모드 라면 inet wait for connect 를 사용 하여 휴면 에 들 어가 악 수 를 기다 리 고 있 습 니 다. 그렇지 않 으 면 바로 돌아 가 고 오류 번 호 를 EINPROGRESS 로 설정 합 니 다.
int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
int addr_len, int flags)
{
struct sock *sk = sock->sk;
int err;
long timeo;
lock_sock(sk);
............................................
switch (sock->state) {
default:
err = -EINVAL;
goto out;
case SS_CONNECTED:
err = -EISCONN;
goto out;
case SS_CONNECTING:
err = -EALREADY;
/* Fall out of switch with err, set for this state */
break;
case SS_UNCONNECTED:
err = -EISCONN;
if (sk->sk_state != TCP_CLOSE)
goto out;
/// tcp_v4_connect . syn.
err = sk->sk_prot->connect(sk, uaddr, addr_len);
if (err < 0)
goto out;
/// .
sock->state = SS_CONNECTING;
/// .
err = -EINPROGRESS;
break;
}
/// , 0, timeo.
timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
/// . .( inet_csk_wait_for_connect , )
if (!timeo || !inet_wait_for_connect(sk, timeo))
goto out;
err = sock_intr_errno(timeo);
if (signal_pending(current))
goto out;
}
/* Connection was closed by RST, timeout, ICMP error
* or another process disconnected us.
*/
if (sk->sk_state == TCP_CLOSE)
goto sock_error;
/// socket . .
sock->state = SS_CONNECTED;
err = 0;
out:
release_sock(sk);
return err;
sock_error:
err = sock_error(sk) ? : -ECONNABORTED;
sock->state = SS_UNCONNECTED;
if (sk->sk_prot->disconnect(sk, flags))
sock->state = SS_DISCONNECTING;
goto out;
}
tcp v4 connect 의 소스 코드 는 분석 하지 않 습 니 다. 저 는 그의 절 차 를 대충 소개 하 겠 습 니 다.
1. 주소 의 합 법성 을 판단 한다.
2. ip route connect 를 호출 하여 나 가 는 길 을 찾 습 니 다 (임시 포트 찾기 포함).
3. sock 상 태 를 TCP SYN SENT 로 설정 하고 inet hash connect 를 호출 하여 임시 포트 (즉, 우리 가 나 간 포트) 를 찾 고 해당 하 는 hash 링크 에 추가 합 니 다 (구체 적 인 동작 은 get port 와 비슷 합 니 다).
4. tcp connect 를 호출 하여 최종 작업 을 수행 합 니 다. 이 함 수 는 보 낼 syn 패 키 지 를 초기 화 하 는 데 사 용 됩 니 다. (창 크기 isn 등 포함) 그리고 이 sk buffer 를 socket 의 쓰기 대기 열 에 추가 합 니 다. 최종 적 으로 tcp transmit skb 를 3 층 으로 호출 합 니 다. 다음 작업 을 하면 제 앞의 blog 를 볼 수 있 습 니 다.
마지막 으로 악 수 를 세 번 하 는 클 라 이언 트 의 상태 변 화 를 살 펴 보 겠 습 니 다. tcp rcv state process 함 수 를 보 겠 습 니 다. 여기 서 우리 가 들 어 온 socket 가설 은 TCP SYN SENT 상태 입 니 다. 즉, syn 과 ack 분 절 을 기다 리 고 있 습 니 다.
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
struct tcphdr *th, unsigned len)
{
..........................................
switch (sk->sk_state) {
case TCP_CLOSE:
goto discard;
case TCP_LISTEN:
..................................
case TCP_SYN_SENT:
/// .
queued = tcp_rcv_synsent_state_process(sk, skb, th, len);
if (queued >= 0)
return queued;
/* Do step6 onward by hand. */
tcp_urg(sk, skb, th);
__kfree_skb(skb);
tcp_data_snd_check(sk);
return 0;
}
그리고 tcp rcv synsent state process 의 상태 변 화 를 살 펴 보 겠 습 니 다.
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
struct tcphdr *th, unsigned len)
{
..................
if (th->ack) {
....................................
/// rst , ,
if (th->rst) {
tcp_reset(sk);
goto discard;
}
/// ack syn , .
if (!th->syn)
goto discard_and_undo;
..................................................
/// TCP_ESTABLISHED, ack .
tcp_set_state(sk, TCP_ESTABLISHED);
.......................................
}
....................................................
if (th->syn) {
/// syn , TCP_SYN_RECV.
tcp_set_state(sk, TCP_SYN_RECV);
...................................
/// ack .
tcp_send_synack(sk);
goto discard;
#endif
}
...................
}
여기 서 syn 만 받 아들 이면 세 번 의 악수 가 완료 되 지 않 았 습 니 다. 마지막 ack 를 기다 리 고 있 습 니 다. 따라서 데이터 보고 가 있 으 면 tcp rcv state process 함수 에 다시 떨 어 집 니 다.
if (th->ack) {
/// ack .
int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH);
switch (sk->sk_state) {
case TCP_SYN_RECV:
if (acceptable) {
tp->copied_seq = tp->rcv_nxt;
smp_mb();
/// TCP_ESTABLISHED, .
tcp_set_state(sk, TCP_ESTABLISHED);
sk->sk_state_change(sk);
/// connect .
if (sk->sk_socket)
sk_wake_async(sk,
SOCK_WAKE_IO, POLL_OUT);
........................................
} else {
return 1;
}
break;
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
정수 반전Udemy 에서 공부 한 것을 중얼거린다 Chapter3【Integer Reversal】 (예) 문자열로 숫자를 반전 (toString, split, reverse, join) 인수의 수치 (n)가 0보다 위 또는 ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.