Socket Address
소켓 주소
- 소켓 주소 구조체는 OS Kernerl과 Process 사이를 양방향으로 오가며 전달될 수 있다.
- 호스트와 호스트 사이에서는 소켓 주소 구조체 전체가 오가지 않으며,
IP주소와 포트번호와 같은 일부 필드만을 주고받을 수 있다.
* Socket
- L4의 데이터를 주고받을 수 있게하는 S/W Object이다.
- TCP에서 Kernel과 User Process 사이에서 Socket을 식별하기 위한 정보는 IP주소와 포트번호이다.
- Transport Layer에서는 "Socket"이란 용어가 규정된 바가 없으며, "Socket"은 BSD 구현 시에 등장한 개념이다.
* Datatypes required by the POSIX Specification
Datatype | Description | Header |
\(\texttt{int8_t}\) | Signed 8-bit Integer | \(\texttt{<sys/types.h>}\) |
\(\texttt{uint8_t}\) | Unsigned 8-bit Integer | \(\texttt{<sys/types.h>}\) |
\(\texttt{int16_t}\) | Signed 16-bit Integer | \(\texttt{<sys/types.h>}\) |
\(\texttt{uint16_t}\) | Unsigned 16-bit Integer | \(\texttt{<sys/types.h>}\) |
\(\texttt{int32_t}\) | Signed 32-bit Integer | \(\texttt{<sys/types.h>}\) |
\(\texttt{uint32_t}\) | Unsigned 32-bit Integer | \(\texttt{<sys/types.h>}\) |
\(\texttt{sa_family_t}\) | 소켓 주소 구조체의 Address Family 값 | \(\texttt{<sys/socket.h>}\) |
\(\texttt{socklen_t}\) | 소켓 주소 구조체의 크기 (일반적으로, \(\texttt{uint32_t}\)) |
\(\texttt{<sys/socket.h>}\) |
\(\texttt{in_addr_t}\) | IPv4 주소 (일반적으로, \(\texttt{uint32_t}\)) |
\(\texttt{<netinet/in.h>}\) |
\(\texttt{in_port_t}\) | TCP or UDP 포트번호 (일반적으로, \(\texttt{uint16_t}\)) |
\(\texttt{<netinet/in.h>}\) |
※ POSIX Specification에서 \(\texttt{u_char, u_short, u_int, u_long}\) Type들은 "Obsolete"하지만,
Backward Compatibility를 위해 여전히 제공되고 있다.
Socket Address Structure (소켓 주소 구조체)
- 대부분의 Socket Function들은 Argument로 소켓 주소 구조체를 나타내는 Pointer를 요구한다.
- 소켓 주소 구조체의 이름은 \(\texttt{sockaddr_}\)로 시작하여, Postfix는 프로토콜에 따라 달라진다.
* RFC 791 (Internet Protocol) (URL)
Generic Socket Address Structure (일반적인 소켓 주소 구조체)
// in <sys/socket.h> Header
struct sockaddr {
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
};
- 일반적인 소켓 주소 구조체의 이름은 \(\texttt{sockaddr}\) 이다.
- \(\texttt{<sys/socket.h>}\) 헤더파일에 정의되어 있다.
- 소켓 주소 구조체는 항상 \(\texttt{socket()}\) Function에 Pass by Reference 방식으로 전달되는데, (포인터 타입으로 넘김)
ANSI C에서는 포인터의 타입으로 \(\texttt{void*}\) 사용했다.
- 그러나 ANSI C보다 오래된 개념인 \(\texttt{socket()}\) Function에서는 포인터 타입 문제를 해결하기 위해 \(\texttt{sockaddr}\)을 정의하고,
포인터 타입으로 \(\texttt{struct sockaddr*}\)를 사용했다.
- Kernel은 \(\texttt{struct sockaddr*}\) 타입(일반적인 소켓 구조)의 Argument를 통해 \(\texttt{sa_family}\) 값을 살펴서
세부적인 소켓 구조의 타입을 결정짓는다.
Example. Argument로써 소켓 주소 구조체를 요구하는 \(\texttt{bind()}\) 함수의 원형과 응용
int bind(int, struct sockaddr *, socklen_t);
// Prototype for bind() Function
struct sockaddr_in serv;
...
bind(sockfd, (struct sockaddr *) &serv, sizeof(serv));
IPv4 Socket Address Structure (IPv4 소켓 주소 구조체)= Internet Socker Address Structure(인터넷 소켓 주소 구조체)
// POSIX Definition
#include <netinet/in.h>
struct in_addr {
in_addr_t s_addr; // 32-bits IPv4 Address
};
struct sockaddr_in {
uint8_t sin_len; // 구조체의 크기값 (보조적 역할)
sa_family_t sin_family; // 주소군 구분값 (ex. AF_INET)
in_port_t sin_port; // 포트번호값
struct in_addr sin_addr; // IPv4 주소값
char sin_zero[8]; // 추후에 추가적으로 사용할 Attribute (Unused)
};
- IPv4 소켓 주소 구조체의 이름은 \(\texttt{sockaddr_in}\) 이다.
- \(\texttt{<netinet/in.h>}\) 헤더파일에 정의되어 있다.
* 주소 체계 (for \(\texttt{sin_family}\) Field)
Address Family (주소 체계) |
Description |
\(\texttt{AF_INET}\) | IPv4 인터넷 프로토콜 |
\(\texttt{AF_INET6}\) | IPv6 인터넷 프로토콜 |
\(\texttt{AF_LOCAL}\) | LOCAL 통신을 위한 UNIX 프로토콜 |
* 프로토콜 체계 (for \(\texttt{sin_family}\) Field)
Protocol Family (프로토콜 체계) |
Description |
\(\texttt{PF_INET}\) | IPv4 인터넷 프로토콜 |
\(\texttt{PF_INET6}\) | IPv6 인터넷 프로토콜 |
\(\texttt{PF_LOCAL}\) | LOCAL 통신을 위한 UNIX 프로토콜 |
\(\texttt{PF_PACKET}\) | Low Level Socket을 위한 인터페이스 |
\(\texttt{PF_IPX}\) | IPX 노벨 프로토콜 |
\(\texttt{sa_family_t sin_family, in_port_t sin_port, struct in_addr sin_addr}\)
- POSIX Specification에서 필수적으로 구현을 요구하는 필드들이다.
- POSIX를 따르는 구현에서는 필드를 추가로 정의하는 것이 가능하다.
(대부분의 인터넷 소켓 주소 구조체에는 필드들이 추가적으로 정의되어 있다.)
* \(\texttt{sin_addr}\)이 \(\texttt{in_addr_t}\) 타입으로 바로 정의되지 않고, 구조체로 정의된 이유
- 초기 4.2BSD에서는 Classful Addressing 방식의 IPv4 주솟값 32-bits를
1-Byte 혹은 2-Bytes씩 접근할 수 있도록 \(\texttt{in_addr}\) 구조체를 여러 타입의 Union(공용체)으로 정의했었다.
- 추후, Subnetting과 Classless Addressing 개념의 등장으로 인해 Union 구현이 필요가 없어지면서,
\(\texttt{in_addr_t}\) 타입 원소 하나만 남게된 것이다.
- \(\texttt{in_addr_t}\) 타입은 \(\texttt{unsigned long}\) 타입의 Alias이다.
* Classful Addressing (클래스 기반 주소 지정) (URL)
* Classless Addressing (클래스 없는 주소 지정) (URL)
\(\texttt{uint8_t sin_len}\)
- 소켓 주소 구조체의 길이를 저장하는 필드이다.
- \(\texttt{sin_len}\)은 가변적인 소켓 주소 구조체를 처리할 때 용이하게 사용된다.
- Routing Socket을 다룰 때에는 \(\texttt{sin_len}\)값이 설정되거나, 검사되지 않는다. (=사용되지 않는다.)
- OSI Protocol을 지원하는 4.3BSD-Reno에서 추가된 필드이다.
(본래, \(\texttt{sockaddr_in}\) 구조체에서는 \(\texttt{sa_family_t sin_family}\)가 첫 번째 필드였다.)
- 모든 POSIX에서 \(\texttt{sin_len}\)를 지원하는 것은 아니며, POSIX Compliant System에서 \(\texttt{sin_len}\)를 제공하고 있다.
(즉, 구현에 필수적이지 않다.)
\(\texttt{char sin_zero[8]}\)
- 소켓 주소 구조체의 크기를 16-bytes로 만들기 위한 Dummy Bits이다.
- 혹은, 추후 구현에 추가사항이 생길것을 대비하여 만들어 놓은 필드이다.
- 구현에 필수적이지 않다.
IPv6 Socket Address Structure (IPv6 소켓 주소 구조체)
#include <netinet/in.h>
struct in6_addr {
uint8_t s6_addr[16]; // 128-bits IPv6 address (Network Byte Ordered)
};
#define SIN6_LEN // Compile-Time Test를 위한 define
struct sockaddr_in6 {
uint8_t sin6_len; // 구조체의 크기값 (보조적 역할)
sa_family_t sin6_family; // 주소군 구분값 (ex. AF_INET6)
in_port_t sin6_port; // 포트번호값 (Network Byte Ordered)
uint32_t sin6_flowinfo; // Flow Information (Undefined)
struct in6_addr sin6_addr; // IPv6 주솟값 (Network Byte Ordered)
uint32_t sin6_scope_id; // Set of Interfaces for a Scope
};
- IPv6 소켓 주소 구조체의 이름은 \(\texttt{sockaddr_in6}\) 이다.
- \(\texttt{<netinet/in.h>}\) 헤더파일에 정의되어 있다.
- 소켓 주소 구조체의 길이(\(\texttt{sin6_len}\))를 지원하는 시스템에서는 \(\texttt{SIN6_LEN}\)을 정의해야 한다.
- IPv6의 Adderss Family값은 \(\texttt{AF_INET6}\)로 정의되어 있다.
\(\texttt{uint32_t sin6_flowinfo}\)
- \(\texttt{sin6_flowinfo}\) 32-bits 값은 크게 두 가지 부분으로 구분된다.
- [19: 0] : Flow Label에 사용된다.
- [31:20] : 차후를 위해 Reserve해둔다.
\(\texttt{uint32_t sin6_scope_id}\)
- Scope Zone*을 식별하는 필드이다.
* Scope Zone
- Link Local Address에 대한 Interface Index이다.
New Generic Socket Address Structure (새로운 일반적인 소켓 주소 구조체)
#include <netinet/in.h>
struct sockaddr_storage {
uint8_t ss_len;
sa_family_t ss_family;
/* implementation-dependent elements to provide:
* a) alignment sufficient to fulfill the alignment requirements of
* all socket address types that the system supports.
* b) enough storage to hold any type of socket address that the
* system supports.
*/
};
- \(\texttt{sockaddr_storage}\) 구조체는 기존의 \(\texttt{sockaddr}\) 구조체의 결점을 보완하여 IPv6 Socket API의 일부분으로 정의되었다.
- \(\texttt{sockaddr_storage}\) 구조체는 시스템이 지원하는 모든 소켓 주소 타입을 수용할 수 있을 만큼 크다.
- 소켓 구조가 정렬을 요구하는 경우, \(\texttt{sockaddr_storage}\)는 매우 엄격한 정렬 기능을 제공한다.
- \(\texttt{sockaddr_storage}\)는 구현에 따라 많은 부분이 달라지기 때문에 \(\texttt{ss_family}\)가 저장하고 있는 주소에 대한
적절한 소켓 주소 구조체로 변환되거나 복사되어야 한다.
Value-Result Arguments (값-결과 인수)
- 모든 Socket Function들은 소켓 주소 구조체를 Pass by Reference 방식으로 넘겨받는다.
(즉, 소켓 주소 구조체를 포인터로 넘겨받는다.)
- 하지만, 소켓 주소 구조체의 길이값은 Kernel to Process이냐, Process to Kernel에 따라 Passing의 방식이 다르다.
- Process to Kernel에서는 구조체의 길이를 Passing by Value 형태로 넘기고,
Kernel to Process에서는 구조체의 길이를 Passing by Result 형태로 넘긴다.
(이런 점 때문에, 구조체의 길이를 값-결과 인수라 할 수 있는 것이다.)
* \(\texttt{bind(), connect(), sendto(), sendmsg()}\) Functions
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // need <sys/types.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // need <sys/types.h>
ssize_t sendto(int socket, const void *message, size_t length,
int flags , const struct sockaddr *dest_addr, socklen_t dest_len);
ssize_t sendmsg(int socket, const struct msghdr *message, int flags);
- Process에서 Kernel로 소켓 주소 구조체를 전달하는 함수들이다.
- Berkeley-Derived Implementation에서 위 네 함수들은 \(\texttt{sockargs()}\) 함수를 통과한다.
- \(\texttt{sockargs()}\) 함수는 Process로부터 소켓 주소 구조체를 복사하고,
\(\texttt{sin_len}\)값을 위 네 함수들의 Argument로 전달되는 Structure의 크기로 결정한다.
- Kernel은 소켓 주소 구조체의 길이를 알 수 있으므로, Process로 부터 복사해야 하는 데이터의 양을 정확히 알 수 있다.
- \(\texttt{sendmsg()}\) 함수를 제외하고, Process에서 Kernel로 소켓 구조체를 넘길 땐,
소켓 주소 구조체의 길이를 Passing by Value형태로 넘긴다.
- \(\texttt{sendmsg()}\) 함수는 소켓 주소 구조체의 길이를 \(\texttt{msghdr}\) 구조체의 원소에 포함시켜 전달한다.
* \(\texttt{accept(), recvfrom(), recvmsg(), getpeername(), getsockname()}\) Functions
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); // need <sys/types.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen); // need <sys/types.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); // need <sys/types.h>
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- Kernel에서 Process로 소켓 주소 구조체를 전달하는 함수들이다.
- Process로 돌아가기 전에, \(\texttt{sin_len}\)값을 결정짓는다.
- \(\texttt{recvmsg()}\) 함수를 제외하고, Kernel에서 Process로 소켓 구조체를 넘길 땐,
Kernel이 소켓 주소 구조체에 저장한 데이터의 길이를 Passing by Reference 형태로 넘긴다.
- \(\texttt{recvmsg()}\) 함수는 소켓 주소 구조체의 길이를 \(\texttt{msghdr}\) 구조체의 원소에 포함시켜 전달한다.
Byte Ordering Functions (바이트 순서 함수)
#include <arpa/inet.h> // Some systems require the inclusion of <netinet/in.h>
uint32_t htonl(uint32_t hostlong); // Host to Network Long (32-bits)
uint16_t htons(uint16_t hostshort); // Host to Network Short (16-bits)
uint32_t ntohl(uint32_t netlong); // Network to Host Long (32-bits)
uint16_t ntohs(uint16_t netshort); // Network to Host Short (16-bits)
- \(\texttt{unit16_t}\) 또는 \(\texttt{unit32_t}\) Type은 \(\texttt{<inttypes.h>}\) 헤더파일에 정의되어 있다.
- 일반적으로, 4 Bytes의 \(\texttt{long}\) 타입은 IP 변환에, 2 Bytes의 \(\texttt{short}\) 타입은 Port 변환에 사용된다.
- 호스트에서는 Big-Endian 방식과 Little_Endian 방식이 혼용되어 사용되고,
네트워크에서의 Byte-Order는 무조건 Big Endian방식만 사용된다.
(IP는 Big-Endian Byte Order를 사용한다.)
- 이와 같이, 시스템마다의 Endian 방식의 표준이 존재하지 않아,
위 네 함수를 이용하여 해당 시스템에 적합하게 Byte Order를 변환해야 한다.
* 대부분의 인터넷 표준에서, Byte를 "Octet"이라 표기하곤 한다.
Byte Manipulation Functions (바이트 조작 함수)
Berkely-Derived Functions (4.2BSD)
#include <strings.h>
void bzero (void *dest, size_t nbytes);
- \(\texttt{*dest}\)의 Bytes를 \(\texttt{nbytes}\)만큼 0으로 초기화한다.
- 소켓 주소 구조체를 0으로 초기화할 때 용이하게 사용된다.
- 2개의 Argument만을 요구하기 때문에, ANSI C의 \(\texttt{memset()}\) 함수보다 사용하기 쉽다.
(또한, 두 매개변수의 Type이 서로 다르기 때문에, 잘못 사용했을 경우 컴파일러가 이를 발견할 수 있다.)
- 소켓 API를 제공하는 Vendor는 일반적으로 \(\texttt{bzero()}\)를 지원한다.
2) \(\texttt{bcopy()}\) Function
#include <strings.h>
void bcopy (const void *src, void *dest, size_t nbytes);
- \(\texttt{*src}\)에서 \(\texttt{*dest}\)로 \(\texttt{n}\) Bytes만큼의 Bytes를 옮긴다.
3) \(\texttt{bcmp()}\) Function
#include <strings.h>
int bcmp (const void *ptr1, const void *ptr2, size_t nbytes);
- \(\texttt{*ptr1}\) 바이트 열과 \(\texttt{*ptr2}\) 바이트 열을 비교하여,
같으면 0을 리턴하고, 그렇지 않으면 0이 아닌 값을 리턴한다.
ANSI C Functions
1) \(\texttt{memset()}\) Function
#include <string.h>
void *memset(void *dest, int c, size_t len);
- \(\texttt{*dest}\)에서 \(\texttt{len}\) 개수 만큼을 \(\texttt{c}\)값으로 초기화한다.
- 3개의 Argument를 요구한다. (\(\texttt{bzero()}\)는 오직 2개의 Argument만 요구한다.)
- 두 번째, 세 번째 매개변수가 둘 다 \(\texttt{int}\)형이기 때문에 착각하기 쉽다.
2) \(\texttt{memcpy()}\) Function
#include <string.h>
void *memcpy(void *dest, const void *src, size_t nbytes);
- \(\texttt{*src}\)에서 \(\texttt{*dest}\)로 \(\texttt{n}\) Bytes만큼의 Bytes를 옮긴다.
(\(\texttt{bcopy()}\)와 Argument의 순서가 다름에 유의하자.)
- \(\texttt{memcpy()}\)는 \(\texttt{*src}\)와 \(\texttt{*dest}\)가 겹쳐있는 경우에 대처방법이 정의되어 있지 않아,
겹침이 있는 경우 \(\texttt{memmove()}\) 함수를 사용해야 한다.
(\(\texttt{bcopy()}\)는 겹침에 대한 처리가 구현되어 있다.)
3) \(\texttt{memcmp()}\) Function
#include <string.h>
int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);
- \(\texttt{*ptr1}\) 바이트 열과 \(\texttt{*ptr2}\) 바이트 열을 비교하여 같으면 0을 리턴하고,
그렇지 않으면 0이 아닌 값을 리턴한다.
Address Conversion Function (주소 변환 함수)
- 사용자는 ASCII 문자열로 IP 주솟값을 나타내고,
시스템에서는 IP 주솟값을 Network Byte Order의 이진수 값으로 나타낸다.
- 주소 변환 함수들은 사용자와 시스템이 원활한 주소 교환이 가능하도록 상호 변환시켜주는 기능을 한다.
- IPv4와 IPv6에서 사용하는 주소 체계가 다르기 때문에,
주소 변환 함수를 통해 프로토콜의 종류에 따라 적절한 변환이 이루어져야 한다.
* IPv4 에서 사용하는 주소 변환 함수 : \(\texttt{inet_aton(), inet_addr(), inet_ntoa()}\)
* IPv4와 IPv6 둘 다 사용 가능한 주소 변환 함수 : \(\texttt{inet_pton(), inet_ntop()}\)
Only IPv4 Conversion Functions
- 사용자가 입력한 Dotted-Decimal String 형태(ASCII String)의 IPv4 주솟값과
32-Bits Network Byte Order로된 Binary Value 형태의 IPv4 주솟값을 상호 변환한다.
1) \(\texttt{inet_aton()}\) Function
#include <arpa/inet.h>
int inet_aton(const char *strptr, struct in_addr *addrptr);
- \(\texttt{strptr}\)(C-String)를 32-Bits Network Byte Order의 binary Value로 변환하여 \(\texttt{*addrptr}\)에 저장한다.
- \(\texttt{inet_aton()}\)은 변환에 성공하면 1을 리턴하고, 그렇지 않으면 0을 리턴한다.
- \(\texttt{*addrptr}\)이 NULL이면, \(\texttt{inet_aton()}\)은 \(\texttt{strptr}\)의 적합성만 검사하고,
실질적인 저장은 수행하지 않는다.
2) \(\texttt{inet_addr()}\) Function
#include <arpa/inet.h>
in_addr_t inet_addr(const char *strptr);
- \(\texttt{strptr}\)(C-String)를 32-Bits Network Byte Order의 binary Value로 변환하여 리턴한다.
- 변환에 에러가 발생되면 \(\texttt{inet_addr()}\)은 상수 \(\texttt{INADDR_NONE}\)(255.255.255.255)을 리턴한다.
(즉, \(\texttt{inet_addr()}\)은 IPv4 Limited Broadcast Address를 처리하지 못한다.)
3) \(\texttt{inet_ntoa()}\) Function
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr inaddr);
- 32-Bits Network Byte Order의 binary Value를 Dotted-Decimal String 형태인 C-String으로 변환하여 리턴한다.
IPv4 and IPv6 Conversion Functions
- p : Presentation
- n : Numeric
1) \(\texttt{inet_pton()}\) Function
#include <arpa/inet.h>
int inet_pton (int family, const char *strptr, void *addrptr);
- Presentation to Numeric : IP주소 표현을 Network Byte Order Binary Value로 변환한다.
- \(\texttt{*strptr}\)를 Network Byte Order Binary Value로 변환하여 \(\texttt{*addrptr}\)에 저장한다.
- \(\texttt{family}\)는 \(\texttt{AF_INET}\)(IPv4) 값이나,
\(\texttt{AF_INET6}\)(IPv6) 값 둘 중 하나로 지정되어 IPv4 주소인지, IPv6 주소인지를 구분짓는다.
Return:
\(\texttt{inet_pton()}\) Return Value | Description |
+1 | 정상적인 변환에 성공한 경우 : 변환 성공 |
0 | \(\texttt{strptr}\)에 주소군에 부합되지 않는 주소가 전달된 경우 : 변환 실패 |
-1 \(\texttt{errno = EAFNOSUPPORT}\) |
\(\texttt{family}\)에 부적절한 값이 전달된 경우 : 변환 실패 |
2) \(\texttt{inet_ntop()}\) Function
#include <arpa/inet.h>
const char *inet_ntop (int family, const void *addrptr, char *strptr, size_t len);
- Numeric to Presentation : Network Byte Order Binary Value를 IP주소 표현으로 변환한다.
- \(\texttt{*addrptr}\)을 family에 지정된 IP주소 형태의 C-String으로 변환하여 \(\texttt{*strptr}\)에 저장한다.
- Callerdml Buffer Overflow를 방지하기 위해, \(\texttt{len}\)에 목적지 버퍼의 크기를 Argument로 요구한다.
Return:
- 변환에 성공하면, \(\texttt{strptr}\)에 NULL이 아닌 값을 리턴한다.
- 변환 결과가 \(\texttt{len}\)보다 크면, \(\texttt{inet_ntop()}\)는 NULL을 리턴하고,
\(\texttt{errno}\)를 \(\texttt{ENOSPC}\)로 설정한다.
- family에 부적절한 값이 입력되면, \(\texttt{inet_ntop()}\)는 NULL을 리턴하고,
\(\texttt{errno}\)를 \(\texttt{EAFNOSUPPORT}\)로 설정한다.
* \(\texttt{len}\)에 일반적으로 사용되는 상수
#include <netinet/in.h>
#define INET_ADDRSTRLEN 16 // for IPv4 dotted-decimal
#define INET6_ADDRSTRLEN 46 // for IPv6 hex string
* IPv4만을 지원하는 간단한 \(\texttt{inet_pton()}\) 구현내용
int inet_pton(int family, const char *strptr, void *addrptr)
{
if (family == AF_INET) {
struct in_addr in_val;
if (inet_aton(strptr, &in_val)) {
memcpy(addrptr, &in_val, sizeof(struct in_addr));
return (1);
}
return (0);
}
errno = EAFNOSUPPORT;
return (-1);
}
* IPv4만을 지원하는 간단한 \(\texttt{inet_ntop()}\) 구현내용
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len)
{
const u_char *p = (const u_char *) addrptr;
if (family == AF_INET) {
char temp[INET_ADDRSTRLEN];
snprintf(temp, sizeof(temp), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
if(strlen(temp) >= len) {
errno = ENOSPC;
return (NULL);
}
strcpy(strptr, temp);
return (strptr);
}
errno = EAFNOSUPPORT;
return (NULL);
}
Protocol Independent Conversion Function (User-Defined, NOT Standard)
char *sock_ntop(const struct sockaddr *sa, socklent_t salen);
* \(\texttt{sock_ntop()}\) Function (URL)
- \(\texttt{sock_ntop()}\)는 소켓 주소 구조체와 그 크기를 Argument로 넘겨받아서,
프로토콜에 맞는 표현 형식을 리턴한다.
(즉, 사용자가 해당 소켓 주소 구조체의 프로토콜을 몰라도 된다.)
readn() Function (User-Defined)
#include <unistd.h>
// read() Function
#include <sys/types.h>
// size_t, ssize_t Types
ssize_t readn(int fd, void *vptr, size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR) // EINTR : Error Interrupt
nread = 0; /* and call read() again */
// read()에서 Blocked되어 있다가, Signal 발생을 통보받으면 nread값을 0으로 초기화한다.
else
return(-1);
} else if (nread == 0)
break; /* EOF */
nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
}
- \(\texttt{fd}\) File Descriptor로부터 \(\texttt{n}\) Bytes만큼을 읽어들이는 함수이다.
- 읽어들일 데이터의 크기가 크면, 한 번의 \(\texttt{read()}\) 호출로 부족할 수 있기 때문에,
\(\texttt{read()}\)를 Loop내에 위치시키고, \(\texttt{read()}\)의 리턴값이 0이하이면 Loop를 종료시킨다.
* \(\texttt{size_t}\)
- 시스템에서 Object를 담을 수 있는 최대 크기 정수이다. (C99 정의)
- Unsigned Type이다.
- \(\texttt{sizeof()}\)의 Return Type이기도 하다.
* \(\texttt{ssize_t}\) Type
- 부호를 가진 \(\texttt{size_t}\) 타입과 같다.
- Signed Type이다.
\(\texttt{read()}\) Function (\(\texttt{read()}\) 함수)
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count)
- File Descriptor \(\texttt{fd}\)에서 \(\texttt{count}\) bytes 만큼의 값을 읽어들여 \(\texttt{buf}\)에 저장한다.
- \(\texttt{read()}\) 또한, \(\texttt{connect()}\)와 같이, 서버로부터 응답을 받을 때 까지 해당 프로세스를 Block시킨다.
- Daytime 프로그램은 서버는 소켓을 통해 \(\texttt{write()}\) 하고,
클라이언트는 소켓을 통해 데이터를 \(\texttt{read()}\) 하는 구조이다.
- 읽어들일 데이터의 크기가 크면, 한 번의 \(\texttt{read()}\) 호출로 부족할 수 있기 때문에,
\(\texttt{read()}\)를 Loop내에 위치시키고, \(\texttt{read()}\)의 리턴값이 0이하이면 Loop를 종료시키는 것이 바람직하다.
(이것이 본 포스트에서 정의한 \(\texttt{readn()}\) 함수의 원리이다.)
Return:
- \(\texttt{read()}\)는 File Offset만큼 읽기 연산을 하는 도중 EOF Event가 발생하면,
더 이상 읽어들이지 않고 0을 리턴한다.
- 또한, \(\texttt{count}\)가 0이어도 0을 리턴하고, 에러가 발생한 경우, -1이 리턴한다.
- 정상적으로, 읽기 연산에 성공한 경우, \(\texttt{read()}\)는 읽어들인 bytes 수를 리턴한다.
Arguments:
1) \(\texttt{int fd}\)
- 데이터를 읽을 소켓의 File Descriptor이다.
2) \(\texttt{void *buf}\)
- 읽어들인 데이터를 저장할 Array이다.
3) \(\texttt{size_t count}\)
- 읽어들일 데이터의 양이다.
- 꼭 \(\texttt{count}\)만큼을 읽어들인다는 보장은 없다.
writen() Function (User-Defined)
#include <unistd.h>
// write() Function
#include <sys/types.h>
// size_t, ssize_t Types
ssize_t writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR)
nwritten = 0; /* and call write() again */
else
return(-1); /* error */
}
nleft -= nwritten;
ptr += nwritten;
}
return(n);
}
- \(\texttt{fd}\) File Descriptor에 \(\texttt{n}\) Bytes만큼 데이터를 쓰는 함수이다.
- \(\texttt{writen()}\)은 \(\texttt{write()}\)를 이용하여 \(\texttt{count}\)만큼의 출력이 보장된 함수이다.
\(\texttt{write()}\) Function (\(\texttt{write()}\) 함수)
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
- File Descriptor \(\texttt{fd}\)에 buf에 있는 값을 \(\texttt{count}\) Bytes 만큼 출력한다.(쓴다.)
Return:
- 정상적으로, 쓰기 연산에 성공한 경우, \(\texttt{write()}\)는 쓴 데이터의 Bytes 수를 리턴한다.
- 출력에 오류가 발생되면 -1을 리턴하고 오류 사항을 \(\texttt{를 설정한다.
- \(\texttt{count}\)가 0이거나, \(\texttt{fd}\)가 부적절한 값을 가리키고 있는 경우에 대한 리턴값은 특정되지 않았다.
Arguments:
1) \(\texttt{int fd}\)
- 데이터를 읽을 소켓의 File Descriptor이다.
2) \(\texttt{void *buf}\)
- 읽어들인 데이터를 저장할 Array이다.
3) \(\texttt{size_t count}\)
- 쓸 데이터의 양이다.
- 꼭 \(\texttt{count}\)만큼을 출력한다는 보장은 없다.
* \(\texttt{write()}\) 함수에서 \(\texttt{count}\) 만큼의 출력이 보장되지 못하는 이유
1) 충분하지 않은 Spec.의 Physical Medium
2) \(\texttt{RLIMIT_FSIZE}\)의 발생
- \(\texttt{SIGXFSZ}\) Signal에 의해 전달되는 값으로, 프로세스가 만든, Byte 단위의 File의 최대 크기이다.
3) \(\texttt{write()}\) 도중에, Signal Handler에 의한 Interrupt 발생
readline() Function (User-Defined)
#include <errno.h>
// errno Variable
// EINTR Signal
#include <sys/types.h>
// size_t, ssize_t Types
ssize_t readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;
ptr = vptr;
for (n = 1; n < maxlen; n++) {
again:
if ( rc = read(fd, &c, 1)) == 1 ) {
*ptr++ = c;
if (c == '\n')
break;
} else if (rc == 0) {
*ptr = 0;
return (n - 1);
} else {
if (errno == EINTR)
goto again;
return (-1);
}
}
*ptr = 0;
return (n);
}
- \(\texttt{readline()}\)은 newline문자('\n')까지, 한 문장씩 읽어들이는 함수이다.
- 소켓 \(\texttt{read()}\)시에, 한 문장씩 읽어들이는 것은 위험하기 때문에 권장되지 않는다.
\(\texttt{fgets()}\) Function (\(\texttt{fgets()}\) 함수)
#include <stdio.h>
char *fgets(char *restrict s, int n, FILE *restrict stream);
- \(\texttt{stream}\)으로부터 Byte단위로 읽어들여 \(\texttt{s}\)에 저장한다.
- \(\texttt{n-1}\) Bytes만큼 입력되거나, \n(Newline)이 입력될 때 까지 \(\texttt{fgets()}\)는 Blocked된다.
Return:
- 성공적으로 읽어들인 경우, \(\texttt{s}\)를 리턴한다.
- EOF가 입력될 경우, \(\texttt{fgets()}\)는 NULL을 리턴한다.
- 출력에 성공하지 못한 경우에도, NULL를 리턴하고 \(\texttt{stream}\)에 error indicator를 설정하는 동시에,
에러사항을 \(\texttt{errno}\)에 설정한다.
- \(\texttt{n-1}\) Bytes만큼 입력되거나, \n(Newline)이 입력되면 \(\texttt{fgets()}\)는 리턴된다.
\(\texttt{fputs()}\) Function (\(\texttt{fputs()}\) 함수)
#include <stdio.h>
int fputs(const char *restrict s, FILE *restrict stream);
- NULL-Terminated String \(\texttt{s}\)를 \(\texttt{stream}\)에 출력시키는 함수이다.
- 이 때, \(\texttt{s}\)의 마지막 문자 NULL은 \(\texttt{stream}\)에 출력되지 않는다.
Return:
- 출력에 성공한 경우, 출력한 문자의 개수를 리턴한다.
- 출력에 성공하지 못한 경우, EOF를 리턴하고 \(\texttt{stream}\)에 error indicator를 설정하는 동시에,
에러사항을 \(\texttt{errno}\)에 설정한다.
* Open File Table의 Index(Descriptor)에는 Device File을 위해, 미리 예약되어 있는 값들이 있다.
- File Descriptor = 0 : \(\texttt{stdin}\) (키보드)
- File Descriptor = 1 : \(\texttt{stdout}\) (스크린)
- File Descriptor = 2 : \(\texttt{stderr}\) (에러)
- \(\texttt{fputs()}\)를 통해 화면에 \(\texttt{s}\)를 출력하고자 한다면, \(\texttt{stream}\)에 \(\texttt{stdout}\)을 입력하면 된다.
\(\texttt{snprintf()}\) Function (\(\texttt{snprintf()}\) 함수)
#include <stdio.h>
int snprintf(char *str, size_t size, const char *format, ...);
- \(\texttt{<stdio.h>}\) 헤더파일에 정의되어 있다.
- \(\texttt{str}\)에 일련의 문자를 \(\texttt{format}\)에 맞춰서 저장한다.
- \(\texttt{snprintf()}\) 함수는 \(\texttt{str}\)에 작성된 Byte 수를 리턴한다.
(문자열 끝의 NULL 문자는 계산하지 않는다.)
Return:
- 출력에 성공하면, 출력한 문자의 개수를 리턴한다.
(이 때, 출력하는 문자의 개수에는 문자열의 끝 문자 NULL('\0')도 포함된다.)
- \(\texttt{size}\) 값으로 인해 출력하고자 했던 \(\texttt{str}\)이 Truncated 된 경우,
원래 출력하고자 했던 문자의 개수를 리턴한다.
(이 때도 물론, 출력하는 문자의 개수에는 문자열의 끝 문자 NULL('\0')도 포함된다.)
- 즉, \(\texttt{snprintf()}\)의 출력값이 \(\texttt{size}\) 값보다 크면, 출력하고자 했던 문자열이 Truncated되었음을 의미한다.
Arguments:
1) \(\texttt{char *str}\)
- 출력할 문자열이 저장되어 있는 Pointer이다.
2) \(\texttt{size_t size}\)
- \(\texttt{size}\)는 상대 머신의 Buffer 크기이다.
- \(\texttt{size_t}\) Type은 32bit 시스템에선 32bits, 64bit 시스템에선 64bits Integer Type으로 정의된다.
- \(\texttt{size}\) 값 이상을 출력하지 않는다.
3) \(\texttt{const char *format}\)
※ \(\texttt{sprintf()}\)는 상대 머신의 Buffer Overflow 여부를 검사할 수 없다.
\(\texttt{snprintf()}\)는 \(\texttt{size}\) Argument를 이용하여 상대 머신의 Overflow를 방지할 수 있기 때문에 \(\texttt{snprintf()}\)를 사용할 것이 권장된다.
Reference: UNIX Network Programming Volume 1, 3E: The Sockets Networking API (W. Richard Stevens, Bill Fenner, Andrew M. Rudoff 저, Addison-Wesley, 2004)