모든 내용은 [윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어] 를 기반으로 제 나름대로 이해하여 정리한 것입니다. 다소 부정확한 내용이 있을수도 있으니 이를 유념하고 봐주세요!
01. TCP/IP 프로토콜 스택을 4개의 계층으로 구분해 보자. 그리고 TCP 소켓이 거치는 계층구조와 UDP 소켓이 거치는 계층구조의 차이점을 설명해보자.
# TCP/IP 프로토콜 스택 : "인터넷 기반의 효율적인 데이터 전송(송수신)"이라는 문제를 계층화시켜서 해결
1) APPLICATION 계층(최상위계층)
2) TCP / UDP 계층
3) IP 계층
4) LINK 계층(최하위계층)
- TCP는 APPLICATION 계층 <=> TCP 계층 <=> IP 계층 <=> LINK 계층
- UDP는 APPLICATION 계층 <=> UDP 계층 <=> IP 계층 <=> LINK 계층
02. TCP/IP 프로토콜 스택 중에서 LINK 계층과 IP 계층이 담당하는 역할이 무엇인지 설명해보자. 그리고 이 둘의 관계도 함께 설명해보자.
# LINK 계층
- 물리적인 영역의 표준화에 대한 결과
- 가장 기본이 되는 영역인 LAN, WAN, MAN 과 같은 네트워크 표준과 관련된 프로토콜을 정의하는 영역
# IP 계층
- 목적지로 데이터를 전송하기 위해서 어떤 경로를 선택할지 해결하는 계층 => 이를 위해 사용하는 프로토콜이 IP
IP 계층은 LINK 계층을 기반으로 구성된 네트워크에서 데이터 전송을 하기 위해 데이터의 경로를 설정한다.
03. TCP/IP 프로토콜 스택을 4개의 계층(또는 7개의 계층)으로 나누는 이유는 무엇인가? 이를 개방형 시스템에 대한 설명과 함께 답해보자.
TCP/IP 프로토콜을 계층화하면,
1) 프로토콜 설계의 용이성이 증대시킬 수 있음
2) 프로토콜 계층별 문제해결을 전문화시킬 수 있음
2) 프로토콜 각 계층별 표준화 작업을 통해 개방형 시스템(여러개의 표준을 근거로 설계된 시스템)으로 발전시킬 수 있으며, 이는 계층별 표준을 따르는 하드웨어 및 소프트웨어 간의 대체가 가능해져 TCP/IP 발전의 가속화를 가져올 수 있음
04. 클라이언트는 connect 함수호출을 통해서 서버로의 연결을 요청한다. 그렇다면 클라이언트는 서버가 어떠한 함수를 호출한 이후부터 connect 함수를 호출할 수 있는가?
서버가 listen 함수를 호출하여 서버소켓(리스닝 소켓)이 되고, 연결요청 대기큐가 생성되면 클라이언트가 connect 함수를 호출 할 수 있다.
05. 연결요청 대기 큐라는 것이 생성되는 순간은 언제이며, 이것이 어떠한 역할을 하는지 설명해보자. 그리고 accept 함수와의 관계도 함께 설명해보자.
listen 함수를 호출하면 연결요청 대기큐가 생성되며, 연결요청 대기큐는 클라이언트의 연결요청을 순서대로 저장한다. accept 함수가 호출되면, 서버는 연결요청 대기큐에 저장된 정보를 참조하여 그 순서대로 클라이언트 요청을 수락한다.
06. 클라이언트 프로그램에서 소켓에 주소정보를 할당하는 bind 함수호출이 불필요한 이유는 무엇인가? 그리고 bind 함수를 호출하지 않았을 경우, 언제 어떠한 방식으로 IP주소와 PORT번호가 할당되는가?
클라이언트는 서버에 "연결을 요청"하는 입장이기에, 서버의 주소정보가 보다 중요하다. 그래서 서버처럼 bind 함수 호출을 통해서 명시적으로 주소정보를 할당할 필요는 없다. 그러나 서버와의 통신을 위해서 클라이언트 또한 자신의 주소정보가 할당되어야 하는 것은 마찬가지이다. 클라이언트가 서버로의 연결요청을 시도하는 connect 함수를 호출하면 IP는 컴퓨터(호스트)의 IP로, PORT번호는 임의로 하여 주소정보가 할당된다.
07. 앞서 선보였던 리눅스, 윈도우 기반 서버, 클라이언트 예제 프로그램을 iterative 모델로 변경하고, 제대로 변경이 되었는지 클라이언트와 함께 테스트해보자.
윈도우 기반 구현은 제외하고 구현해보았습니다.
# 리눅스 기반 서버
- Iterative 모델을 구현하기 위해 반복문을 활용했음을 유의해서 보자!
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
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;
char message[]="Hello World!";
if(argc!=2) // 실행파일의 경로/PORT번호 를 입력 받아야 함
{
printf("Usage : %s <port>\n",argv[0]);
exit(EXIT_FAILURE);
}
serv_sock=socket(PF_INET,SOCK_STREAM,0); // TCP 소켓 생성
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,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
error_handling("bind() error");
// 명시적으로 서버(리스닝)소켓이 되며, 이때 연결요청 대기큐가 생성되고
// 이 함수 호출 이후부터 클라이언트의 연결요청이 가능해짐
if(listen(serv_sock,5)==-1) // 대기큐의 크기는 5
error_handling("listen() error");
while(true)
{
clnt_addr_size=sizeof(clnt_addr);
// 연결요청 대기큐에 있는 클라이언트의 연결요청들을 순서대로 수락
// 이 때, 서버소켓과는 별도로 클라이언트 소켓과의 데이터 송수신을 위한
// 새로운 소켓이 따로 생성됨
clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr, &clnt_addr_size);
if(clnt_sock==-1)
error_handling("accept() error");
write(clnt_sock,message,sizeof(message)); // 널문자를 포함하여 전송함을 유의!
close(clnt_sock); // 클라이언트에게 문자열을 전송하고 연결을 끊음
}
close(serv_sock);
return 0;
}
void error_handling(char *message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(EXIT_FAILURE);
}
# 리눅스 기반 클라이언트
hello_server_iter.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/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;
if(argc!=3) // 실행파일의 경로/IP/PORT번호 를 입력받아야 함
{
printf("Usage : %s <IP> <port>\n",argv[0]);
exit(EXIT_FAILURE);
}
sock=socket(PF_INET,SOCK_STREAM,0); // TCP 소켓 생성
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!");
// 서버로부터 데이터를 수신
// 서버가 널문자를 포함하여 전송했음을 유의
str_len=read(sock,message,sizeof(message));
if(str_len==-1)
error_handling("read() error!");
// 수신한 데이터를 출력
printf("Message from server : %s \n",message);
// 연결 종료
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(EXIT_FAILURE);
}
[출처] : 윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어
'Programming > 열혈 TCP, IP 소켓 프로그래밍(저자 윤성우)' 카테고리의 다른 글
Ch 05. 내용 확인문제 (1) | 2020.08.06 |
---|---|
Ch 05. TCP 기반 서버/클라이언트 2 (0) | 2020.08.06 |
Ch 04. TCP 기반 서버/클라이언트 1 (0) | 2020.08.06 |
Ch 03. 내용 확인문제 (0) | 2020.08.05 |
Ch 03. 주소체계와 데이터 정렬 (0) | 2020.08.05 |