'Programmings/TCP/IP socket programming'에 해당되는 글 4건
- 2009.12.18 connect()
- 2009.12.16 listen() and accept()
- 2009.12.15 bind()
- 2009.12.15 socket()
connect()
TCP 기반의 클라이언트 프로그램의 구현순서는 간단하다
- 소켓 생성
- 연결 요청
- 데이터 송수신
- 연결 종료
서버 프로그램에서 연결 요청 대기를 하고 있을 때 비로소 클라이언트 프로그램에서는 연결 요청을 할 수 있는 것이다. 연결 요청은 'connect()' 함수를 사용한다.
#include <sys/socket.h>
int connect (int sockfd, struct sockaddr *serv_addr, int addrlen);
- sockfd - 통신을 위해 생성한 소켓의 파일 디스크립터
- serv_addr - 연결 요청을 할 서버의 주소정보를 가진 구조체 변수의 포인터
- addrlen - 연결 요청을 할 서버의 주소정보를 가진 구조체 변수의 크기
connect() 예제
int sock;
struct sockaddr_in serv_addr;
...
// 클라이언트 소켓 생성
sock = socket(PF_INET, SOCK_STREAM, 0);
...
// connect()로 연결 요청
if(connect(sock, (struct sockaddr* ) &serv_addr, sizeof(serv_addr)) == -1)
...
'Programmings > TCP/IP socket programming' 카테고리의 다른 글
listen() and accept() (0) | 2009.12.16 |
---|---|
bind() (0) | 2009.12.15 |
socket() (0) | 2009.12.15 |
listen() and accept()
socket() 함수를 통해 통신을 위한 소켓을 생성하고, bind() 함수를 통해 소켓에 주소를 할당 한 다음 해야할 일은 어디선가로 부터의 통신 연결 요청에 대해 대기하고 있어야 한다.
이 때 연결 요청 대기상태로 만들어주는 함수가 'listen()' 함수이다.
int listen (int s, int backlog);
s - 연결 요청을 받아들일 소켓의 파일 디스크립터(서버 소켓)
backlog - 연결 요청 대기 큐의 크기
리턴값 - 성공 시 0, 실패 시 -1을 리턴
listen() 함수는 SOCK_STREAM과 SOCK_SEQPACKET 에만 사용된다.
실제 네트워크 상에서 소켓을 이용한 통신이 이루어지는 순서를 보자.
'서버측'의 프로그램은 socket() 함수를 통해 통신을 위한 소켓을 하나 생성한다. 이는 클라이언트의 연결을 받아들이기 위한 용도로 사용된다. 주소정보를 sockaddr_in 구조체에 채워 넣은 후 bind() 함수를 사용해 소켓에 서버측 프로그램의 주소정보를 할당한다. 그리고 클라이언트의 연결 요청이 있기 전까지 서버측의 프로그램은 listen() 함수를 통해 응답 요청에 대한 대기상태로 들어간다.
'클라이언트측'의 프로그램은 socket() 함수를 통해 통신을 위한 소켓을 생성하고, connect() 함수를 통해 서버측 프로그램에 연결을 요청한다. 이러한 클라이언트 프로그램의 요청에 대해 서버측 프로그램은 backlog 크기의 연결 요청 대기 큐에 클라이언트측 프로그램의 요청을 밀어넣고, accept() 함수를 사용하여 클라이언트측 프로그램과 연결을 하고 통신을 하게 된다.
backlog는 최대 접속할 수 있는 클라이언트의 수를 의미한다. backlog를 5로 설정했을 때 연결 요청 대기 큐에는 5개 까지의 클라이언트 프로그램의 요청이 들어갈 수 있다.
이때 서버측 프로그램은 새로이 소켓을 하나 생성하게 된다.
처음 만들어진 소켓은 통신을 위해 클라이언트 측의 요청을 대기하기 위한 용도의 소켓이고, accept() 시 생성되는 소켓은 클라이언트와 실제 데이터를 주고 받는 통신을 하기 위해 사용되는 용도의 소켓이다.
다음은 'accept()' 함수의 프로토타입이다.
#include <sys/socket.h>
int accept (int s, struct sockaddr *addr, int *addrlen);
s - 연결 요청을 받아들일 소켓의 파일 디스크립터(서버 소켓)
addr - 연결 요청을 수락할 클라이언트측 프로그램의 주소정보를 가진 변수의 포인터
addrlen - 두번째 인자 addr 포인터가 가리키는 구조체의 크기
리턴값 - 성공 시 클라이언트와 데이터를 주고받기 위해 사용될 소켓의 파일 디스크립터, 실패 시 -1
accept() 함수는 연결지향 소켓타입 (SOCK_STREAM, SOCK_SEQPACKET, SOCK_RDM)에 사용된다.
'Programmings > TCP/IP socket programming' 카테고리의 다른 글
connect() (0) | 2009.12.18 |
---|---|
bind() (0) | 2009.12.15 |
socket() (0) | 2009.12.15 |
bind()
통신을 위한 소켓을 생성하고나면, 소켓에 주소를 할당해야 한다. 이때 사용하는 함수가 'bind()' 이다.
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, int addrlen);
- sockfd - 주소를 할당하고자 하는 소켓의 파일 디스크립터
- myaddr - 할당하고자 하는 주소정보를 가지고 있는 구조체 변수의 포인터
- addrlen - 인자로 전달되는 주소정보 구조체의 길이
- 리턴값 - 성공 시 0, 실패 시 -1 을 리턴
두 번째 인자 myaddr는 struct sockaddr* 형이지만, 실제는 sockaddr_in을 형변환 한 것이다.
sockaddr_in을 살펴보면...
sa_family_t sin_family; // 소켓 타입
uint16_t sin_port; // 연결에 사용되는 포트 번호
struct in_addr sin_addr; // 연결을 받아들일 주소
char sin_zero[8]; // 사용되지 않음
}
struct in_addr {
uint32_t s_addr; // IPv4 인터넷 주소
}
소켓 생성을 해서 소켓에 주소를 할당하는 bind() 까지의 예
int serv_sock;
char *serv_ip = "127.0.0.1";
char *serv_port = "9190";
struct sockaddr_in serv_addr;
serv_sock = socket(PF_INET, SOCK_STREAM, 0); /* 소켓 생성 */
if(serv_sock == -1)
// error 처리..
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; // 소켓 타입
serv_addr.sin_addr.s_addr = inet_addr(serv_ip); // IP 주소
serv_addr.sin_port = htons(atoi(serv_port)); // 포트 번호
if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1) /* 소켓에 주소 할당 */
// error 처리..
...
추가 #1.
소켓에 사용되는 모든 데이터는 Big Endian으로 동작한다. 일반적으로 사용하는 기계가 Little Endian 인경우가 대부분이고, 혹시나 모르기 때문에 소켓에 사용되는 데이터(IP 주소, 포트 번호 등)은 네트워크 바이트 순서(Big Endian)으로 변경해 주어야 한다.
이 때 사용되는 함수가 htons(), ntohs(), htonl(), ntohl() 이다.
h - host byte
n - network byte
l - long (32bit)
s - short (16bir)
htons()라고 한다면 short형의 host byte를 short형의 network byte로 변경해준다는 의미이다.
[참고] Endian에 대해서는 이곳을 참고!!!
추가 #2.
IP 주소를 보면 127.0.0.1 과 같은 형태인 것을 볼 수 있다. 하지만 실제 사용될 때에는 unsigned long 형으로 표현이 된다.
127.0.0.1 과 같은 형태를 'Dotted-Decimal Notation'이라고 부르며, 이는 위의 예제 소스에서 확인 할 수 있듯 문자열이다. 이를 unsigned long 형으로 바꾸어주는 함수가 'inet_addr()' 이다.
#include <netinet/in.h>
#include <arpa/inet.h>
unsigned long inet_addr (const char* string);
추가 #3.
위의 예제 소스를 보면 IP 주소를 직접 입력했다. 하지만 직접 IP 주소를 입력할 경우 다른 기계에서 동작을 할 수 없게 된다. 각 기계마다 IP 주소가 동일한 것이 아니기에...
이 때 'INADDR_ANY' 라는 상수를 통해서 현재 시스템의 IP를 자동으로 찾아서 할당해 주는 방법을 사용할 수 있다.
char *serv_port = "9190";
struct sockaddr_in serv_addr;
...
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; // 소켓 타입
serv_addr.sin_addr.s_addr = inet_addr(INADDR_ANY); // IP 주소
serv_addr.sin_port = htons(atoi(serv_port)); // 포트 번호
...
'Programmings > TCP/IP socket programming' 카테고리의 다른 글
connect() (0) | 2009.12.18 |
---|---|
listen() and accept() (0) | 2009.12.16 |
socket() (0) | 2009.12.15 |
socket()
통신을 하기 위해선 소켓을 생성해야 한다. 소켓은 통신을 위한 엔드포인트 정도로 생각하면 될 듯 하다.
소켓을 생성하기 위해서 socket() 이란 함수를 사용한다. socket()를 통해 시스템 내부적으론 소켓을 생성하고, 생성된 소켓을 조작하기 위한 파일 디스크립터를 리턴한다.
#include <sys/socket.h>
int socket (int domain, int type, int protocol);
- domain - 통신을 하기 위해 사용할 프로토콜 체계(Protocol Family)
- type - 소켓에서 사용하게 될 데이터 전송 타입
- protocol - 통신을 하기 위한 프로토콜
- 리턴값 - 성공 시 int형 파일 디스크립터, 실패 시 -1 리턴
일반적으로 아래와 같이 TCP socket 또는 UDP socket 를 생성하여 사용하게 된다.
int tcp_sock, udp_sock;
tcp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
udp_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
...
리턴값으로 파일 디스크립터를 받게 되는데, 리눅스에서 현재 사용하고 있는 디스크립터(0, 1, 2 이외의 )가 있다면 그 다음 숫자부터 차례로 디스크립더를 리턴받게 된다.
'Programmings > TCP/IP socket programming' 카테고리의 다른 글
connect() (0) | 2009.12.18 |
---|---|
listen() and accept() (0) | 2009.12.16 |
bind() (0) | 2009.12.15 |