C - Socket
< 함수 >
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocal); // domain : 사용할 프로토콜 패밀리
type : 프로토콜에서 사용할 소켓 유형
protocal : IPPROTO_TCP, IPPROTO_UDP, 0 등의 프로토콜
TCP/IP 전송 계층의 통신에서 소켓이 전화기 역할을 한다. 크게 연결형 서비스를 제공하는 TCP용 소켓과 비연결형 서비스를 제공하는 UDP용 소켓 두 유형이 있다.
응용 프로그램에서 해당 소켓을 생성할 때는 socket 함수를 사용한다.
SOCKET 함수는 이들 인자로 지정한 유형의 소켓을 성공적으로 생성하면 소켓 기술자를 반환한다. 이것은 소켓의 포인터를 갖는 기술자 테이블의 색인이다.
소켓 통신에서는 소켓 기술자를 이용한다. 만일 소켓을 성공적으로 생성하지 못하면 socket 함수는 -1을 반환한다.
그리고 socket함수는 인자를 설정해서 다양한 프로토콜을 수용할 수 있다.
인자 | 값 | 비고 |
domain | PF_INET | IPv4의 인터넷 프로토콜 패밀리 |
PF_INET6 | IPv6의 인터넷 프로토콜 패밀리 | |
PF_LOCAL | 로컬 유닉스 소켓 프로그램 패밀리 | |
PF_UNIX | 로컬 유닉스 소켓 프로그램 패밀리 | |
type | SOCK_STREAM | 스트림 소켓 |
SOCK_DGRAM | 데이터그램 소켓 | |
SOCK_RAW | Raw 소켓 | |
SOCK_SQPACKET | 순차 패킷 소켓 | |
protocal | IPPROTO_TCP | 스트림 소켓 |
IPPROTO_UDP | 데이터그램 소켓 |
아래의 표는 TCP와 UDP 소켓을 생성할 때 인자 domain, type, protocal에 유효한 조합을 정리한 것이다.
인자 설정 | 생성 소켓 |
socket(PF_INET, SOCK_STREAM, 0); | TCP 소켓 생성 |
socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); | TCP 소켓 생성 |
socket(PF_INET, SOCK_DGRAM, 0); | UDP 소켓 생성 |
socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); | UDP 소켓 생성 |
* 가령 소켓을 생성할 때 socket 함수의 인자에 (PF_INET, SOCK_STREAM, IPPROTO_UDP)나 (PF_INET, SOCK_DGRAM, IPPROTO_TCP)의 조합을 사용하는 것은 허용되지 않는다!!
소켓 기술자를 이용해서 원격지의 응용 프로그램과 자료를 송수신 할 때 자료를 읽어 오거나 써서 보낼 때 read 와 write 함수를 사용하기도 한다.
이들 함수는 파일 기술자(File Description)를 이용해서 파일을 읽거나 쓸 때 뿐만 아니라 소켓을 이용해서 자료를 송수신할 때도 사용한다.
#include <unistd.h>
int read(int fd, void *buf, int count); // fd : 자료를 수신할 소켓 기술자
buf : 읽어 들인 자료를 저장하는 버퍼의 주소(수신 버퍼)
count : 읽어 들이는 자료의 최대 크기(바이트)
//성공 시 실제 읽은 자료의 크기(바이트), 실패 시 -1
read 함수는 fd가 가리키는 파일이나 소켓으로부터 최대 count 크리만큼의 자료를 buf로 읽어들인다. 읽기가 성공하면 읽은 자료의 크기(바이트)를 반환한다.
네트워크나 터미널에서 자료를 읽을 때 현재 가용한 자료의 크기가 count보다 적으면 적은 양의 자료만을 읽어 온다.
이런 경우는 오류가 아니며, 실제 읽은 자료의 크기를 반환한다. 읽기에 실패하면 -1을 반환한다.
자료를 원격지에서 가져오기 위해 read함수를 호출하면 원격지에서 자료를 가져오는 것이 아니고, 원격지로부터 전송받은 자료가 보관되어 있는 소켓의 수신 버퍼(Receive buffer)에서 사용자 버퍼로 복사 해 오는 것이다.
만약 read함수를 호출했는데 소켓의 수신 버퍼에 자료가 없으면 원격지로부터 자료를 전송받을 때까지 프로그램이 대기한다. 즉, 프로그램이 일시적으로 차단(Blocking)된다.
커널(Kernel)에서는 응용 프로그램의 실행과 상관없이 원격지에서 해당 소켓으로 전송한 자료를 수신 버퍼에 저장한다.
#include <unistd.h>
int write(int fd, const void *buf, int count); // fd : 자료를 수신할 소켓 기술자
buf : 읽어 들인 자료를 저장하는 버퍼의 주소(수신 버퍼)
count : 읽어 들이는 자료의 최대 크기(바이트)
//성공 시 실제 쓴 자료의 크기(바이트), 실패 시 -1
write 함수는 fd가 가리키는 파일이나 소켓에 count 크기만큼 buf의 내용을 저장한다. 저장에 성공하면 저장한 자료의 크기를 반환하고, 저장에 실패하면 -1을 반환한다.
0이 반환된 경우는 아무것도 저장하지 못했음을 의미한다.
write함수를 호출하는 경우도 사용자 버퍼에서 소켓의 송신 버퍼로 단순히 복사하는 것에 불과하다. 만약 write 함수는 호출했는데 송신 버퍼가 꽉 차 있어서 복사를 할 수 없으면 공간이 빌 때까지 프로그램이 대기한다.
즉, 프로그램이 일시적으로 차단된다. 커널에서는 소켓의 송신 버퍼에 보관되어있는 자료를 응용 프로그램과 관계없이 원격지로 전송한다.
이들 함수 외에도 send와 recv등의 입출력 함수를 사용해서 소켓에 자료를 저장하거나 가져올 수 있다.
< 주소에 연결 >
#include <sys/socket.h>
int bind(int sock_fd, const struct sockaddr *addr, in addrlen) // sock_fd : 클라이언트의 연결 요청을 받아 처리할 소켓 기술자
// addr : 서버가 처리할 자료의 목적지 IP 주소와 포트 번호를 지닌 주소
// addrlen : 주소의 길이
//성공 시 0, 실패 시 -1
bind 함수는 소켓 sock_fd를 구조체(sockaddr형) 변수 addr에 연결해서, 해당 주소를 목적지로 해서 들어오는 자료를 수신 할 수 있게 한다.
ex)
int s_socket;
struct sockaddr_in s_addr;
bind(s_socket, (struct sockaddr*)&s_addr, sizeof(s_addr))
위의 예는 bind 함수를 호출해서 소켓에 주소를 연결하고 있다. 서버가 별도의 bind 함수를 호출하지 않으면 서버는 listen 함수를 호출할 때 자동으로 사용 가능한 포트를 선택한다. 일반적으로 클라이언트에서는 별도의 bind 함수를 호출하지 않고, connect 함수를 호출할 때 커널에서 부여하는 포트를 사용한다. 서버에서는 클라이언트가 서버의 포트 번호를 알아야 하기 때문에, 커널이 임의로 부여하는 포트를 사용하기 어렵다. 따라서 사용할 포트 번호를 명시하고, bind 함수를 호출해서 특정 포트를 사용하게 해야 한다. 한편 포트 번호로 0을 설정하면 커널이 임의로 포트를 배정한다.
< 개통 요청 >
#include <sys/socket.h>
int listen(int sock_fd, int backlog) // sock_fd : 연결을 받아들이도록 요청할 소켓 기술자
// backlog : 이 소켓으로 통신하려고 큐에서 대기하는 클라이언트의 수
//성공 시 0, 실패 시 -1
TCP 서버는 listen 함수를 호출해서 원격지로부터의 연결 요청을 시스템이 받아들이도록 커널에 통보한다. 이 함수의 두번째 인자인 backlog는 커널이 관리하는 대기 큐에서 여녈 요청을 하고 기다리는 큐에 연결 요청을 기다리는 총 클라이언트 수를 알려준다. 이 큐에는 연결 요청한 소켓이 SYN_RCVD이나 ESTABLISHED 상태로 바뀐다.(소켓의 내부적인 상태와 변화에 대해서는 11장 TCP 내부 동작에서 자세히 다룬다.)
⊙ SYN_RCVD 상태 - 클라이언트가 connect 함수로 서버에 요청한 연결을 처리하기 위해 시스템 내부에서 서버로 SYN 패킷을 보내야 3방향 핸드셰이킹을 진행 중인 상태이다.
⊙ ESTABLISHED 상태 - 클라이언트가 connect 함수로 서버에 요청한 연결을 시스템이 모두 처리한 상태이다. 3방향 핸드셰이킹이 모두 완료되면 이 상태가 된다.
예를 들어 backlog 인자가 10으로 설정되어 있으면 현재 연결이 완료된 소켓의 수와, 연결 요청을 위해 SYN 패킷을 보내서 연결 설정이 진행 중인 소켓의 수를 합쳐 최대 10개까지 허용한다는 의미이다.
그런데 설정된 backlog의 값에 대해 실제로 연결되는 개수는 운영체제의 구현 상황에 따라 다를 수 있다. 예를 들어 Linux 2.4.7에서는 backlog의 값을 10으로 설정하는 경우 실제 연결 개수는 13개까지 연결할 수 있다. backlog의 값을 0으로 동일하게 설정해도 Solaris 2.9에서는 실제 연결 개수가 1개인데 비해서 Linux 2.4.7에서는 3개이기 때문에 사용에 주의해야 한다.
[출처] Socket의 기본개념 - 3|작성자 몽상가
[출처] SOCKET 기본개념 - 2|작성자 몽상가