- 소켓의 프로토콜과 그에 따른 데이터 전송 특성
- 프로토콜(Protocol)이란 무엇인가?
프로토콜이란 대화에 필요한 통신규약을 의미한다
컴퓨터 상호간의 대화에 필요한 통신규약
- 소켓의 생성
#include <sys/socket.h> int socket(int domain, int type, int protocol); // 성공 : 파일 디스크립터 // 실패 : -1 domain : 소켓이 사용할 프로토콜 체계(Protocol Family) 정보 전달 type : 소켓의 데이터 전송방식에 대한 정보 전달 protocol : 두 컴퓨터간 통신에 사용되는 프로토콜 정보 전달 |
- 프로토콜 체계(Protocol Family)
socket 함수의 첫 번째 인자로, 생성되는 소켓이 사용할 프로토콜의 부류정보를 전달해야 한다. 이러한 부류정보를 가리켜 '프로토콜 체계'라 하며, 프로토콜 체계의 종류는 다음과 같다
이름 | 프로토콜 체계(Protocol Family) |
PF_INET | IPv4 인터넷 프로토콜 체계 |
PF_INET6 | IPv6 인터넷 프로토콜 체계 |
PF_LOCAL | 로컬 통신을 위한 UNIX 프로토콜 체계 |
PF_PACKET | Low Level 소켓을 위한 프로토콜 체계 |
PF_IPX | IPX 노벨 프로토콜 체계 |
PF_INET에 해당하는 IPv4 인터넷 프로토콜 체계 외에 중요도가 떨어지거나 아직 보편화 되지 않은 프로토콜 체계이다. 실제 소켓이 사용할 최종 프로토콜 정보는 socket 함수의 세 번째 인자를 통해서 잔달하게 되어 있다. 첫 번째 인자를 통해서 지정한 프로토콜 체계의 범위 내에서 세 번째 인자가 결정되어야 한다.
- 소켓의 타입(type)
소켓의 타입이란 소켓의 데이터 전송방식을 의미하는데, 바로 이 정보를 socket 함수의 두 번째 인자로 전달해야 한다. 그래야 생성되는 소켓의 데이터 전송방식을 결정할 수 있기 때문이다. 프로토콜 체계가 결정되었다고 해서 데이터의 전송방식까지 완전히 결정되는 것은 아니다. 즉, socket 함수의 첫 번째 인자로 전달되는 PF_INET에 해당하는 프로토콜 체계에도 둘 이상의 데이터 전송방식이 존재한다.
- 소켓의 타입 1 : 연결지향형 소켓(SOCK_STREAM)
socket 함수의 두 번째 인자로 SOCK_STREAM을 전달하면 '연결지향형 소켓'이 생성된다
연결지향형 소켓의 데이터 송수신 방식 특징은 다음과 같다
- 중간에 데이터가 소멸되지 않고 목적지로 전송된다
- 전송 순서대로 데이터가 수신된다
- 전송되는 데이터의 경계(Boundary)가 존재하지 않는다
라인상의 문제만 없다면, 데이터가 소멸되지 않음을 보장받을 수 있다. 뿐만 아니라 먼저 보내진 데이터보다 뒤에 보내진 데이터가 일찍 도착할 수 없다. 데이터의 경계가 존재하지 않는다는것은 데이터를 전송하는 컴퓨터가 세 번의 write 함수호출을 통해 총 100바이트를 전송하였다. 그런데 데이터를 수신하는 컴퓨터는 한 번의 read 함수호출을 통해서 100바이트를 전부 수신한다.
데이터를 송수신하는 소켓은 내부적으로 버퍼(buffer), 쉽게 말해서 바이트 배열을 지니고 있다. 그리고 소켓을 통해 전송되는 데이터는 일단 이 배열에 저장된다. 때문에 데이터가 수신되었다고 해서 바로 read함수를 호출해야 하는 것은 아니다. 이 배열의 용량을 초과하지 않는 한, 데이터가 채워진 후에 한 번의 read 함수호출을 통해서 데이터 전부를 읽어 들일수도 있고, 반대로 한번의 write 함수호출로 전송된 데이터 전부를 여러 번의 read 함수 호출을 통해서 읽어 들일 수도 있다. 즉, read 함수의 호출횟수와 write 함수의 호출횟수는 연결지향형 소켓의 경우 큰 의미를 갖지 못한다. 때문에 연결지향형 소켓은 데이터의 경계가 존재하지 않는다고 말하는 것이다.
"소켓 대 소켓의 연결은 반드시 1대 1이어야 한다"
즉, 연결지향형 소켓 하나는 다른 연결지향형 소켓 하나와만 연결이 가능하다.
"신뢰성 있는 순차적인 바이트 기반의 연결지향 데이터 전송 방식의 소켓"
- 소켓의 타입2 : 비 연결지향형 소켓(SOCK_DGRAM)
socket 함수의 두 번째 인자로 SOCK_DGRAM을 전달하면 '비연결지향형 소켓'이 생성된다. 비연결 지향형 소켓은 엄청난 속도로 이동하는 오토바이 택배 서비스에 비유할 수 있다.
SOCK_DGRAM의 데이터 송수신 방식의 특징은 다음과 같다
- 전송된 순서에 상관없이 가장 빠른 전송을 지향한다
- 전송된 데이터는 손실의 우려가 있고, 파손의 우려가 있다.
- 전송되는 데이터의 경계(Boundary)가 존재한다
- 한번에 전송할 수 있는 데이터의 크기가 제한된다.
SOCK_DGRAM은 SOCK_STREAM에 비해 데이터 전송속도는 빠르나, 데이터의 손실 및 훼손이 발생하지 않음을 보장하지 않는다. 그리고 한번에 전송할 수 있는 데이터의 크기가 제한이 되며, 데이터의 경계가 존재한다. 데이터의 경계가 존재한다는 것은 데이터를 전송할 때 두 번의 함수 호출이 수반되었다면, 데이터를 수신할 때에도 두 번의 함수 호출이 수반되어야 함을 의미한다.
"비연결지향형 소켓은 신뢰성과 순차적 데이터 전송을 보장하지 않는, 고속의 데이터 전송을 목적으로 하는 소켓"
- 프로토콜의 최종선택
socket 함수의 세 번째 인자는 최종적으로 소켓이 사용하게 될 프로토콜 정보를 전달하는 목적으로 존재한다.
socket 함수의 첫 번째, 두 번째 전달인자를 통해서도 충분히 원하는 유형의 소켓을 생성할 수 있다. 따라서 대부분의 경우, 세 번째 인자로 그냥 0을 넘겨줘도 우리가 원하는 소켓을 생성할 수 있다. 하지만 다음과 같은 상황 때문에 세 번째 인자는 필요하다
"하나의 프로토콜 체계 안에 데이터의 전송방식이 동일한 프로토콜이 둘 이상 존재하는 경우"
즉, 소켓의 데이터 전송방식은 같지만, 그 안에서도 프로토콜이 다시 나뉘는 상황이 존재할 수 있다. 그리고 이러한 경우에는 세 번째 인자를 통해서 원하는 프로토콜 정보를 조금 더 구체화해야 한다.
"IPv4 인터넷 프로토콜 체계에서 동작하는 연결지향형 데이터 전송 소켓"
int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
"IPv4 인터넷 프로토콜 체계에서 동작하는 비연결지향형 데이터 전송 소켓"
int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
- 연결지향형 소켓! TCP 소켓의 예
다음 예제는 TCP소켓의 다음 특성을 확인해 보려고 한다.
"전송되는 데이터의 경계(Boundary)가 존재하지 않는다"
예제 tcp_clinent.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpha/inet.h> #include <sys/socket.h>
void error_handling(char* message);
int main(int argc, char* argv[]) { int sock; struct sockaddr_in serv_addr; char message[30]; int str_len = 0; int idx = 0, read_len = 0;
if( argc != 3 ) { printf("Usage : %s <IP> <port>\n", argv[0]); exit(1); }
sock = socket(PF_INET, SOCK_STREAM, 0); if( sock == -1 ) error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(atoi(argv[2]));
if( connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) error_handling("connect() error!");
while(read_len = read(sock, &message[idx++], 1)) { if(read_len == -1) error_handling("read() error!");
str_len += read_len; }
printf("Message from server: %s\n", message); printf("Function read call count: %d\n", str_len); close(sock);
return 0; }
void error_handling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } |
- 윈도우 기반에서 이해 및 확인하기
- 윈도우 운영체제의 socket 함수
함수의 이름과 인자로 전달되는 상수의 이름까지 리눅스와 동일하지만 반환형에서 조금 차이가 난다
#include <winsock2.h>
SOCKET socket(int af, int type, int protocol); // 성공 : 소켓 핸들 // 실패 : INVALID_SOCKET 반환 |
반환형이 SOCKET인데, 이는 정수로 표현되는 소켓의 핸들 값을 저장하기 위해 정의된 자료형의 이름이다. 즉, 위 함수가 반환하는 값은 SOCKET형 변수를 하나 선언해서 저장해야 한다.
- 윈도우 기반 TCP소켓의 예
#include <stdio.h> #include <stdlib.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib")
void errorHandling(char* message);
int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hSocket; SOCKADDR_IN servAddr;
char message[30]; int strLen = 0; int idx = 0, readLen = 0;
if( argc != 3 ) { printf("Usage : %s <IP> <port>\n", argv[0]); exit(1); }
if( WSAStartup(MAKEWORD(2,2), &wsaData) != 0 ) errorHandling("WSAStartup() error");
hSocket = socket(PF_INET, SOCK_STREAM, 0); if( hSocket == INVALID_SOCKET ) errorHandling("socket() error");
memset(&servAddr, 0, sizeof(servAddr)); servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = inet_addr(argv[1]); servAddr.sin_port = htons(atoi(argv[2]));
if( connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR ) errorHandling("connect() error");
while(readLen = recv(hSocket, &message[idx++], 1, 0)) { if( readLen == -1 ) errorHandling("recv() error");
strLen += readLen; }
printf("Message form server : %s\n", message); printf("function read call count : %d\n", strLen);
closesocket(hSocket); WSACleanup();
return 0; }
void errorHandling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } |
'책정리 > 열혈 TCP,IP' 카테고리의 다른 글
5장 TCP 기반 서버/클라이언트2 (0) | 2015.08.05 |
---|---|
4장 TCP 기반 서버/클라이언트1 (0) | 2015.08.05 |
3장 주소체계와 데이터 정렬 (0) | 2015.08.05 |
1장 네트워크 프로그래밍과 소켓의 이해 (0) | 2015.08.05 |
목차 (0) | 2015.08.05 |