>  기사  >  php教程  >  소켓에 대한 자세한 설명

소켓에 대한 자세한 설명

高洛峰
高洛峰원래의
2016-12-13 10:17:381133검색

우리는 정보 교환의 가치를 잘 알고 있는데 네트워크의 프로세스는 어떻게 통신합니까? 예를 들어 매일 웹을 탐색하기 위해 브라우저를 열 때 브라우저 프로세스는 웹 서버와 어떻게 통신합니까? QQ를 사용하여 채팅할 때 QQ 프로세스는 서버나 친구가 있는 QQ 프로세스와 어떻게 통신합니까? 이 모든 것이 소켓에 의존합니까? 그럼 소켓이란 무엇인가? 소켓의 종류는 무엇입니까? 이 글에서 소개하고자 하는 소켓의 기본 기능도 있습니다. 이 글의 주요 내용은 다음과 같습니다.

1. 네트워크에서 프로세스 간 통신은 어떻게 하나요?

2.소켓이란?

3. 소켓의 기본 동작

3.1.socket() 함수

3.2.bind() 함수

3.3. ) 함수

3.4, accept() 함수

3.5, read(), write() 함수 등

3.6, close() 함수

4 , 소켓에서 연결을 설정하기 위한 TCP의 3방향 핸드셰이크에 대한 자세한 설명

5. 소켓에서 연결을 해제하기 위한 TCP의 4방향 핸드셰이크에 대한 자세한 설명

6. it)

7. 질문을 남겨주시면 모두 답변해 드립니다! ! !

1. 네트워크 내 프로세스간 통신은 어떻게 하나요?

로컬 IPC(프로세스 간 통신) 방법은 여러 가지가 있지만 다음 4가지 범주로 요약할 수 있습니다.

메시지 전달(파이프라인, FIFO, 메시지 큐)

동기화(뮤텍스, 조건 변수, 읽기-쓰기 잠금, 파일 및 쓰기 레코드 잠금, 세마포)

공유 메모리(익명 및 이름 지정)

원격 프로시저 호출(Solaris 게이트 및 Sun RPC)

그러나 이것은 이 글의 주제가 아닙니다! 우리가 논의할 내용은 네트워크의 프로세스 간 통신 방법에 관한 것입니다. 해결해야 할 첫 번째 문제는 프로세스를 고유하게 식별하는 방법입니다. 그렇지 않으면 통신이 불가능합니다! 프로세스는 프로세스 PID를 통해 로컬에서 고유하게 식별될 수 있지만 네트워크에서는 작동하지 않습니다. 실제로 TCP/IP 프로토콜 제품군은 이 문제를 해결하는 데 도움이 되었습니다. 네트워크 계층의 "ip 주소"는 네트워크의 호스트를 고유하게 식별할 수 있는 반면 전송 계층의 "프로토콜 + 포트"는 애플리케이션을 고유하게 식별할 수 있습니다. (프로세스) 호스트에서. 이러한 방식으로 삼중항(IP 주소, 프로토콜, 포트)을 사용하여 네트워크 프로세스를 식별할 수 있으며, 네트워크의 프로세스 통신은 이 표시를 사용하여 다른 프로세스와 상호 작용할 수 있습니다.

TCP/IP 프로토콜을 사용하는 애플리케이션은 일반적으로 네트워크 프로세스 간의 통신을 달성하기 위해 UNIX BSD의 소켓과 UNIX System V의 TLI(이미 사용되지 않음)와 같은 애플리케이션 프로그래밍 인터페이스를 사용합니다. 현재는 거의 모든 응용 프로그램이 소켓을 사용하고 있으며 이제는 네트워크상의 프로세스 통신이 어디에나 존재합니다. 이것이 제가 "모든 것이 소켓입니다"라고 말하는 이유입니다.

2.소켓이란?

우리는 이미 네트워크의 프로세스가 소켓을 통해 통신한다는 것을 알고 있는데, 소켓이란 무엇일까요? 소켓은 Unix에서 유래되었으며 Unix/Linux의 기본 철학 중 하나는 "모든 것이 파일이다"이며 "열기 -> 읽기 및 쓰기 쓰기/읽기 -> 닫기" 모드에서 작동할 수 있다는 것입니다. 제가 이해한 바에 따르면 소켓은 이 모드의 구현입니다. 소켓은 특수 파일이며 일부 소켓 기능은 이에 대한 작업(읽기/쓰기 IO, 열기, 닫기)입니다.

소켓이라는 단어의 유래

네트워킹 분야에서 최초로 사용된 것은 Stephen Carr, Steve Crocker, Vint Cerf가 작성한 1970년 2월 12일 발표된 IETF RFC33 문서에서 발견되었습니다. . 컴퓨터 역사 박물관에 따르면 Croker는 다음과 같이 썼습니다. "네임스페이스의 요소는 소켓 인터페이스라고 불릴 수 있습니다. 소켓 인터페이스는 연결의 한쪽 끝을 형성하며 연결은 소켓 인터페이스 쌍에 의해 완전히 지정될 수 있습니다. "컴퓨터 역사 박물관 추가: "BSD의 소켓 인터페이스 정의보다 약 12년 전입니다."

3. 소켓의 기본 동작

소켓은 "open-write./read-close" 모드이므로 소켓은 이러한 작업에 해당하는 기능적 인터페이스를 제공합니다. 다음은 TCP를 예로 들어 몇 가지 기본 소켓 인터페이스 기능을 소개합니다.

3.1.socket() 함수

int 소켓(int domain, int type, int 프로토콜);

소켓 함수는 일반 소켓의 열기 작업에 해당합니다. 파일. 일반적인 파일 열기 작업은 파일 설명자를 반환하고, 소켓()을 사용하여 소켓을 고유하게 식별하는 소켓 설명자(소켓 설명자)를 생성합니다. 이 소켓 설명자는 파일 설명자와 동일하며 일부 읽기 및 쓰기 작업을 수행하기 위한 매개변수로 사용됩니다.

다른 매개변수 값을 fopen에 전달하여 다른 파일을 열 수 있는 것과 같습니다. 소켓을 생성할 때 다양한 매개변수를 지정하여 다양한 소켓 설명자를 생성할 수도 있습니다. 소켓 함수의 세 가지 매개변수는 다음과 같습니다.

domain: 프로토콜 계열이라고도 합니다. 일반적으로 사용되는 프로토콜 제품군에는 AF_INET, AF_INET6, AF_LOCAL(또는 AF_UNIX, Unix 도메인 소켓), AF_ROUTE 등이 포함됩니다. 프로토콜 패밀리는 소켓의 주소 유형을 결정하며 해당 주소를 통신에 사용해야 합니다. 예를 들어 AF_INET은 ipv4 주소(32비트)와 포트 번호(16비트)의 조합을 사용하도록 결정하고 AF_UNIX는 결정합니다. 절대 경로를 주소로 사용합니다.

유형: 소켓 유형을 지정합니다. 일반적으로 사용되는 소켓 유형에는 SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_PACKET, SOCK_SEQPACKET 등이 있습니다. (소켓 유형은 무엇입니까?)

프로토콜: 따라서 이름은 프로토콜을 지정한다는 의미입니다. 일반적으로 사용되는 프로토콜에는 IPPROTO_TCP, IPPTOTO_UDP, IPPROTO_SCTP, IPPROTO_TIPC 등이 있으며 각각 TCP 전송 프로토콜, UDP 전송 프로토콜, STCP 전송 프로토콜 및 TIPC 전송 프로토콜에 해당합니다(이 프로토콜에 대해서는 별도로 설명하겠습니다!).

참고: 위의 유형과 프로토콜은 임의로 결합할 수 없습니다. 예를 들어 SOCK_STREAM은 IPPROTO_UDP와 결합할 수 없습니다. 프로토콜이 0이면 유형 유형에 해당하는 기본 프로토콜이 자동으로 선택됩니다.

소켓을 생성하기 위해 소켓을 호출할 때 반환된 소켓 설명자는 프로토콜 패밀리(주소 패밀리, AF_XXX) 공간에 존재하지만 특정 주소를 갖지 않습니다. 주소를 할당하려면 반드시 바인딩() 함수를 호출해야 합니다. 그렇지 않으면 시스템은 connect() 또는 Listen()을 호출할 때 자동으로 포트를 무작위로 할당합니다.

3.2.bind() 함수

위에서 언급한 것처럼 바인딩() 함수는 주소 계열의 특정 주소를 소켓에 할당합니다. 예를 들어, AF_INET 및 AF_INET6에 대응하여 ipv4 또는 ipv6 주소와 포트 번호 조합이 소켓에 할당됩니다.

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

함수의 세 가지 매개변수는 다음과 같습니다.

sockfd: 즉, 소켓() 함수를 통해 생성되고 소켓을 고유하게 식별하는 소켓 설명 Word입니다. 바인딩() 함수는 이 설명자에 이름을 바인딩합니다.

addr: sockfd에 바인딩될 프로토콜 주소를 가리키는 const struct sockaddr * 포인터. 이 주소 구조는 주소가 소켓을 생성할 때 주소 프로토콜 계열에 따라 다릅니다. 예를 들어 ipv4는

struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */};/* Internet address. */struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */};
ipv6对应的是: 
struct sockaddr_in6 { 
    sa_family_t     sin6_family;   /* AF_INET6 */ 
    in_port_t       sin6_port;     /* port number */ 
    uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
    struct in6_addr sin6_addr;     /* IPv6 address */ 
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ };struct in6_addr { 
    unsigned char   s6_addr[16];   /* IPv6 address */ };
Unix域对应的是: 
#define UNIX_PATH_MAX    108struct sockaddr_un { 
    sa_family_t sun_family;               /* AF_UNIX */ 
    char        sun_path[UNIX_PATH_MAX];  /* pathname */ };
addrlen:对应的是地址的长度。

에 해당합니다. 일반적으로 서버는 잘 알려진 주소(예: IP 주소 + 포트)를 바인딩합니다. 번호)는 서비스 제공을 위해 사용되며, 고객은 이를 통해 서버에 접속할 수 있으며, 클라이언트는 이를 지정할 필요 없이 시스템이 자동으로 포트 번호와 자체 IP 주소 조합을 할당합니다. 이것이 서버가 일반적으로 수신 대기 전에 바인딩()을 호출하지만 클라이언트는 이를 호출하지 않는 대신 시스템이 connect() 중에 임의로 생성하는 이유입니다.

네트워크 바이트 순서 및 호스트 바이트 순서

호스트 바이트 순서는 우리가 일반적으로 빅 엔디안 및 리틀 엔디안 모드라고 부르는 것입니다. CPU마다 바이트 순서 유형이 다르며, 이 단어는 섹션 순서를 나타냅니다. 정수가 메모리에 저장되는 것을 호스트 순서라고 합니다. Big-Endian과 Little-Endian의 표준 정의는 다음과 같습니다.

a) Little-Endian은 하위 바이트가 메모리의 하위 주소 끝에 배열되고 상위 바이트가 배열된다는 의미입니다. 바이트는 메모리의 상위 주소 끝에 배열됩니다.

b) Big-Endian은 상위 바이트가 메모리의 하위 주소 끝에 배열되고, 하위 바이트가 메모리의 상위 주소 끝에 배열되는 것을 의미합니다.

네트워크 바이트 순서: 4바이트 32비트 값은 먼저 0~7비트, 그 다음 8~15비트, 16~23비트, 마지막으로 24~31비트의 순서로 전송됩니다. 이 전송 순서를 빅엔디안이라고 합니다. TCP/IP 헤더의 모든 이진 정수는 네트워크를 통해 전송될 때 이 순서로 되어 있어야 하므로 이를 네트워크 바이트 순서라고도 합니다. 바이트 순서는 이름에서 알 수 있듯이 1바이트 유형보다 큰 데이터가 메모리에 저장되는 순서입니다. 1바이트 데이터에는 순서 문제가 없습니다.

따라서: 주소를 소켓에 바인딩할 때 먼저 호스트 바이트 순서를 네트워크 바이트 순서로 변환하고 호스트 바이트 순서가 네트워크 바이트 순서와 동일한 Big을 사용한다고 가정하지 마십시오. -엔디안. 이 문제로 인해 살인 사건이 발생했습니다! 이 문제로 인해 회사의 프로젝트 코드에 설명할 수 없는 많은 문제가 발생하였으므로 호스트 바이트 순서에 대해 어떤 가정도 하지 마시고, 소켓에 할당하기 전에 반드시 네트워크 바이트 순서로 변환하시기 바랍니다.

3.3, listening(), connect() 함수

서버인 경우, 소켓()과 바인드()를 호출한 후, 클라이언트가 connect()를 호출하여 연결 요청을 하면 서버는 요청을 받습니다. .

int listening(int sockfd, int backlog);int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

listen 함수의 첫 번째 매개변수는 두 번째 매개변수는 해당 소켓에 대해 대기할 수 있는 최대 연결 수입니다. 소켓() 함수에 의해 생성된 소켓은 기본적으로 능동형이며, Listen 함수는 소켓을 수동형으로 변경하여 클라이언트의 연결 요청을 기다린다.

connect 함수의 첫 번째 매개변수는 클라이언트의 소켓 설명자이고, 두 번째 매개변수는 서버의 소켓 주소, 세 번째 매개변수는 소켓 주소의 길이입니다. 클라이언트는 connect 함수를 호출하여 TCP 서버와 연결을 설정합니다.

3.4.accept() 함수

TCP 서버는 소켓(), 바인드(), 청취()를 차례로 호출한 후 지정된 소켓 주소를 수신합니다. TCP 클라이언트는 소켓()과 연결()을 차례로 호출한 후 TCP 서버에 연결 요청을 보냅니다. TCP 서버는 이 요청을 모니터링한 후 accept() 함수를 호출하여 요청을 수신하고 연결이 설정됩니다. 그런 다음 일반 파일 읽기 및 쓰기 I/O 작업과 유사한 네트워크 I/O 작업을 시작할 수 있습니다.

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept 함수의 첫 번째 매개변수는 서버의 소켓 설명자이고 두 번째 매개변수는 포인터입니다. 클라이언트의 프로토콜 주소를 반환하는 데 사용되는 sockaddr *를 구성합니다. 세 번째 매개변수는 프로토콜 주소의 길이입니다. accpet이 성공하면 반환 값은 반환 클라이언트에 대한 TCP 연결을 나타내는 커널에 의해 자동으로 생성된 새로운 설명자입니다.

참고: accept의 첫 번째 매개 변수는 서버의 소켓 설명자입니다. 이는 서버가 수신 소켓 설명자라고 하는 소켓() 함수 호출을 시작할 때 생성되며, accept 함수는 연결된 소켓 설명을 반환합니다. . 성격. 서버는 일반적으로 서버의 수명 주기 동안 존재하는 청취 소켓 설명자만 생성합니다. 커널은 서버 프로세스가 수락한 각 클라이언트 연결에 대해 연결된 소켓 설명자를 생성합니다. 서버가 클라이언트 서비스를 완료하면 해당 연결된 소켓 설명자가 닫힙니다.

3.5, read(), write() 및 기타 함수

모든 것이 준비되었으며 서버와 클라이언트 간의 연결이 설정되었습니다. 읽기 및 쓰기 작업을 위해 네트워크 I/O를 호출할 수 있으며, 이는 네트워크의 서로 다른 프로세스 간의 통신이 실현된다는 것을 의미합니다! 네트워크 I/O 작업에는 다음 그룹이 있습니다.

read()/write()

recv()/send()

readv()/writev()

recvmsg()/sendmsg()

recvfrom()/sendto()

recvmsg()/sendmsg() 함수를 사용하는 것이 좋습니다. 이 두 함수가 가장 좋습니다. 다양한 I/O 기능을 사용하면 실제로 위의 다른 기능을 이 두 기능으로 대체할 수 있습니다. 해당 선언은 다음과 같습니다:

#include

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

            #include
            #include

        ssize_t send(int sockfd, const void *buf, size_t len, int 플래그);
ssize_t recv(int sockfd, void *buf, size_t len, int 플래그);

ssize_t sendto(int sockfd, const void *buf, size_t len , struct sockadd r *src_addr, socklen_t *addrlen);

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int 플래그);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int 플래그 );

read 함수는 읽기에 성공하면 읽은 실제 바이트 수를 반환합니다. 반환된 값이 0이면 내용이 끝났음을 의미합니다. 파일을 읽었다는 의미입니다. 0보다 작으면 오류가 발생한 것입니다. 오류가 EINTR이면 인터럽트로 인해 읽기가 발생했음을 의미합니다. ECONNREST이면 네트워크 연결에 문제가 있음을 의미합니다.

write 함수는 buf의 nbytes 바이트 내용을 파일 설명자 fd에 씁니다. 성공 시 작성된 바이트 수를 반환합니다. 실패하면 -1이 반환되고 errno 변수가 설정됩니다. 네트워크 프로그램에서 소켓 파일 설명자에 쓸 때 두 가지 가능성이 있습니다. 1) 쓰기의 반환 값이 0보다 크면 데이터의 일부 또는 전체가 기록되었음을 나타냅니다. 2) 반환된 값이 0보다 작아서 오류가 발생했습니다. 오류 유형에 따라 처리해야 합니다. 오류가 EINTR이면 쓰기 중에 인터럽트 오류가 발생했음을 의미합니다. EPIPE인 경우 네트워크 연결에 문제가 있음을 의미합니다(상대방이 연결을 끊었습니다).

이러한 I/O 기능 쌍을 하나씩 소개하지는 않겠습니다. 자세한 내용은 맨 문서를 참조하거나 다음 예에서는 Send/recv를 사용합니다.

3.6, close() 함수

在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。

#include int close(int fd);

close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。

注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

4、socket中TCP的三次握手建立连接详解

我们知道tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:

客户端向服务器发送一个SYN J

服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1

客户端再想服务器发一个确认ACK K+1

只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:

소켓에 대한 자세한 설명

图1、소켓에 대한 자세한 설명

从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。

总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。

5、socket中TCP的四次握手释放连接详解

上面介绍了socket中TCP的三次握手建立过程,及其涉及的socket函数。现在我们介绍socket中的四次握手释放连接的过程,请看下图:

소켓에 대한 자세한 설명

图2、소켓에 대한 자세한 설명

图示过程如下:

某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;

另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;

一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;

接收到这个FIN的源发送端TCP对它进行确认。

这样每个方向上都有一个FIN和ACK。

6、一个例子(实践一下)

说了这么多了,动手实践一下。下面编写一个简单的服务器、客户端(使用TCP)——服务器端一直监听本机的6666号端口,如果收到连接请求,将接收请求并接收客户端发来的消息;客户端与服务器端建立连接并发送一条消息。

服务器端代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>

#define MAXLINE 4096

int main(int argc, char** argv)
{
    int    listenfd, connfd;
    struct sockaddr_in     servaddr;
    char    buff[4096];
    int     n;

    if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
    printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(6666);

    if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
    printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }

    if( listen(listenfd, 10) == -1){
    printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }

    printf("======waiting for client&#39;s request======\n");
    while(1){
    if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){
        printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
        continue;
    }
    n = recv(connfd, buff, MAXLINE, 0);
    buff[n] = &#39;\0&#39;;
    printf("recv msg from client: %s\n", buff);
    close(connfd);
    }

    close(listenfd);
}

当然上面的代码很简单,也有很多缺点,这就只是简单的演示socket的基本函数使用。其实不管有多复杂的网络程序,都使用的这些基本函数。上面的服务器使用的是迭代模式的,即只有处理完一个客户端请求才会去处理下一个客户端的请求,这样的服务器处理能力是很弱的,现实中的服务器都需要有并发处理能力!为了需要并发处理,服务器需要fork()一个新的进程或者线程去处理请求等。

7、动动手

留下一个问题,欢迎大家回帖回答!!!是否熟悉Linux下网络编程?如熟悉,编写如下程序完成如下功能:

服务器端:

接收地址192.168.100.2的客户端信息,如信息为“Client Query”,则打印“Receive Query”

客户端:

"Client Query test", "Cleint Query", "Client Query Quit" 정보를 192.168.100.168 주소의 서버로 순차적으로 보내고 종료합니다.

질문에 나오는 IP 주소는 실제 상황에 따라 판단될 수 있습니다.


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
이전 기사:소켓 통신 소개다음 기사:소켓 통신 소개