|
System Program/socket 2014. 3. 6. 15:37
1. 서버 프로그램
클라이언트로부터 송신한 문자열을 stdout에 단지 출력한다.
서버에서는 accept()함수로부터 단지 하나의 새로운 소켓 디스크립터를 할당 받고
이 파일 디스크립터를 이용하여 클라이언트로부터 문자열을 받는다.
즉, 멀티프로세스, 멀티쓰레드 구현이 아니다. 이를 구현하기 위해서는
accept()함수로 계속적으로 새로운 소켓 파일 디스크립터를 생성받고 새로운 프로세스나 새로운 쓰레드를
통해서 다른 추가적인 작업 후 종료하는 방식으로 작성되야 할 것이다.
/*
* main.c -- C Test App.
*
* Copyright (C) 2012-2013, 2013 heesoon.kim <chipmaker.tistory.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define READ_MAX_LEN 128
int main(int argc, char *argv[])
{
int sockfd, acceptfd;
ssize_t nread;
struct sockaddr_in server_addr, client_addr;
socklen_t server_addrlen, client_addrlen = 0;
char buffer[READ_MAX_LEN] = {0};
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("192.168.0.2");
//server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(5000);
server_addrlen = sizeof(server_addr);
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sockfd < 0)
{
perror("socket : ");
exit(EXIT_FAILURE);
}
if(bind(sockfd, (struct sockaddr *)&(server_addr), server_addrlen) < 0)
{
perror("bind : ");
exit(EXIT_FAILURE);
}
if(listen(sockfd, 5) < 0)
{
perror("listen : ");
exit(EXIT_FAILURE);
}
acceptfd = accept(sockfd, (struct sockaddr *)&(client_addr), &client_addrlen);
if(acceptfd < 0)
{
perror("listen : ");
exit(EXIT_FAILURE);
}
while(1)
{
memset(buffer, 0, READ_MAX_LEN);
nread = read(acceptfd, buffer, READ_MAX_LEN);
if(nread == -1)
{
perror("read : ");
exit(EXIT_FAILURE);
}
write(STDOUT_FILENO, buffer, strlen(buffer));
if(strncmp(buffer, "bye", 3) == 0)
{
close(acceptfd);
break;
}
}
close(sockfd);
printf("exit process \n");
exit(EXIT_SUCCESS);
}
2. 클라이언트 프로그램
단지 문자열을 서버단으로 전송하는 작업만을 수행한다.
/*
* main.c -- C Test App.
*
* Copyright (C) 2012-2013, 2013 heesoon.kim <chipmaker.tistory.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define WRITE_MAX_LEN 128
int main(int argc, char *argv[])
{
int sockfd;
ssize_t nread = 0;
char buffer[WRITE_MAX_LEN] = {0};
struct sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.s_addr = inet_addr("192.168.0.2");
ServerAddr.sin_port = htons(5000);
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sockfd < 0)
{
perror("socket : ");
exit(EXIT_FAILURE);
}
if(connect(sockfd, (struct sockaddr *)&(ServerAddr), sizeof(ServerAddr)) == -1)
{
perror("connect : ");
exit(EXIT_FAILURE);
}
while(1)
{
memset(buffer, 0x00, WRITE_MAX_LEN);
puts("input message : ");
nread = read(STDIN_FILENO, buffer, WRITE_MAX_LEN /* SSIZE_MAX -1 */);
if(nread == -1)
{
perror("read : ");
exit(EXIT_FAILURE);
}
if(write(sockfd, buffer, (size_t)nread) < 0)
{
perror("write : ");
exit(EXIT_FAILURE);
}
if(strncmp(buffer, "bye", 3) == 0)
break;
}
close(sockfd);
printf("exit process \n");
exit(EXIT_SUCCESS);
}
System Program/socket 2014. 3. 6. 11:46
1. 서버 프로그램
단순히 클라이언트가 연결을 시도하여 연결이 성사되면 "hellow world from echo server program"
문자를 클라이언트에게 에코한다.
/*
* main.c -- C Test App.
*
* Copyright (C) 2012-2013, 2013 heesoon.kim <chipmaker.tistory.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main(int argc, char *argv[])
{
struct sockaddr_in server_addr, client_addr;
int sockfd, acceptfd;
socklen_t server_addrlen, client_addrlen = 0;
ssize_t n;
char ClientAddrStr[INET_ADDRSTRLEN];
char buffer[128] = "hellow world from echo server program \n";
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("192.168.0.2");
//server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(5000);
server_addrlen = sizeof(server_addr);
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sockfd < 0)
{
perror("socket : ");
exit(EXIT_FAILURE);
}
if(bind(sockfd, (struct sockaddr *)&(server_addr), server_addrlen) < 0)
{
perror("bind : ");
exit(EXIT_FAILURE);
}
if(listen(sockfd, 5) < 0)
{
perror("listen : ");
exit(EXIT_FAILURE);
}
while(1)
{
acceptfd = accept(sockfd, (struct sockaddr *)&(client_addr), &client_addrlen);
if(acceptfd < 0)
{
perror("listen : ");
exit(EXIT_FAILURE);
}
else
{
// echo fucntion
write(acceptfd, buffer, strlen(buffer));
printf("client address = %s \n", inet_ntop(client_addr.sin_family, &(client_addr.sin_addr),
ClientAddrStr, INET_ADDRSTRLEN));
close(acceptfd);
}
}
close(sockfd);
exit(EXIT_SUCCESS);
}
2. 클라이언트 프로그램
telnet 192.168.0.2 5000 으로 동작을 확인할 수 있다.
더 고찰 사항은 서버가 연결해제된 상태에서 연결 시도를 수행할 때 발생하는 시그널 관련하여
클라이언트처리를 확인하기 위해서는 별도의 클라이언트 프로그램 구현이 필요할 것으로 보인다.
System Program/socket 2014. 3. 5. 10:23
1. 소개
소켓을 이용해서 프로그램을 개발할 때, 일반적인 소켓 API를 구성하여 작성하는 기본적인 구현 이외에
소켓 설정에 대한 수정을 통해서 더 정교한 제어를 할 수 있다.
소켓 설정에 대한 수정은 소켓 옵션 설정 시스템 함수를 통하여 이루어진다.
2. 소켓 옵션 함수
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
성공하면 '0'을 리턴하고, 에러가 발생하면 '-1'을 리턴
위의 함수에서 다른 매개변수은 쉽게 이해할 수 있다.
두번째 매개변수인 level에 대해서 정리하면 아래와 같다.
level |
설명 |
참고문서 |
SOL_SOCKET |
socket() 함수 레벨에서 옵션 수정 |
socket (7) manual 참조 |
IPPROTO_TCP |
TCP 소켓에 대한 옵션 수정 |
tcp (7) manual 참조 |
IPPROTO_UDP |
UDP 소켓에 대한 옵션 수정 |
udp (7) manual 참조 |
IPPROTO_IP |
IP 단계에 대한 옵션 수정 |
ip (7) manual 참조 |
세번째 인자에 대한 가능한 optname은 참고문서에서 기술한 메뉴얼을 참조
3. 예시
완전한 예시라기 보다는 옵션 함수 사용법 정도 본다는 수준이다.
/*
* main.c -- C Test App.
*
* Copyright (C) 2012-2013, 2013 heesoon.kim <chipmaker.tistory.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main(int argc, char *argv[])
{
static int reuseflag = 1;
size_t sockbuffer = (188 * 7) * 150;
socklen_t sockbufferlen = 0;
int sockfd;
struct sockaddr_in server_addr;
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0)
{
perror("socket : ");
exit(EXIT_FAILURE);
}
/* 연결 중에 잠시 발생한 에러로 연결해제된 주소에 대해 기존 kernel이 유지 중인 주소를 재 사용 */
if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void *)&reuseflag, (socklen_t)sizeof(reuseflag)) != 0)
{
perror("setsockopt : ");
exit(EXIT_FAILURE);
}
/* socket buffer 수정 */
if(setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &sockbuffer, sizeof(sockbuffer)) != 0)
{
perror("setsockopt : ");
exit(EXIT_FAILURE);
}
if(getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &sockbuffer, &sockbufferlen) != 0)
{
perror("setsockopt : ");
exit(EXIT_FAILURE);
}
printf("socket buffer size = %d\n", sockbuffer);
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(5000);
if(bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
perror("bind : ");
exit(EXIT_FAILURE);
}
return 0;
}
System Program/socket 2014. 3. 4. 14:30
1. getaddrinfo() 함수
호스트와 서비스명에 해당하는 IP 주소와 포트로 찾아 변환한다.
기존 gethostbyname()과 getserverbyname() 함수를 대체하였다.
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *host, const char *service,
const struct addrinfo *hints, struct addrinfo **result);
성공하면 '0'을 리턴하고, 에러가 발생하면 '0'이 아닌 값을 리턴
getaddrinfo()함수는 host, service, hist 값을 입력으로 result로부터 정보를 추출하는 구조이다.
addrinfo 구조체는 아래와 같다.
struct addrinfo{
int ai_flags; /* 입력 플래그 (AI_* 상수) */
int ai_family; /* 주소 패밀리 : AF_INET, AF_INET6 */
int ai_socktype; /* 종류 : SOCK_STREAM, SOCK_DGRAM */
int ai_protocol; /* 소켓 프로토콜 */
size_t ai_addrlen; /* ai_addr 이 가르키는 구조체 크기 */
char * ai_canonname; /* 공식 호스트 명 */
struct sockaddr *ai_addr; /* 소켓 주소 구조체를 가르키는 포인터 */
struct addrinfo *ai_next; /* 링크드 리스트에서 다음 구조체 */
};
1.1. hints 인자에 대한 고찰
hints 인자를 통해서 getaddrinfo() 함수에서 리턴하는 주소 구조체의 정보를 선택적으로 취할 수 있다.
ai_flags, ai_family, ai_socktype, ai_protocol만 설정할 수 있다.
ai_family에는 AF_INET, AF_INET6, AF_UNSPEC 설정 가능. 마지막 값은 모든 종류를 리턴하도록 강제한다.
ai_socktype에는 SOCK_DGRAM, SOCK_STREAM, 0이 가능, 0이면 모든 값을 리턴
ai_protocol에는 대충 '0'을 설정한다.
ai_flags는 getaddrinfo()함수의 수행 방식을 변경하는 비트 마스크다. '0'혹은 아래 값과 OR연산한다.
flags |
설명 |
AI_ADDRCONFIG |
로컬 시스템에 IPv4, IPv6 중에 하나가 있으면 리턴 |
AI_ALL |
AI_V4MAPPED 참조 |
AI_CANNAME |
host가 NULL이 아닌 경우, NULL로 종료하는 공식 호스트명 포인트 리턴 |
AI_NUMERICHOST |
host를 숫자로 이뤄진 주소 문자열로 해석하도록 강제 |
AI_NUMERICSERC |
service를 숫자 포트 번호로 해석하도록 강제 |
AI_PASSIVE |
수동 접속에 적합한 소켓 구조체 리턴 |
AI_V4MAPPED |
IPv6 주소를 발견하지 못하면 IPv4로 매핑된 주소를 리턴 |
2. addrinfo 리스트 해제 : freeaddrinfo()
getaddrinfo() 함수는 result가 가르키는 모든 구조체 메모리를 동적으로 할당한다.
getaddrinfo() 함수를 호출한 곳에서 해당 구조체를 사용하고 메모리 해제를 책임져야 한다.
#include <sys/socket.h>
#include <netdb.h>
void freeaddrinfo(struct addrinfo *result);
3. 에러 진단 : gai_strerror()
에러가 발생한 경우 아래 값 중에 하나를 에러코드로 리턴한다.
#include <netdb.h>
const char *gai_strerror(int errcode);
아래 에러 상수에 해당하는 문자열을 리턴
에러 상수 |
설명 |
EAI_ADDRFAMILY |
hints.ai_family에 해당하는 호스트 주소가 없다 |
EAI_AGAIN |
이름 해석 일시 오류(나중에 다시 시도) |
EAI_BADFLAGS |
hints.ai_flags에 잘못된 플레그 설정 |
EAI_FAIL |
이름 서버에 접속하면서 회복할 수 없는 실패가 생성 |
EAI_FAMILY |
hints.family에 설정한 주소 패밀리를 지원할 수 없음. |
EAI_MEMORY |
메모리 할당 에러 |
EAI_NODATA |
host에 해당하는 주소를 찾을 수 없음 |
EAI_NONAME |
host나 service를 알수 없음. |
EAI_OVERFLOW |
인자 버퍼 오버플로우 |
EAI_SERVICE |
hints.ai_socktype에 알맞은 service를 지원하지 않음. |
EAI_SOCKTYPE |
hints.ai_socktype을 지원하지 않음. |
EAI_SYSTEM |
errno로 시스템 에러를 리턴 |
4. getnameinfo() 함수
getaddrinfo() 함수와 정반대의 동작을 수행한다.
#include <sys/socket.h>
#include <netdb.h>
int getnameinfo(const struct sockaddr *addr, socklen_t addrlen,
char *host, size_t hostlen, char *service, size_t servlen, int flags);
성공하면 '0'을 리턴, 에러가 발생하면 '0'이 아닌 값을 리턴
sockaddr 구조체를 통해 address 정보를 주면 host와 service에 대한 정보를 얻을 수 있다.
flags에 대한 자세한 내용은 아래 표를 참조
flags |
설명 |
NI_DGRAM |
데이터그램 소켓에 대한 정보를 리턴하도록 강제한다. |
NI_NAMEREQD |
호스트명을 찾을 수 없으면 숫자를 기본적으로 리턴하나 해당 플레그로 에러 리턴 |
NI_NOFQDN |
기본적으로 FQDN을 리턴, 호스트가 로컬일 경우만 FQDN의 첫 부분만 리턴 |
NI_NUMERICHOST |
DNS 대신 host에 숫자 형식의 문자열을 리턴하도록 강제 |
NI_NUMERICSERV |
십진수 포트 번호를 리턴하도록 강제, /etc/services에 검색하지 않음. |
5. 구형 유사 함수
5.1. IP 주소를 얻어오는 함수
#include <netdb.h>
extern int h_errno;
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const char *addr, socklen_t len, int type);
성공하면 hostent 구조체의 포인터를 리턴, 에러가 발생하면 NULL을 리턴
5.2. 포트 정보를 얻어오는 함수
/etc/services 파일에서 레코드를 가져오는 함수들이다.
#include <netdb.h>
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);
성공하면 servent구조체의 포인터를 리턴, 에러가 발생하면 NULL을 리턴
System Program/socket 2014. 3. 4. 14:06
1. DNS 기본 지식
네트워크 상에 다른 호스트에 접속하기 위해서 IP 주소보다는 사람이 읽을 수 있는 이름으로 찾는 것이
더 효율적일 때가 많다.
DNS가 나오기 전에는 호스트 자체적으로 /etc/hosts 파일에 호스트와 IP정보를 매핑하여 관리를 하였다.
하지만 네트워크 규모의 증가로 이를 커버하고자 DNS 기술이 등장하였다.
2. 호스트 상에서 IP 주소 획득 순서
1) /etc/hosts 검색
위의 파일에 찾고자하는 호스트가 목록에 존재하면 그 주소를 이용하고 그렇지 않은 경우 아래 2)단계 수행
2) /etc/resolv.conf
DNS 서버 주소를 확인하고 DNS서버에 호스트에 대한 주소를 얻어온다.
만약 /etc/resolv.conf 파일에 DNS서버 설정에 문제가 없더라도 /etc/hosts 파일에 정상적으로 찾으면
호스트 주소를 얻어오는데는 문제가 없다.
3. 호스트 포트 번호 관리
포트 번호도 잘 알려진 번호에 대해서는 IANA에서 중앙 집중적으로 등록하여 관리한다.
이들 각 포트 번호에는 대응하는 서비스명이 있다.
포트 번호는 IP주소 비해 변경이 없는 편이여서 DNS처럼 관리는 하지 않으나 호스트 내부적으로
/etc/services 파일을 통해서 관리를 한다.
# Network services, Internet style # # Note that it is presently the policy of IANA to assign a single well-known # port number for both TCP and UDP; hence, officially ports have two entries # even if the protocol doesn't support UDP operations. # # Updated from http://www.iana.org/assignments/port-numbers and other # sources like http://www.freebsd.org/cgi/cvsweb.cgi/src/etc/services . # New ports will be added on request if they have been officially assigned # by IANA and used in the real-world or are needed by a debian package. # If you need a huge list of used numbers please install the nmap package.
tcpmux 1/tcp # TCP port service multiplexer echo 7/tcp echo 7/udp discard 9/tcp sink null discard 9/udp sink null systat 11/tcp users daytime 13/tcp daytime 13/udp netstat 15/tcp qotd 17/tcp quote msp 18/tcp # message send protocol msp 18/udp chargen 19/tcp ttytst source chargen 19/udp ttytst source ftp-data 20/tcp ftp 21/tcp fsp 21/udp fspd ssh 22/tcp # SSH Remote Login Protocol ssh 22/udp telnet 23/tcp smtp 25/tcp mail time 37/tcp timserver time 37/udp timserver rlp 39/udp resource # resource location nameserver 42/tcp name # IEN 116 whois 43/tcp nicname tacacs 49/tcp # Login Host Protocol (TACACS)
System Program/socket 2014. 3. 4. 11:10
1. 유닉스 주소 체계 (내부 내트워크)
struct sockaddr_un{
sa_family_t sun_family; /* 항상 AF_UNIX */
char sun_path[108]; /* NULL로 종료되는 소켓 경로명 */
};
2. IPv4 소켓 주소
struct in_addr {
in_addr_t s_addr; /* IPv4 4바이트 부호없는 32비트 정수 */
};
struct sockaddr_in {
sa_family_t sin_family; /* 주소 패밀리 AF_INET 설정 */
in_port_t sin_port; /* 포트 번호 */
struct in_addr sin_addr; /* IPv4 주소 */
unsigned char __pad[X]; /* 패딩 */
};
3. IPv6 소켓 주소
struct in6_addr {
in_addr_t s6_addr[16]; /* IPv6 16바이트 부호없는 128비트 정수 */
};
struct sockaddr_in6 {
sa_family_t sin6_family; /* 주소 패밀리 AF_INET6 설정 */
in_port_t sin6_port; /* 포트 번호 */
uint32_t sin6_flowinfo; /* 흐름 정보 */
struct in6_addr sin6_addr; /* IPv6 주소 */
uint32_t sin6_scope_id; /* 범위 ID */
};
위의 주소 체계는 결국 소켓함수 API에서 사용될 때 struct sockaddr로 변환되어야 한다.
아래 소켓 시스템 함수 domain 표에서 5번째 필드 참조
http://chipmaker.tistory.com/entry/기본-소켓-시스템-함수
System Program/socket 2014. 3. 4. 10:42
호스트는 네트워크 상에 연결된 단말기를 말한다.
호스트에서 작업한 내용을 네트워크로 소켓 API로 전송 시 이종 시스템간 및 네트워크 특성을
반영하여 변환이 필요하다. 이 때 사용되는 함수들을 정리한다.
1. 호스트와 네트워크 바이트 순서 변경 함수
호스트는 빅엔디안/리틀엔디안 시스템에 따라 둘 중 하나를 사용할 수 있다.
하지만 네트워크 전송 시에는 네트워크 바이트 순서로 바꾸어 주어야 한다.
네트워크는 빅엔디안을 사용한다. 이들 바이트 순서간의 상호 변환하는 함수를 기술한다.
#include <arp/inet.h>
uint16_t htons(uint16_t host_uint16);
host_uint16을 네트워크 바이트 순서로 변환한 값을 리턴
uint32_t htonl(uint32_t host_uint32);
host_uint32을 네트워크 바이트 순서로 변환한 값을 리턴
uint16_t ntohs(uint16_t net_uint16);
net_uint16을 호스트 바이트 순서로 변환한 값을 리턴
uint32_t ntohl(uint32_t net_uint32);
net_uint32을 호스트 바이트 순서로 변환한 값을 리턴
네트워크 프로그램 시에는 바이트 순서가 호스트와 네크워크간에 일치하더라도 항상 위의 함수로 변환하는
습관을 가져야 이식성을 높일 수 있다.
2. 호스트와 서비스 변환 함수
컴퓨터 내부에서 IP 주소와 포트 번호는 이진 형식으로 표현한다.
사람이 친숙한 포멧과 기계가 알아먹을 수 있는 포멧간의 상호 변환 함수이다.
일반적인 사람이 사용하는 IP 포멧은 127.0.0.1과 같은 형식이다.
#include <arp/inet.h>
int inet_pton(int domain, const char *src_str, void *addrptr);
src_str이 프리젠테이션 포멧이 아닌 경우에는 '0'을, 에러가 발생하면 '-1'을 리턴, 변환 성공하면 '1'을 리턴
const char *inet_ntop(int domain, const void *addrptr, char *dst_str, size_t len);
성공하면 dst_str의 포인터를 리턴, 에러가 발생하면 NULL을 리턴
[예제]
/*
* main.c -- C Test App.
*
* Copyright (C) 2012-2013, 2013 heesoon.kim <chipmaker.tistory.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
struct sockaddr_in sa;
char buffer[INET_ADDRSTRLEN] = {0};
if(argc < 2)
{
printf("not enough parameter [usage]: %s 'ip address' ", argv[0]);
exit(EXIT_FAILURE);
}
inet_pton(AF_INET, argv[1], &(sa.sin_addr));
if(inet_ntop(AF_INET, &(sa.sin_addr), buffer, INET_ADDRSTRLEN) == NULL)
{
perror("inet");
exit(EXIT_FAILURE);
}
printf("%s\n", buffer);
exit(EXIT_SUCCESS);
}
위의 함수는 IPv6까지 지원하는 함수이다. 과거에는 아래의 함수를 많이 사용하였으나 IPv4만을 지원하는
한계로 사용하지 않는 것이 좋다.
#include <arp/inet.h>
int inet_aton(const char *str, struct in_addr *addr);
str이 유효한 점으로 구분하는 십진수 주소이면 '1'을 리턴, 에러가 발생하면 '0'을 리턴
char *inet_ntoa(struct in_addr addr);
addr을 점으로 구분하는 십진수 문자열로 변환한 결과를 가르키는 포인터값을 리턴한다.
System Program/socket 2014. 3. 3. 11:48
1. 소켓 생성
소켓 생성은 연결형 소켓 생성함수와 동일하게 socket()함수를 사용한다.
단, socket()함수 사용 시 type에 SOCKET_DGRAM을 준다.
2. 소켓 결속
다른 응용 프로그램에서 보낸 데이터를 전송 받으려면 bind()를 이용해 자신의 소켓을 이미 알려진
주소로 결속한다.
여기까지는 연결형 소켓 생성 과정과 동일하다. 자세한 함수 정의는 연결형 소개 부분 참조
3. 데이터 교환
3.1. 데이터 전송
#include <sys/socket.h>
ssize_t sendto(int sockfd, void *buffer, size_t length, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
전송한 바이트 수를 리턴한다. 에러가 발생하면 '-1'을 리턴
첫번째부터 세번째까지는 write()와 동일하고, flags는 send() 함수를 참조
뒤의 주소는 데이터를 보낼 곳의 주소를 지정한다.
3.2. 데이터 수신
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buffer, size_t length, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
전송된 바이트 수를 리턴한다. EOF인 경우에는 '0'을 에러가 발생하면 '-1'을 리턴한다.
세번째까지 인자는 위와 동일하고 주소는 데이터를 전송한 원격지의 주소 정보를 갖는다.
4. 연결형 함수의 변칙적 사용
연결형 소켓에서 사용하는 read()/write()함수를 비연결형에서도 사용할 수 있다.
다만 비연결형에서 사용하기 위해서 설정을 해 주어야 한다. 일반적이지 아니니 이런 것이 있다고만 생각하자.
비연결형 소켓에 또한 연결형 소켓에서 사용하는 connect() 함수를 사용할 수 있다.
이런 경우를 연결형 데이터그램 소켓이라고 한다.
이 연결형 데이터 그램 소켓일 경우에는 read()/write()함수를 사용할 수 있고,
연결형 소켓처럼 지정한 주소에서만 데이터 송수신이 가능하게 할 수 있는 장점이 있다.
5. 흐름도
System Program/socket 2014. 3. 3. 09:18
1. socket() 함수
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
성공하면 파일 디스크립터을 리턴, 에러가 발생하면 '-1'을 리턴
1.1. domain 매개 변수
도메인 |
통신수단 |
응용프로그램 간의 통신 |
주소포멧 |
주소구조체 |
AF_UNIX |
커널 내부 |
동일 호스트 |
경로명 |
sockaddr_un |
AF_INET |
IPv4 이용 |
IPv4로 연결된 호스트 |
32비트 주소 +
16비트 포트 번호 |
sockaddr_in |
AF_INET6 |
IPv6 이용 |
IPv6로 연결된 호스트 |
128비트 주소
16비트 포트 번호 |
sockaddr_in6 |
1.2. type 매개 변수
종류 |
설명 |
SOCK_STREAM |
연결지향적 양방향 신뢰성 있는 통신 방식 |
SOCK_RAW |
socket API에 의한 데이터 가공없이 데이터를 받고자 할 때 사용 |
SOCK_DGRAM |
SOCKET_STREAM과 상반되는 개념 |
1.3. protocol 매개 변수
IPPROTO_TCP : TCP 프로토콜로 AF_INET 도메인과 SOCK_STREAM 유형 사용 시.
IPPROTO_UDP : UDP 프로토콜로 AF_INET 도메인과 SOCK_DGRAM 유형 사용 시.
IPPROTO_RAW : SOCK_RAW 사용하는 경우 시
일반적으로 '0'을 설정, 자세한 설명은 man page를 참조하는 것이 좋다.
2. bind() 시스템 함수
소켓을 주소에 결속한다. 아래 sockaddr 구조에는 결속된 주소 정보를 담고 있다.
전화기에 비유하자면 전화번호를 전화국 (커널)에 등록하는 절차에 해당한다.
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
성공하면 '0'을 리턴, 에러가 발생하면 '-1'을 리턴
domain 매개 변수 표, 5번째 열에 보여지는 것처럼 도메인에 따라 주소체계가 모두 다르다.
모두 다른 주소체계를 위의 sockaddr 구조체를 통해서 통일화할 수 있다.
sockaddr 구조체는 아래와 같다.
struct sockaddr {
sa_family_t sa_family; /* 주소 패밀리 (AF_* 상수) */
char sa_data[14]; /* 소켓 주소 (소켓도메인에 따라 크기가 변한다) */ };
3. listen() 시스템 함수
커널에게 bind에서 결속한 주소로 연결을 받기위해 대기 중임을 알린다.
전화기로 따지면 전화를 받을 준비가 모두된 상태를 의미한다.
이처럼 연결이 되기를 기다리는 형태의 동작을 수동형 연결이라 하며, 일반적으로 서버쪽의 동작이다.
#include <sys/socket.h>
int listen(int sockfd, int backlog);
성공하면 '0'을 리턴, 에러가 발생하면 '-1'을 리턴
위에서 backlog 값은 연결이 지연된 요청에 대한 개수를 리턴한다.
리눅스에서 이 값은 128로 설정되어 있으며, /proc/sys/net/core/somaxconn 파일을 통해서 런타임으로
한도값을 설정할 수 있다.
4. accept() 시스템 함수
연결을 요청한 소켓을 수락하는 동작이다.
전화기로 따지면 수화기를 드는 동작에 해당한다.
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
성공하면 파일 디스크립터를 리턴하고, 에러가 발생하면 '-1'을 리턴
accept에서 핵심은 connect() 호출한 상대방의 소켓과 새로운 연결을 맺을 소켓을 생성하는 것이다.
따라서 socket()함수를 통해서 생성된 소켓은 연결을 계속 기다리는 소켓으로 남아있고,
accept()를 통해서 새로 생성된 소켓을 통해서 connect() 호출한 쪽과 통신을 할 수 있다.
따라서 accept()함수의 리턴값에 새로운 소켓을 리턴하는 것이다.
뒤이은 sockaddr 구조체 포인터를 통해서 connect()을 요청한 쪽의 주소를 얻을 수 있다.
상대방의 주소가 필요없을 경우, 각각 NULL, '0'을 설정하면 된다.
5. connect() 시스템 함수
위에서 listen()으로 대기중인 소켓에 연결을 요청할 때 사용하는 시스템 함수이다.
능동형 연결에 해당하며, 일반적으로 클라이언트쪽의 동작이다.
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
성공하면 '0'을 리턴하고, 에러가 발생하면 '-1'을 리턴
클라이언트쪽에서는 별도의 bind() 동작을 통해서 주소를 결속하는 작업은 없다.
connect()함수의 주소관련 매개변수를 통해서 bind() 동작과 유사한 동작은 포함되어 있다.
6. 소켓을 통한 데이터 전송 및 수신 함수
연결 지향형 소켓 응용에서는 read()/write()함수를 통해서 데이터를 송수신한다.
여기서 중요한 점은 양단간에 연결이 끊어졌을 때, 가장 기본적인 에러처리에 대한 것이다.
연결이 끊어진 곳에 데이터를 읽을려고 시도하면 EOF를 리턴한다.
반대로 데이터를 쓰려고 할 경우, 해당 프로그램은 SIGPIPE 시그널을 받게되고,
시스템 호출은 EPIPE로 실패한다. 시그널 핸들러가 없는 경우 errno를 확인하는 것으로 에러처리하는 것이 바람직하다.
7. close() 시스템 함수
연결된 socket를 닫을 때 사용한다.
8. 연결형 서버-클라이언트 흐름도
|