- Layered Architecture
우선 기본적인 Layered Architecture에 대해 살펴보자.
사실 저번 HTTP 시간에 공부했지만, 다시 한 번 살펴보는 느낌으로...
데이터 통신은 5가지 요소로 이루어져있다.
- Message
통신하려고 하는 데이터 자체를 말한다.
- Sender
당연히 보내는 사람을 말한다.
- Receiver
당연히 받는 사람을 말한다.
- Transmission Medium
Sender에서 Receiver로 갈 때 운반되는 물리적인 경로를 말한다.
- Protocol
데이터 통신을 위해 고려해야 하는 규칙들을 말한다.
이 중에서 Protocol을 살펴보자.
위에서 말한 것처럼 Protocol은 데이터 통신에서 Sender와 Receiver, 그리고 모든 중간 단계에서 통신을 직접적으로 하기 위해 정의한 규칙들을 말한다.
현재 인터넷에서 가장 많이 사용하는 TCP/IP Protocol Suite에 대해 알아보자.
TCP/IP Protocol Suite는 5계층으로 이루어져있다.
Applcation | -> | Layer 5 |
Transport | -> | Layer 4 |
Network | -> | Layer 3 |
Data link | -> | Layer 2 |
Physical | -> | Layer 1 |
Application 계층에서 데이터를 전송하려고 하면, Transport 계층에 해당하는 TCP or UDP로 메시지나 데이터 스트림을 전송한다.
Transport 계층은 대상 데이터를 작은 조각으로 나누고 대상 주소를 포함시켜 패킷으로 만든 후에 다음 계층으로 넘긴다.
Network 계층은 패킷을 IP 데이터그램에 포함한 후 데이터그램 헤더 및 트레일러에 넣고 데이터그램 전송 위치(대상에 직접 또는 게이트웨이에)를 결정한 후 Data link 단계로 넘긴다.
Data link 단계는 충돌과 보낼 시기를 관리하고 Physical 단계로 넘긴다.
Physical 단계는 물리적인 하드웨어를 이용하여 데이터를 전송한다.
Application Layer | |||
DATA | |||
▼ | |||
Transport Layer | |||
TCP 헤더 | DATA | ||
▼ | |||
Network Layer | |||
IP 헤더 | TCP 헤더 | DATA | |
▼ | |||
Data link Layer | |||
이더넷 헤더 | IP 헤더 | TCP 헤더 | DATA |
▼ | |||
Physical Layer |
이렇게 단계를 하나씩 거칠 때마다, 헤더를 하나씩 추가한다.
수신 할 때에도 단계를 하나씩 올리면서 헤더를 하나씩 확인하며 떼어 내면서 수신하게 된다.
이정도만 소개하고 만약 더 필요한 내용이 있다면, HTTP 공부한 내용을 참고하기 바란다.
- 네트워크 프로그래밍
네트워크 프로그래밍은 소켓을 기반으로 프로그래밍을 한다. 그렇기 때문에 소켓 프로그래밍이라고도 한다.
네트워크로 연결된 둘 이상의 컴퓨터 사이에서의 데이터 송수신 프로그램 작성을 의미한다.
소켓은 네트워크 연결의 도구로, 운영체제에 의해 제공이 되는 소프트웨어 장치이다.
프로그래머는 이 소켓만 이용하고, 소켓 덕분에 더 아래 계층에 대한 사용과 이해를 피할 수 있게 한다.
아래 부분은 아직 1주차에 배울 내용은 아니지만, 한 번 실습을 해보는 부분이다.
이해가 안되어도 그냥 넘어가면 된다.
Server 함수
소켓은 전화기라고 생각하면 된다.
하지만, 서버와 클라이언트에 따라 전화를 받는 용도의 소켓이 있으며 전화를 거는 소켓이 있다.
이 소켓은 다른 방법으로 생성을 해야한다.
socket
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
- domain
생성할 소켓이 통신을 하기 위해 사용할 프로토콜
- type
소켓이 데이터를 전송하는 데, 사용하는 전송 타입
- protocol
소켓을 사용하기 위한 프로토콜을 지정
return: File descriptor, -1(error)
시스템 프로그래밍에서 사용했던 File descriptor처럼 생각을 하면 된다.
이 곳에다가 쓰면 데이터가 송신이 되고, 이곳을 읽으면 데이터를 수신하는 것이다.
소켓은 전화기로 생각을 하면 된다고 했었다.
하지만 전화기를 샀다고 바로 전화가 되지는 않을 것이다.
해당 전화기에 번호를 부여해 주어야 한다.
마찬 가지로 소켓에도 주소 정보를 넣어주어야 한다.
주소 정보는 당연히 IP와 PORT 번호로 구성이 된다.
bind
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
- sockfd
socket 함수로 생성한 file descriptor
- myaddr
IP 주소와 PORT 번호를 지정한 sockaddr 구조체
- addrlen
주소 정보를 담은 변수의 길이
return: 0(success), -1(error)
이렇게 번호까지 지정을 해주었다.
그러면 이제 이 전화기를 VOIP처럼 연결 가능상태로 만들어주어야 한다.
자리 비움이 아닌 수신 가능 상태로 설정한다고 생각하면 될 것이다.
특이하게 전화를 받을 수 있는 횟수가 지정이 되는데
그 값을 backlog 인수로 넣어준다.
listen
#include <sys/socket.h>
int listen(int sockfd, int backlog);
- sockfd
socket 함수로 생성한 descriptor
- backlog
수신할 큐의 개수 설정
return: 0(success), -1(error)
이제 VOIP에서 전화를 받아야 한다.
스피커 폰이 아닌 이상, 수화기를 들어야 내용을 수신할 수 있다.
그렇기 때문에 listen으로 연결 가능으로 설정을 해주어도, accept로 수락을 해주어야 내용을 수신할 수 있다.
수락 이후에 데이터의 송수신은 양방향으로 가능하다.
accept
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- sockfd
socket 함수로 생성한 descriptor
- addr
sockaddr 구조체 포인터로, 연결이 성공하면 이 포인터에 클라이언트에 대한 정보를 채워 돌려주게 된다.
- addrlen
sockaddr 크기
return: file descriptor(success), -1(error)
이렇게 4가지를 알아보았고, 단계별로 이해하는 게 제일 쉬울 것이다.
STEP 1 | 소켓을 생성 | socket 함수 |
STEP 2 | IP와 PORT 번호를 할당 | bind 함수 |
STEP 3 | 연결 가능한 상태로 변경 | listen 함수 |
STEP 4 | 연결 요청에 대한 수락 | accept 함수 |
Client 함수
클라이언트는 소켓을 생성하고 연결을 하면 된다.
connect
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
- sockfd
socket 함수로 생성한 descriptor
- serv_addr
연결할 서버의 IP 주소와 PORT 번호들을 담아올 sockaddr 구조체
- addrlen
sockaddr 구조체의 크기
return: 0(success), -1(error)
다시 한 번 말하지만, 1주차에 한 번 해보는 내용으로 당연히 이해가 안 될수도 있다.
client와 server의 코드를 한 번 작성해보도록 하겠다.
실제 학교에서 실습했던 코드를 살짝만 수정해서 보도록 하겠다.
함수들에 들어가는 인자들은 나중에 배우도록 하겠다.
Server 코드
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.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[]="KNU, Han Seungkyu";
if(argc!=2){
printf("Usage : %s port\n", argv[0]);
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); //IP 주소 할당(자기 자신의 IP 주소로)
serv_addr.sin_port=htons(atoi(argv[1])); //PORT 번호 할당
if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1) //bind 함수로 IP, PORT 할당
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, (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(1);
}
Client 코드
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.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]); //IP 주소 할당
serv_addr.sin_port=htons(atoi(argv[2])); // PORT 번호 할당
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);
}
실행을 해보면 비록 스스로와의 통신이긴 하지만 잘 작동하는 것을 볼 수 있다.
컴파일 하는 방법과 실행 방법은 시스템 프로그래밍에서 배웠을 것이다.
'학교 생활 > 네트워크 프로그래밍' 카테고리의 다른 글
네트워크 프로그래밍 3주차 - 2 (0) | 2023.04.03 |
---|---|
네트워크 프로그래밍 3주차 - 1 (0) | 2023.04.03 |
네트워크 프로그래밍 2주차 (0) | 2023.03.20 |