Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

메모장

TCP/IP 소켓 프로그래밍 복습3 본문

TCP&IP 복습

TCP/IP 소켓 프로그래밍 복습3

Captic 2020. 10. 29. 17:53

참고문헌 - 윤성우 <윤성우의 열혈 TCP/IP 소켓 프로그래밍>

♨ 개인적 해석이 들어간 글임으로, 인지하지 못한 오류가 있을 수 있습니다 ♨

 

 

○ TCP기반 서버/클라이언트

TCP(Transmission Control Protocol)

  • 네트워크 전송과정의 컨트롤
  • 연결지향형 -> 스트림 기반 소켓

▣ TCP/IP 프로토콜 스택 (주로 OSI 7 계층과 연계해서 공부한다)

[ APPLICATION 계층 ]

     ↕            ↕

[TCP 계층] [UDP 계층]

     ↕            ↕

       [ IP 계층 ]

            ↕

     [ LINK 계층 ]

  • 인터넷 기반의 효율적인 데이터 전송을 게층화하여 해결
  • 각 계층을 담당하는 것은 운영체제 같은 소프트웨어나 NIC와 같은 물리적인 장치이기도 하다
  • TCP 소켓을 생성하여 데이터를 송수신 할 경우 APPLICATION - TCP - IP - LINK
  • UDP 소켓을 생성하여 데이터를 송수신 할 경우 APPLICATION - UDP - IP - LINK

※ 개방형 시스템 (Open System)

  • 여러 개의 표준을 근거로 설계된 시스템
  • TCP/IP 프로토콜 스택도 개방형 시스템이다
  • → IP 계층을 담당하는 '라우터'는 제조사가 다양하지만, 그 제조사들이 IP계층의 표준에 맞추어 라우터를 제작하기 때문에 제조사 구분 없이 교체, 작동할 수 있다

▣ LINK 계층

  • 물리적인 영역의 표준화
  • LAN, WAN, MAN과 같은 네트워크 표준과 관련된 포토토콜을 정의하는 영역
  • 둘 이상의 호스트가 인터넷을 통해 데이터를 주고 받기 위해 필요한 물리적인 연결
  • LINK 계층에서 물리적인 연결이 완료 -> 데이터를 보낼 기본준비 완료

▣ IP 계층

  • 데이터의 전송을 위해 경로를 선택 (목적지로 데이터를 전송하기 위해서 중간에 어떤 경로를 거쳐갈 것인가?)
  • → IP 계층 + 이 계층에서 사용하는 프로토콜이 IP(Internet Protocol)이다
  • IP 자체는 비 연결지향적이며 신뢰할 수 없는 프로토콜
    • 데이터 전송 도중에 경로 상에 문제가 발생하면 다른 경로를 선택해 주는데, 이 과정에서 데이터가 손실 되거나 오류가 발생하기도 한다 (이를 해결해주지도 않는다)
  • → 데이터를 전송할 때마다 거쳐야 할 경로를 선택해 주지만, 그 경로는 일정하지 않다

∴ IP = 오류 발생에 대한 대비가 되어있지 않은 프로토콜

▣ TCP/UDP 계층

  • IP계층에서 알려준 경로정보를 바탕으로 데이터의 실제 송수신을 담당 : 전송(Transform) 계층
  • TCP는 신뢰성 있는 데이터의 전송을 담당 → 그러나 TCP가 데이터를 보낼 때 기반이 되는 프로토콜이 IP?

? : 이 둘 (TCP와 IP)의 관계는?

→ IP는 오로지 하나의 데이터 패킷(데이터 전송의 기본 단위)이 전송되는 과정에만 중심을 두고 설계

예) IP만을 이용해서 데이터를 전송한다면,

먼저 전송한 A 패킷보다 뒤에 전송한 B 패킷이 먼저 도달할 수 있다

또는 순차적으로 전송한 A, B, C 패킷 중 A와 C패킷만 전송될 수 있으며, C 패킷은 손상된 상태로 전송될 수도 있다

여기에 TCP 프로토콜이 추가되어 데이터를 송수신하면, 데이터를 주고 받는 과정에서 서로 데이터의 주고 받음을 확인한다 (확인절차를 걸친다)

예) 호스트 A가 호스트 B에게 ㄱ패킷을 전송하면, B는 ㄱ패킷을 수신상태를 A에게 알려준다

이때 패킷을 온전히 받지 않았다는 정보를 A가 받으면 다시 ㄱ패킷을 B에게 보낸다

∴ TCP/UDP는 " 호스트 : 호스트 "의 데이터 송수신 방식을 약속한 것

TCP는 신뢰성이 없는 IP에 확인절차를 통해 신뢰성을 부여한 프로토콜

 

▣ APPLICATION 계층

  • 지금까지 내용(LINK ~ TCP/UDP)은 소켓을 생성하면 데이터 송수신과정에서 자동으로 처리되는 것들
  • 데이터의 전송결로를 확인하는 과정 / 데이터 수신에 대한 응답의 과정 ∈ 소켓
  • 데이터를 송수신할 수 있는 소켓을 가지고 프로그램을 작성할 때,

프로그램의 성격에 따라 클라이언트와 서버간의 데이터 송수신에 대한 약속이 정해진다

→ 이것이 APPLICATION 프로토콜

▣ TCP 서버에서의 기본적인 함수 호출 순서

  1. socket() : 소켓 생성
  2. bind() : 소켓 주소할당
  3. listen() : 연결요청 대기상태
  4. accept() : 연결허용
  5. read() / write() : 데이터 수/송신
  6. close() : 연결종료

▣ 연결 요청 대기 상태로의 진입

  • socket 함수를 통해 소켓을 생성하고, bind 함수로 소켓에 주소까지 할당했다면, listen 함수호출을 통해서 '연결요청 대기상태'로 만든다
  • 서버에서 listen 함수가 호출 되어야 클라이언트에서 connect 함수로 연결요청을 할 수 있다

(listen 이전에 connect 함수를 호출 시 에러)

int listen(int sock, int backlog);

① sock : 연결요청 대기상테에 두고자 하는 소켓의 파일 디스크립터 전달, 이 함수의 인자로 전달된 디스크립터의 소켓이 서버 소켓(리스닝 소켓)이 된다

② backlog : 연결요청 대기 큐(Queue)의 크기정보 전달

예) 5가 전달되면 큐의 크기가 5가되어 클라이언트의 연결요청을 5개까지 대기시킬 수 있다

  • 연결요청 대기상태 : 클라이언트가 연결요청을 했을 때 연결이 수락될 때까지 연결요청 자체를 대기시킬 수 있는 상태에 있다는 것을 의미
  • 클라이언트의 연결요청도 인터넷을 통해 들어오는 일종의 데이터 전송

∴ 그 요청을 받을 소켓이 필요 -> 그 역할(클라이언트의 연결요청을 받는)을 하는 것이 서버 소켓

  • listen 함수가 호출되면 서버소켓이 만들어지고 listen함수의 두 번째 인자로 전다로디는 정수의 크기에 해당하는 클라이언트 '연결요청 대기 큐'가 만들어진다
  • 서버 소켓과 연결요청 개기 큐가 준비되어, 클라이언트의 연결요청을 받아 들일 수 잇는 상태가 '연결요청 대기상태'
  • listen 함수의 두 번째 인자 값은, 웹서버와 같이 잦은 연결요청을 받는 서버의 경우, 최소 15 이상을 전달해야 한다

▣ 클라이언트의 연결 요청 수락

  • listen 함수 호출 이후에 클라이언트의 연결 요청이 들어왔다면, 들어온 순서대로 연결 요청을 수락해야 한다 → 연결 요청을 수락한다 : 클라이언트와 데이터를 주고받을 수 있는 상태가 되었다
  • 클라이언트와 데이터를 주고 받기 위한 소켓이 하나 더 필요하다 → 서버 소켓은 클라이언트의 연결 요청만을 위한 소켓이기 때문에, 클라이언트가 보내는 '데이터'를 받기 위한 소켓이 필요한 것
  • accept 함수를 호출하면 호출결과로 소켓이 만들어지고, 만들어진 소켓은 연결요청을 한 클라이언트 소켓과 자동으로 연결된다

SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen);

// 성공 시 생성된 소켓 핸들, 실패 시 -1 반환

① s : 서버 소켓 핸들

② addr : 연결요청 한 클라이언트의 주소정보를 담은 변수의 주소 값 전달,

함수호출이 완료되면 인자로 전달된 주소의 변수에는 클라이언트의 주소정보가 채워진다

③ addrlen : 두 번째 매개변수 addr에 전달된 주소의 변수 크기를 바이트 단위로 전달, 단 크기 정보를 변수에 저장한 다음에 변수의 주소 값을 전달한다

그리고 함수 호출이 완료되면 크기 정보로 채워져 있던 변수에는 클라이언트의 주소 정보 길이가 바이트 단위로 계산되어 채워진다

▣ TCP 클라이언트의 기본적인 함수 호출 순서

  1. socket() : 소켓 생성
  2. connect() : 연결 요청
  3. read() / write() : 데이터 송수신
  4. close() : 연결 종료

int connect(SOCKET s, const struct sockaddr* name, int namelen);

// 성공 시 생성된 소켓 핸들, 실패 시 -1 반환

① s : 클라이언트 소켓 핸들

② name : 연결요청 할 서버의 주소정보를 담은 변수의 주소 값 전달

③ addrlen : 두 번째 매개변수 servaddr에 전달된 주소의 변수 크기를 바이트 단위로 전달

  • connect 함수 호출 시 다음 둘 중 한가지 상황이 되어야 함수가 반환된다(함수 호출이 완료된다)
    1. 서버에 의해 연결요청 대기 큐에 등록된다
    2. 네트워크 단절 등 오류상황이 발생해서 연결요청이 중단된다

▣ 에코 서버 / 에코 클라이언트

  • 에코 서버(Echo Server) : 클라이언트가 전송하는 문자열 데이터를 그대로 재전송하는 서버(echo, 메아리쓰)

▣ Iterative 서버의 구현

  • 계속해서 들어오는 클라이언트의 연결요청을 수락하기 위해서, 서버쪽에서 accept함수를 반복 호출한다
    1. socket()
    2. bind()
    3. listen()
    4. accept()
    5. read() / write()
    6. close(client)
    7. back to accept()(4번) or close(server) → 6번의 close함수의 대상은 서버 소켓이 아니라 accept 함수의 호출 과정에서 생성된 소켓을 대상으로 한다
  • 이후 프로세스와 쓰레드를 배워야 한 번에 둘 이상의 클라이언트에게 서비스를 제공하는 서버를 만들 수 있다

▣ Iterative 에코 서버, 에코 클라이언트

  • 위와 같은 형태로 작동하는 서버를 Iterative 서버라 한다
  • Iterative 형태로 동작하는 에코 서버/클라이언트 프로그램의 기본 동작 방식
  1. 서버는 한 순간에 하나의 클라이언트와 연결되어 에코 서비스를 제공한다
  2. 서버는 총 다섯 개의 클라이언트에게 순차적으로 서비스를 제공하고 종료한다
  3. 클라이언트는 프로그램 사용자로부터 문자열 데이터를 입력 받아서 서버에 전송한다
  4. 서버는 전송 받은 문자열 데이터를 클라이언트에게 재전송한다 (에코시킨다)
  5. 서버와 클라이언트간의 문자열 에코는 클라이언트가 Q(또는 q)를 입력할 때 가지 계속한다

▣ UDP 기반의 데이터 입출력 함수

#include <sys/socket.h>

int sendto (SOCKET s, const char* buf, int len, int flags, const struct sockaddr* to, int tolen);

// 성공 시 전송된 바이트 수, 실패 시 -1 반환

① s : 데이터 전송에 사용될 UDP 소켓 핸들을 인자로 전달

② buf : 전송할 데이터를 저장하고 잇는 버퍼의 주소 값 전달

③ len : 전송할 데이터 크기를 바이트 단위로 전달

④ flags : 옵션 지정에 사용되는 매개변수, 지정할 옵션이 없다면 0 전달

⑤ to : 목적지 주소정보를 담고 있는 sockaddr 구조체 변수의 주소 값 전달

⑥ tolen : 매개변수 to로 전달된 주소 값의 구조체 변수 크기 전달 → ⑤이 CP 기반의 출력함수와 비교되는 점 = 목적지 주소정보를 요구하고 있다는 것

 

int recvfrom (SOCKET s, char* buf, int len, int flags, struct sockaddr* from, int* fromlen); // 성공 시 수신한 바이트 수, 실패 시 -1 반환

① s : 데이터 수신에 사용될 UDP 소켓 핸들을 인자로 전달

② buf : 데이터 수신에 사용될 버퍼의 주소 값 전달

③ len : 수신할 최대 바이트 수 전달, 때문에 buf 매개변수가 가리키는 버퍼의 크기를 넘을 수 없다

④ flags : 옵션 지정에 사용되는 매개변수, 지정할 옵션이 없다면 0 전달

⑤ from : 발신지 주소정보를 담고 있는 sockaddr 구조체 변수의 주소 값 전달

⑥ fromlen : 매개변수 from으로 전달된 주소에 해당하는 구조체 변수의 크기정보를 담고 있는 변수의 주소값 전달

 

 

▣ 윈도우 기반의 UDP 에코 클라이언트 / 에코 서버

예) 서버

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>

#define BUF_SIZE 30

void ErrorHandling(char* Message);

int main(int argc, char* argv[])
{
WSADATA wsaData;
SOCKET servSock;
char message[BUF_SIZE];
int strLen;
int clntAdrSz;
SOCKADDR_IN servAdr, clntAdr;

if (argc != 2)
{
printf("Usage : %s <port> \n", argv[0]);
exit(1);
}

if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");

servSock = socket(PF_INET, SOCK_DGRAM, 0);

if (servSock == INVALID_SOCKET)
ErrorHandling("UDP socket creation error");

memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = AF_INET;
servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
servAdr.sin_port = htons(atoi(argv[1]));

if (bind(servSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
ErrorHandling("bind() error");

while (1)
{
clntAdrSz = sizeof(clntAdr);
strLen = recvfrom(servSock, message, BUF_SIZE, 0, (SOCKADDR*)&clntAdr, &clntAdrSz);
sendto(servSock, message, strLen, 0, (SOCKADDR*)&clntAdr, sizeof(clntAdr));
}

closesocket(servSock);
WSACleanup();
return 0;
}

void ErrorHandling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}

 

예) 클라이언트

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>

#define BUF_SIZE 30

void ErrorHandling(char* Message);

int main(int argc, char* argv[])
{
WSADATA wsaData;
SOCKET sock;
char message[BUF_SIZE];
int strLen;
SOCKADDR_IN servAdr;

if (argc != 3)
{
printf("Usage : %s <port> \n", argv[0]);
exit(1);
}

if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)// 윈도우 소켓 초기화 (윈속 버전, WSADATA 구조체 포인터)
ErrorHandling("WSAStartup() error!");

sock = socket(PF_INET, SOCK_DGRAM, 0);

if (sock == INVALID_SOCKET)
ErrorHandling("socket() error");

memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = AF_INET;
servAdr.sin_addr.s_addr = inet_addr(argv[1]);// 한 컴퓨터가 서버와 클라이언트 역할을 동시에 수행할 때 INDADR_ANY 사용 가능?
servAdr.sin_port = htons(atoi(argv[2]));// PORT번호는 실행시 입력

connect(sock, (SOCKADDR*)&servAdr, sizeof(servAdr));

while (1)
{
fputs("Insert message(q to quit ) : ", stdout);
fgets(message, sizeof(message), stdin);

if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
break;

send(sock, message, strlen(message), 0);

strLen = recv(sock, message, sizeof(message) - 1, 0);

message[strLen] = 0;
printf("Message from server : %s", message);
}

closesocket(sock);
WSACleanup();

return 0;
}

void ErrorHandling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}

'TCP&IP 복습' 카테고리의 다른 글

TCP/IP 소켓 프로그래밍 복습4  (0) 2020.10.30
TCP/IP 소켓 프로그래밍 복습2  (0) 2020.10.28
TCP/IP 소켓 프로그래밍 복습 1  (0) 2020.10.26