TCP 프로 토 콜 과 소스 코드 를 깊이 이해 합 니 다.

15238 단어
이 숙제 를 하기 전에 socket 의 소스 코드 를 계속 봤 어 요.
솔직히 너무 어려워 요. 많은 데이터 구조 가 잘 모 르 고 코드 작성 도 흐릿 하 게 보 여서 부화뇌동 합 니 다.
더 중요 한 것 은 며칠 을 봐 도 accpet 함수 와 세 번 악 수 를 하 는 syn, ack 값 등 절차 처리 가 무슨 상관 이 있 는 지 모 르 겠 어 요...
내 가 보 는 방법 이 틀 렸 을 수도 있 고, 내 수준 이 부족 할 수도 있 지만, 어쨌든 뭔 가 를 써 야 하기 때문에 socket 에서 accpet 4 () 함수 에 대한 소스 코드 이 해 를 썼 다.
이 숙제 도 후발 주자 들 에 게 지뢰 를 제거 하여 그들 로 하여 금 우여곡절 을 덜 없 앨 수 있 게 하 는 셈 이다.
어떤 곳 은 제 이 (cai) 해 (xiang) 를 넣 었 습 니 다. 잘못 이 있 으 면 꼭 알려 주세요. 감사합니다.
 
int __sys_accept4(int fd, struct sockaddr __user *upeer_sockaddr, int __user *upeer_addrlen, int flags)

이 함수 가 accept 4 라 고 부 르 는 이 유 는 여기에 네 개의 매개 변 수 를 작성 할 수 있 고 소스 코드 에 accpet 가 있 기 때문에 앞의 세 개의 매개 변수 만 쓸 수 있 고 네 번 째 매개 변 수 는 flags 가 직접 주어진 다.
        fd: 파일 식별 자 를 말 합 니 다. 여기 서 는 이번 링크 에 대응 하 는 파일 을 말 합 니 다.우 리 는 Linux 에서 모든 것 이 파일 이라는 것 을 알 고 있 습 니 다. socket 과 같은 네트워크 연결 도 파일 을 통 해 '가상' 으로 이 루어 집 니 다.
  *upeer_sockaddr 구조 체 정 보 를 저장 하 는 첫 번 째 주소 * upeeraddrlen 주소 정 보 를 저장 하 는 구조 체 크기 flags 는 표 지 를 설정 하 는 데 사 용 됩 니 다. 예 를 들 어 차단 이나 비 차단 모드 를 설정 합 니 다.
 
{
struct socket *sock, *newsock;        /*     */ struct file *newfile;           
int err, len, newfd, fput_needed;    

err 는 일반적으로 오류 표지 입 니 다. Linux 에서 내부 시스템 호출 이 성공 하면 0 으로 돌아 가 고 실 패 는 - 1 로 돌아 갑 니 다. err 값 에 따라 프로그램 이 정상적으로 작 동 하 는 지 알 수 있 고 err 값 을 통 해 이상 을 처리 할 수 있 습 니 다. 프로그램 이 무 너 지 는 것 을 방지 합 니 다. new fd 는 새 파일 식별 자 입 니 다.
struct sockaddr_storage address;

다음은 struct sockaddrstorage 의 정의:
struct sockaddr_storage
 {
     sa_family_t ss_family;        /*     */
     __ss_aligntype __ss_align;    /*     ,         sockaddr_storage  SOCKADDR */
char __ss_padding[_SS_PADSIZE]; }; /* _SS_PADSIZE =_SS_SIZE - (2 * sizeof (__ss_aligntype) */

 
if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
return -EINVAL;

이곳 의 논 리 는 좀 복잡 하 다. 한참 을 생각해 보 니 flags 가 SOCK 가 아니라면CLOEXEC 또는 SOCKNONBLOCK 중 하 나 는 EINVAL 로 돌아 갑 니 다.
 
SOCK_CLOEXEC 에 서 는 직접적인 자 료 를 찾 을 수 없 지만, 원본 주석 은 close - on - exec 를 언급 했 습 니 다. 자 료 를 찾 아 보면 이 플래그 위 치 는 Linux 가 파일 작업 에 대한 표지 이 며, fork () 또는 계승 한 같은 프로 세 스 가 닫 혔 을 때 다른 같은 프로 세 스 를 닫 는 용도 로 사 용 됩 니 다.socket 에서 이 식별 자 를 쓰 지 않 으 면 하나의 프로 세 스 가 ip 과 포트 를 차지 한 후에 계승 프로 세 스 도 ip 를 차지 할 수 있 습 니 다. 부모 프로 세 스 가 닫 혔 을 때 하위 프로 세 스 가 닫 히 지 않 아 새로운 부모 프로 세 스 가 네트워크 통신 을 할 수 없습니다.
SOCK_NONBLOCK 은 비 차단 식 식별 자 입 니 다.차단 과 비 차단 식 의 주요 차이 점 은 비 차단 식 이 read () 이후 데 이 터 를 받 지 못 하면 바로 0 으로 돌아 가 고 차단 식 은 한 동안 기 다 렸 다가 0 으로 돌아 가 는 것 이다. 즉, 비 차단 식 은 비동기 적 인 연결 이 고 차단 식 은 동기 적 인 연결 (이전 단계 가 되 지 않 으 면 다음 단 계 는 진행 할 수 없다) 이다.
 
EINVAL 은 errno. h 에 정 의 된 매크로 정의 로 성형 변 수 를 정의 합 니 다. 오류 코드 의 수치 이 고 값 은 22 이 며 잘못된 매개 변 수 를 표시 합 니 다.
즉, accept 4 에 있어 서 직면 하 는 연결 은 비 차단 식 이다.
 
if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

 
O_NONBLOCK 은 비 차단 모드 를 말 하 며 데 이 터 를 읽 지 못 할 때 반환 값 은 - 1 입 니 다.
 
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (!sock) 
goto out;

sockfd_lookup_light 함 수 는 fd 파일 식별 자 에 따라 socket 을 찾 아 대응 하 는 것 입 니 다. 이 함 수 는 accpet 뿐만 아니 라 bid, connect 등 다른 관건 적 인 함수 에서 도 흔히 볼 수 있 습 니 다.
만약 sock 이 할당 에 성공 하지 않 았 다 면, 직접 goto 를 출구 로 보 냅 니 다. 여 기 는 Linux 의 이상 처리 입 니 다.
여기 fd 를 통 해 감청 중인 socket 을 찾 습 니 다.전체 tcp 프로 토 콜, 이 fd 는 서로 다른 함 수 를 연결 하 는 데 사 용 됩 니 다.
 
err = -ENFILE;

ENFILE 는 이전 EINVAL 과 마찬가지 로 오류 코드 의 추출 값 이 고 값 은 23 이 며 파일 시트 가 넘 치 는 것 을 의미 합 니 다.
 
newsock = sock_alloc();
if (!newsock)
goto out_put;

sock_alloc () 함 수 는 파일 과 연 결 된 socket 변 수 를 분배 하 는 데 사 용 됩 니 다. 여 기 는 뉴스 록 에 변 수 를 분배 하여 연결 을 처리 하 는 데 사 용 됩 니 다.실패 하면 goto 는 출구 로 나 가 또 하나의 이상 처 리 를 한다.Linux 소스 코드 에 이상 처리 가 많 습 니 다. 상징적 코드 는 goto 구문 입 니 다.이후 의 이상 처 리 는 더 이상 군말 하지 않 고 독자 가 알 면 된다.
 
 
newsock->type = sock->type;
newsock->ops = sock->ops;

여 기 는 할당 과정 이 니 말 하지 않 겠 습 니 다.
 
__module_get(newsock->ops->owner);

module_get () 함수, 파일 참조 수 를 증가 하고 운영 체제 내용 과 관련 되 어 많이 전개 되 지 않 습 니 다.
 
newfd = get_unused_fd_flags(flags);
if (unlikely(newfd < 0)) {
err = newfd;
sock_release(newsock);
goto out_put;
}
newfile = 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;
}
get_unused_fd_flags, , open 。
sock_alloc_file, 파일 설명 자 를 만 듭 니 다.
이것 은 주로 새 파일 을 만 드 는 것 입 니 다.
연결 요청 을 감청 한 후 이 연결 에 새 파일 을 할당 해 야 합 니 다.
err = security_socket_accept(sock, newsock);
if (err)
goto out_fd;

security_socket_accept: security 폴 더 에서 대응 하 는 함 수 를 찾 을 수 있 습 니 다. 모두 hook () 라 는 함 수 를 통 해 한 단계 더 호출 할 수 있 습 니 다.왜 리 눅 스 가 이렇게 쓸데없는 짓 을 합 니까?자 료 를 계속 찾 아 보 세 요. hook 은 Linux Secrity Module 의 방문 판단 함수 입 니 다. 안전성 을 고려 하여 관건 적 인 시스템 호출 을 하기 전에 먼저 hook 함 수 를 통 해 이 호출 에 대해 안전성 검 사 를 하고 호출 을 허용 하면 0 으로 돌아 갑 니 다.
이것 은 리 눅 스 와 관련 된 안전 체제 이다. 쉽게 말 하면 hook 은 미리 남 겨 진 안전 인터페이스 로 사용 자 는 스스로 안전 체 제 를 실현 한 다음 에 hook () 를 통 해 관건 적 인 시스템 호출 을 방문 할 수 있다.
 
err = sock->ops->accept(sock, newsock, sock->file->f_flags, false);
if (err < 0)
goto out_fd;

accpet 함수 호출 inetstream_ops.inet_accept, inetstream_ops.inet_accept 마지막 tcp 호출prot.inet_csk_accept 는 최종 적 으로 accept 기능 을 실현 합 니 다.
tcp_prot.inet_csk_accept 소스 코드 와 설명 은 다음 과 같 습 니 다.
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct request_sock_queue *queue = &icsk->icsk_accept_queue;
    struct sock *newsk;
    struct request_sock *req;
    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 */
    //    accept socket    ,         
    if (reqsk_queue_empty(queue)) {
        ////    SO_RCVTIMEO    
        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;
        //   accept     
        error = inet_csk_wait_for_connect(sk, timeo);
        if (error)
            goto out_err;
    }
    //    accept        request_sock  
    req = reqsk_queue_remove(queue);
    // request_sock sock  ,           
    //       req       
    //req->sk                sock
    newsk = req->sk;
    //sk_ack_backlog   1
    sk_acceptq_removed(sk);
    //
    if (sk->sk_protocol == IPPROTO_TCP && queue->fastopenq != NULL) {
        spin_lock_bh(&queue->fastopenq->lock);
        if (tcp_rsk(req)->listener) {
            req->sk = NULL;
            req = NULL;
        }
        spin_unlock_bh(&queue->fastopenq->lock);
    }
out:
    release_sock(sk);//listen sock
    //    newsk  ,           
    if (req)
        __reqsk_free(req);
    return newsk;
out_err:
    newsk = NULL;
    req = NULL;
    *err = error;
    goto out;
}

 
간단하게 말하자면 accpet () 함 수 는 socket 대기 열 을 먼저 검사 합 니 다. 대기 열 이 요청 을 기다 리 지 않 으 면 flags 가 막 히 는 지 비 막 히 는 지 봅 니 다.
막 히 지 않 으 면 바로 돌아 갑 니 다. 막 히 면 시간 초과 표 시 를 설정 합 니 다. timeo 시간 이 되면 돌아 갑 니 다.
socket 요청 대기 열 에 요청 이 있 으 면 socket 대기 열 에서 첫 번 째 sock 요청 처 리 를 꺼 냅 니 다.
 
accept 4 () 함수 원본 계속 보기:
if (upeer_sockaddr) {
len = newsock->ops->getname(newsock,
(struct sockaddr *)&address, 2);
if (len < 0) {
err = -ECONNABORTED;
goto out_fd;
}
err = move_addr_to_user(&address,
len, upeer_sockaddr, upeer_addrlen);
if (err < 0)
goto out_fd;
}

getname () 함 수 는 새 주소 의 연결 을 얻 는 데 사 용 됩 니 다. moveaddr_to_user () 함 수 는 주 소 를 커 널 로 옮 깁 니 다.
 
fd_install(newfd, newfile);

새로 만 든 파일 식별 자 와 파일 을 연결 하면 accept 주요 기능 이 끝 납 니 다.
 
out_put:
fput_light(sock->file, fput_needed);

출구 1, 파일 구조 방출
out:
return err;

출구 2, 오류 값 을 직접 되 돌려 줍 니 다.
out_fd:
fput(newfile);
put_unused_fd(newfd);
goto out_put;
}

출구 3. 새 파일 을 풀 고 출구 1 로 이동 합 니 다.
 
링크 ux 소스 읽 기 소감:
1. 함수 의 규범 적 인 명명 과 주석 은 매우 중요 하 다. 그렇지 않 으 면 읽 기 가 매우 어렵다.2. C 언어 로 다 형 을 실현 하 는 방법 을 배 웠 고 개인 적 으로 이런 방식 이 쉽게 실현 되 고 오 류 를 잘 조사 할 수 있다 고 생각 합 니 다.(이런 방법 은 먼저 가장 복잡 한 함 수 를 실현 한 다음 에 다 중 함수 return 이라는 함 수 를 실현 하고 일정한 매개 변 수 를 추가 하 는 것 을 말한다) 3. 이상 한 처리 에 있어 소스 코드 의 절반 에 가 까 운 편폭 이 이상 한 처리 에 사용 되 는데 이것 은 우리 가 평소에 코드 에서 매우 참고 할 만 한 부분 이다. 대형 프로젝트 에서 이런 쓰기 방법 은 오 류 를 신속하게 찾 을 수 있다.프로그램 을 붕괴 시 키 지 않 는 다.

좋은 웹페이지 즐겨찾기