Chapter 02 - 소켓의 타입과 프로토콜의 설정
Chapter 02 - 소켓의 타입과 프로토콜의 설정
02-1 : 소켓의 프로토콜과 그에 따른 데이터 전송 특성
프로토콜 = 컴퓨터 상호간의 대화에 필요한 통신규약
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
// 성공시 fd, 실패시 -1 반환
domain
: 생성되는 소켓이 사용할 프로토콜 체계(Protocol Family) 정보
PF_INET
: IPv4 인터넷 프로토콜 체계PF_INET6
: IPv6 인터넷 프로토콜 체계PF_LOCAL
: 로컬 통신을 위한 UNIX 프로토콜 체계PF_PACKET
: Low Level 소켓을 위한 프로토콜 체계PF_IPX
: IPX 노벨 프로토콜 체계
type
: 소켓의 데이터 전송방식
SOCK_STREAM
: 연결지향형 소켓(TCP)- 데이터가 전송 도중에 소멸되지 않음
- 전송 순서대로 데이터가 수신됨
- 전송되는 데이터의 경계(Boundary)가 존재하지 않음 : 송수신 버퍼에 저장 - 버퍼의 상태를 확인해가며 송수신함
- 반드시 1대 1로 연결됨
SOCK_DGRAM
: 비 연결지향형 소켓(UDP)- 전송 순서에 관계없이 가장 빠른 전송을 지향
- 데이터의 손실 및 손상 우려가 있음
- 전송되는 데이터의 경계가 존재
- 한번에 전송할 수 있는 데이터의 크기가 제한
- 소켓 연결 개념이 존재하지 않음
protocol
: 최종적으로 소켓이 사용하게될 프로토콜 정보
- 인자로 0을 줄 경우
domain
과type
만으로도 원하는 소켓을 생성할 수 있음 - 단, 하나의 프로토콜 체계 안에 데이터의 전송방식이 동일한 프로토콜이 둘 이상 존재하는 경우가 있기 때문에
protocol
인자를 사용
IPv4 프로토콜 체계의 연결지향형 데이터 전송 소켓 생성
int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)
IPv4 프로토콜 체계의 비 연결지향형 데이터 전송 소켓 생성
int tcp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)
리눅스 기반 TCP 소켓 예시
// tcp_server.cpp는 hello_server.cpp와 동일
// tcp_client.cpp
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
using namespace std;
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)
{
cout << "Usage : " << argv[0] << " <IP> <port>\n";
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, (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;
}
cout << "Message from server : " << message << endl;
cout << "Function read call count : " << str_len << endl;
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
02-2 : 윈도우 기반에서 이해 및 확인하기
#include <winsock2.h>
SOCKET socket(int af, int type, int protocol);
// 성공시 SOCKET, 실패시 INVALID_SOCKET 반환
반환형인 SOCKET
은 소켓의 핸들 값을 저장하기 위해 정의된 자료형으로 사실상은 정수임
오류발생시 반환하는 INVALID_SOCKET
역시 실제 값은 -1
윈도우 기반 TCP 소켓 예시
// tcp_server_win.cpp는 hello_server_win.cpp와 동일
// hello_client_win.cpp
#include <iostream>
#include <winsock2.h>
using namespace std;
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)
{
cout << "Usage : " << argv[0] << " <IP> <port>\n";
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)
error_handling("connect() error");
while (readLen = recv(hSocket, &message[idx++], 1, 0))
{
if (readLen == -1)
ErrorHandling("read() error!");
strLen += readLen;
}
cout << "Message from server : " << message << endl;
cout << "Function read call count : " << strLen << endl;
closesocket(hSocket);
WSACleanup();
return 0;
}
void ErrorHandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
내용 확인문제
-
프로토콜이란 무엇을 의미하는가? 그리고 데이터의 송수신에 있어서 프로토콜을 정의한다는 것은 어떤 의미가 있는가?
프로토콜이란 컴퓨터 상호간의 대화에 필요한 통신규약이며, 데이터 송수신에서 프로토콜을 정의한다는 것은 데이터를 주고받는 방식을 규정한다는 의미임
-
연결지향형 소켓인 TCP 소켓의 전송 특성 세가지를 나열하여라.
데이터가 전송 도중에 소멸되지 않음
전송 순서대로 데이터가 수신됨
전송되는 데이터의 경계가 존재하지 않음 - 다음 중 비 연결형 소켓의 특성에 해당하는 것을 모두 고르면?
- 전송된 데이터는 손실될 수 있다. (O)
- 데이터의 경계(Boundary)가 존재하지 않는다. (X)
- 가장 빠른 전송을 목표로 한다. (O)
- 한번에 전송할 수 있는 데이터의 크기가 제한되어있지 않다. (X)
- 연결지향형 소켓과 달리 연결이라는 개념이 존재하지 않는다. (O)
- 다음 유형의 데이터 송수신에 적합한 타입의 소켓은 무엇인지 결정하고, 그러한 결정을 하게 된 이유를 설명해보자.
-
- 서태지와 아이들의 실시간 라이브 방송 멀티미디어 데이터
- UDP, 실시간으로 데이터가 갱신되므로 데이터의 일부 손실에 민감하지 않으며 전송속도가 중요시됨
-
- 철수가 압축한 텍스트 파일의 전송
- TCP, 압축파일은 데이터가 손상될경우 압축해제가 불가능함
-
- 인터넷 뱅킹을 이용하는 고객과 은행 사이에서의 데이터 송수신
- TCP, 데이터의 손실에 민감하며 데이터의 전송 순서와 수신 순서가 달라져서는 안됨
-
-
데이터의 경계(Boundary)가 존재하지 않는 소켓은 어떠한 타입의 소켓인가? 그리고 이러한 소켓은 데이터를 수신할 때 무엇을 주의해야하는지 서술해보자.
연결지향형 소켓(TCP)
입출력 함수의 호출 횟수는 데이터의 송수신에 유의미한 영향을 주지 않고, 데이터의 손실이 없어 송신된 데이터와 수신된 데이터가 정확히 같아야 하기 때문에 송수신 데이터를 비교하여 정확한 전송이 이루어졌는지를 확인해야함 tcp_server.c
와tcp_client.c
에서는 서버가 한번의write
함수호출을 통해서 전송한 문자열을 여러 차례의read
함수호출을 통해서 읽어들였다. 그럼 이번에는 서버가 여러차례의write
함수호출을 통해서 전송한 문자열을 클라이언트에서 한번의read
함수호출을 통해서 읽어들이는 형태로 예제를 변경해보자. 단, 이를 위해서 클라이언트는read
함수의 호출 시기를 다소 늦출 필요가 있다. 서버가 데이터를 모두 전송할 때까지 기다려야하기 때문이다. 그럼 이를 위해서 리눅스와 윈도우 양쪽 모두에서 다음 유형의 문장을 이용해서read
또는recv
함수의 호출시기를 늦추기로 하자.for (i = 0; i < 3000; i++) printf("Wait time %d \n", i);
이렇게 CPU에게 불필요한 일을 시켜가면서 실행의 흐름을 지연시키는 것을 가리켜
Busy Waiting
이라 하는데, 이를 적절히 활용하면 우리에게 필요한 만큼 함수의 호출시기를 늦출 수 있다.// q6_server.cpp #include <iostream> #include <fstream> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> using namespace std; void error_handling(char *message); int main(int argc, char *argv[]) { int serv_sock; int clnt_sock; struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; socklen_t clnt_addr_size; string message = "Hello World!"; if(argc != 2) { cout << "Usage : " << argv[0] << " <port>\n"; exit(1); } serv_sock = socket(PF_INET, SOCK_STREAM, 0); if (serv_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 = htonl(INADDR_ANY); serv_addr.sin_port = htons(atoi(argv[1])); if (::bind(serv_sock, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) error_handling("bind() error"); if (::listen(serv_sock, 5) == -1) error_handling("listen() error"); clnt_addr_size = sizeof(clnt_addr); clnt_sock = accept(serv_sock, (sockaddr*)&clnt_addr, &clnt_addr_size); if (clnt_sock == -1) error_handling("accept() error"); int idx = 0, str_len = 0, write_len = 0; while ((write_len = write(clnt_sock, &((message.c_str())[idx++]), 1))) { if (write_len == -1) error_handling("write() error!"); str_len += write_len; if (str_len == message.length()) break; } close(clnt_sock); close(serv_sock); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
// q6_client.cpp #include <iostream> #include <fstream> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> using namespace std; void error_handling(char *message); int main(int argc, char *argv[]) { int sock; struct sockaddr_in serv_addr; char message[30]; int str_len; if(argc != 3) { cout << "Usage : " << argv[0] << " <IP> <port>\n"; 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, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) error_handling("connect() error"); for (int i = 0; i < 3000; i++) cout << "Wait time " << i << endl; str_len = read(sock, message, sizeof(message) - 1); if (str_len == -1) error_handling("read() error"); cout << "Message from server : " << message << endl; close(sock); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }