Socket API
Socket API 를 이용해 네트워크 프로그램을 구현할 수 있다.
설명할 API 종류
- socket() : socket handler 생성
- close() : socket handle 해제
- bind() : IP 및 port 할당
- listen() : accept 에서 사용될 connection 대기열 설정
- accept() : connection 대기열에서 connection 을 가져옴
- connect() : server 로 연결
- setsockopt() : 다양한 소켓 설정
- getsockopt() : 소켓 설정 정보를 취득
socket
int socket(int domain, int type, int protocol);
http://man7.org/linux/man-pages/man2/socket.2.html
파라메터:
- domain : 우선 AF_INET 와 AF_INET6 만 알아두자
- type : 우선 SOCK_STREAM 과 SOCK_DGRAM 만 알아두자
- protocol : 우선 IPPROTO_TCP 와 IPPROTO_UDP 만 알아주자
설명:
- socket handle 을 하나 할당 받는다.
- socket 옵션들도 많지만 우선 TCP/UDP 와 IPv4/IPv6 만 고려하자.
domain 에 AF_INET 를 입력하면 IPv4 socket, AF_INET6 를 입력하면 IPv6 socket 이 생성된다. - type 에 SOCK_STREAM 을 입력하면 TCP socket, SOCK_DGRAM 을 입력하면 UDP socket 이 생성된다.
- protocol 에 TCP/UDP 관계없이 0 을 입력하여도 무방하고 IPPROTO_TCP/IPPROTO_UDP 를 TCP/UDP 구분하여 입력하여도 된다.
예) IPv4 TCP socket 생성
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0) {
// error
}
close
int close(int fd);
http://man7.org/linux/man-pages/man2/close.2.html
파라메터:
- fd : socket() 으로 생성된 핸들
- socket() 함수로 얻은 handle 을 해소
예)
close(sock);
bind
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
http://man7.org/linux/man-pages/man2/bind.2.html
파라메터:
- sockfd : socket() 으로 생성된 핸들
- addr : bind 할 주소 구조체
- addrlen : 주소 구조체의 크기
- socket handle 에 network interface 와 port 를 할당 한다.
- addr 파라메터에 bind 원하는 IP 주소 및 port 정보를 채운 구조체를 입력한다.
- struct sockaddr 은 IPv4 와 IPv6 에 따라 구조체가 확장된다.
- IPv4 는 struct sockaddr_in 구조체를 사용
- IPv6 는 struct sockaddr_in6 구조체를 사용
예) IPv4 주소에 할당
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
// error
}
- INADDR_ANY : 기본 network interface 의 IP 주소 (개발자가 고려할 필요없이 운영체제 기본 network interface 에 할당됨)
- htonl() : host to network long 이라 풀어 읽을 수 있겠다.
- htons() : host to network short 이라 풀어 읽을 수 있겠다.
listen
int listen(int sockfd, int backlog);
http://man7.org/linux/man-pages/man2/listen.2.html
파라메터:
- sockfd : socket() 으로 생성된 핸들
- backlog : connection 대기열 크기
- accept() 를 하기 위해 client 접속 요청 대기열의 크기를 정한다.
예)
if (listen(sock, 5) != 0) {
// error
}
accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
http://man7.org/linux/man-pages/man2/accept.2.html
- sockfd : socket() 으로 생성된 핸들
- addr : client 의 주소가 담길 구조체
- addrlen : client 주소 구조체의 크기
- client 접속 요청 대기열에서 client 정보를 하나 가져온다.
- 대기열에 client 접속 요청이 없으면 요청이 있을 때까지 기다린다.
예) IPv4 주소로 bind 한 경우
struct sockaddr_in client_addr;
socklen_t client_addr_len;
int client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &client_addr_len);
if (client_sock < 0) {
// error
}
- client_addr 에 client 의 IP 및 port 주소가 담겨있다.
connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
http://man7.org/linux/man-pages/man2/connect.2.html
파라메터:
- sockfd : socket() 으로 생성된 핸들
- addr : 연결하고자 하는 remote 주소 구조체
- addrlen : remote 주소 구조체 크기
- Server 소켓은 bind(), listen(), accept() 함수의 호출로 client 접속 요청을 대기하는 반면 client 소켓은 connect() 로 대기하고 있는 server 에 접속 한다.
예) IPv4 server 로 접속 요청
struct sockaddr_in remote_addr;
memset(&remote_addr, 0, sizeof(remote_addr));
remote_addr.sin_family = AF_INET;
remote_addr.sin_addr.s_addr = inet_addr("192.168.0.1");
remote_addr.sin_port = htons(port);
if (connect(sock, (struct sockaddr*)&remote_addr, sizeof(remote_addr)) != 0) {
// error
}
setsockopt
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
http://man7.org/linux/man-pages/man2/setsockopt.2.html
- sockfd : socket() 으로 생성된 핸들
- level : 우선 SOL_SOCKET 만 알아두자
- optname : 설정 이름
- optval : 설정 값
- optlen : 설정 값 데이터의 크기 (e.g. sizeof(int))
- 예제로 제일 많이 사용하는 옵션인 SO_REUSEADDR 을 설명
- bind() 하려는 port 가 이미 다른 프로세스 등에서 bind() 했다면 해당 프로세스가 port 를 반환하기 전까지는 사용할 수 없다.
- 때로는 port 반환이 시간이 걸리거나 프로세스가 비정상 종료되어 close() 를 제대로 해주지 못하는 경우가 있다.
- 이런 경우 socket 에 SO_REUSEADDR 옵션을 주면 해당 port 가 실제로 소유주가 없는 상태라면 할당 받을 수 있도록 한다.
- 이 외에도 여러 옵션들이 있으며 상황에 따라 구글에서 사용법 및 구동 방식을 참고하면 되겠다.
예)
int reuse = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*)&reuse, sizeof(reuse)) != 0) {
// error
}
getsockopt
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
http://man7.org/linux/man-pages/man2/getsockopt.2.html
파라메터:
- sockfd : socket() 으로 생성된 핸들
- level : 우선 SOL_SOCKET 만 알아두자
- optname : 설정 이름
- optval : 설정 값이 담길 데이터
- optlen : 설정 값 데이터의 크기 (e.g. sizeof(int))
- setsockopt() 과 반대로 현재 설정된 값을 가져온다.
- Socket option 에는 set/get 모두 가능한 option 이 있고 set/get 중 하나만 가능한 option 도 있다.
- 필요한 경우 구글에서 검색해 보도록 하자.
참고사항
Socket API 의 공통점이라면 socket() 과 accept() 함수를 제외하고 성공시 모두 0 을 return 한다.
Socket API 데이터 타입
- struct sockaddr : 기본 주소 구조체
- struct sockaddr_in : IPv4 주소 구조체
- struct sockaddr_in6 : IPv6 주소 구조체
- socklen_t : socket API 들이 사용하는 기본적인 크기 표시용 데이터 타입
IPv4 와 IPv6 대응
sockaddr 구조체는 이렇게 생겼다.
struct sockaddr
{
__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
출처: http://repo-genesis3.cbi.utsa.edu/crossref/ns-sli/usr/include/bits/socket.h.html
sockaddr_in 과 sockaddr_in6 는 이렇게 생겼다.
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
/* Ditto, for IPv6. */
struct sockaddr_in6
{
__SOCKADDR_COMMON (sin6_);
in_port_t sin6_port; /* Transport layer port # */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* IPv6 scope-id */
};
출처: http://repo-genesis3.cbi.utsa.edu/crossref/ns-sli/usr/include/netinet/in.h.html
모두 __SOCKADDR_COMMON 라는 macro 로 시작한다.
__SOCKADDR_COMMON macro 는 이렇게 생겼다.
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family
http://repo-genesis3.cbi.utsa.edu/crossref/ns-sli/usr/include/netinet/in.h.html
sockaddr_in 와 sockaddr_in6 구조체의 처음 인자가 동일하게 sa_family_t 형태의 변수다.
family 멤버 변수는 AF_INET 또는 AF_INET6 가 입력되었던 부분이다.
sockaddr_in 또는 sockaddr_in6 의 첫번째 인자인 family 값이 AF_INET 또는 AF_INET6 인지 확인 후 type casting 을 이용해 다른 처리를 할 수 있다.
이후 예제를 통해 다시 살펴 보겠다.
Cross Platform
위 API 들은 Winsock API 에도 동일하게 존재함
Winsock API 에 다른 점
- socket 변수 타입 : int 대신 SOCKET 을 사용
- Socket API 에러 리턴 값 : socket() 이나 accept() 에러 리턴 값은 INVALID_SOCKET, 이외 API 함수들은 SOCKET_ERROR 를 리턴함
댓글 없음:
댓글 쓰기