윤성우의 열혈 TCP/IP Ch.4

KSH·2022년 9월 17일
0

열혈강의 TCP/IP

목록 보기
4/4

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


1. TCP와 UDP에 대한 이해

데이터 전송과정의 컨트롤의 공부


1.1 TCP/IP 프로토콜 스택

				↗↙	 TCP계층		↘↖
Application 계층 						IP계층	<->	LINK 계층
				↘↖ 	UDP 계층	↗↙

여기서 TCP 계층만 거쳐가면 TCP 프로토콜
UDP 계층만 거쳐가면 UDP 프로토콜이된다

1.2 TCP/IP 프로토콜의 탄생 배경
인터넷을 이용한 효율적인 데이터의 송수신을 해결하기 위해 고안된 개념
이를 위해 프로토콜과 계층구조가 만들어지게 되었다.

1.3 LINK 계층(OSI 7 Layer)

  • 물리적인 영역의 표준화에 대한 결과
  • LAN, WAN, MAN과 같은 네트워크 표준과 관려된 프로토콜을 정의하는 영역

1.4 IP 계층

  • LINK 계층에서 물리적인 연결이 형성되었다. -> 데이터를 전송할 준비가 완료되었다.
  • 여기서는 경로의 선택을 담당한다.
  • 경로의 선택을 위해 IP 프로토콜을 사용한다. 단 IP는 신뢰할 수 없는 프로토콜이므로 오류 발생에 대한 대비가 되지 않았다.

1.5 TCP/IP 계층(Transport Layer, 전송계층)

  • IP 계층에서 알려준 경로정보를 바탕으로 데이터의 실제 송수신을 담당한다.
  • TCP의 역할 : 데이터를 주고 받는 과정에서 주고 받음을 확인한다
  • UDP의 역할 : 확인하지 않고 밀어낸다.


1.6 APPLICATION 계층

  • 데이터 송수신과정에서 자동으로 처리되는 내용이다.
  • 소켓을 이용한 통신에서 프로그램의 성격에 따라 서버간의 데이터 송수신에 대한 규칙들이 정해진다 이런 내용르 통틀어 APPLICATION 계층이라 한다.

2. TCP기반 서버, 클라이언트 구현


2.1 TCP기반 서버, 클라이언트 구현 - TCP 서버의 함수호출 순서
socket()		소켓 생성
	↓
bind()			소켓 주소 할당
	↓
listen()		연결 요청 대기상태
	↓
accept()		연결 허용
	↓
read()/write() 	데이터 송수신
	↓
close()			연결 종료

2.2 연결 요청 대기상태로의 진입
1. listen() 함수

  • 헤더
#include<sys/socket.h>
  • 함수 원형
int listen(int sock, int backlog);

-파라미터

int sock	: 	연결요청 대기상태에 두고자 하는 소켓의 파일 디스크립터 전달, 
				이 함수의 인자로 전달된 디스크립터의 소켓이 서버 소켓(리스팅 소켓)이 된다
int backlog	:	연결 요청 대기 큐(Queue)의 크기정보 전달,
				5가 전달되면 큐의 크기가 5가 되어 클라이언트의 연결 요청을 5개까지 대기시킬 수 있다.

bind()함수를 통해 소켓에 주소까지 할당했다면
listen() 함수를 통해서 연결 대기상태로 넘긴다.

2.3 클라이언트의 연결요청 수락
listen함수 호출 이후 연결 요청이 들어오면, 들어온 순서대로 연결 요청을 수락해야 한다.
1. accept() 함수

  • 헤더
#include<sys/socket.h>
  • 함수 원형
int accept(int sock, struct sockaddr* addr, socklen_t* addrlen);
  • 파라미터
int 			 sock	: 서버 소켓의 파일 디스크립터 전달
struct sockaddr* addr	: 연결 요청 한 클라이언트의 주소 정보를 담을 변수의 주소값 전달.
						함수 호출이 완료되면 인자로 전달된 주소의 변수에는 클라이언트의 주소 정보가 채워진다.
socklen_t*		 addrlen: 두번째 매개변수 addr에 전달된 주소의 변수 크기를 바이트 단위로 전달. 
						단 크기 정보를 변수에 저장한 다음에 변수의 주소 값을 전달한다. 
						그리고 함수호출이 완료되면 크기정보로 채워져 있던 변수에는 클라이언트의 주소정보 길이가
                        바이트 단위로 계산되어 채워진다

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

  1. connect() 함수
  • 헤더
#include<sys/socket.h>
  • 함수 원형
int connect(int sock, struct sockaddr* servaddr, socklen_t addrlen);
  • 파라미터
int 				sock : 클라이언트 소켓의 파일 디스크립터 전달
struct sockaddr* servaddr: 연결요청 할 서버의 주소정보를 담은 변수의 주소 값 전달
socklen_t		 addrlen : 두번째 매개변수 servaddr에 전달된 주소의 변수 크기를 바이트 단위로 전달

  1. TCP 클라이언트 함수 호출 순서
socket()		소켓 생성
	↓
connect()		연결요청
	↓
read()/write() 	데이터 송수신
	↓
close()			연결 종료

서버와 다르단 점에서 주의!

  1. 클라이언트 소켓의 주소정보는 어디에?
    클라이언트 프로그램의 구현 순서에는 Bind과정이 없다.
    서버로의 연결은 connect 함수의 호출 하나 뿐이었다.

소켓의 할당 과정
connect 함수가 호출될 때
운영체제(커널)에서
IP는 컴퓨터에 할당된 IP로 PORT는 임의 선택

3. iterative 기반의 서버, 클라이언트 구현


여태까지는 전부 일회용 서버였으나 여기부터는 다회용 서버가 된다.

3.1 iterative 서버의 구현

  1. 실행 과정
socket()
	↓
bind()
	↓
listen()	←
	↓
accept()		↑
	↓
read()/write()	↑
	↓
close(client)	↑
	↓
close(client)	↑
	↓		→	
close(server)

3.2 iterative 에코서버, 에코 클라이언트

  1. 작동 방식 정리
  • 서버는 한번에 하나의 클라이언트와 연결되어 에코 서비스를 제공한다.
  • 서버는 총 다섯개의 클라이언트에게 순차적으로 서비스를 제공하고 종료한다.
  • 클라이언트는 프로그램 사용자로부터 문자열 데이터를 입력받아서 서버에 전송한다.
  • 서버는 전송 받은 문자열 데이터를 클라이언트에게 재전송한다. -> 에코시킨다
  • 서버와 클라이언트 간의 문자열 에코는 클라이언트가 Q를 입력할 때까지 계속한다.
  1. 구현 코드
  • 서버
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    char message[BUF_SIZE];
    int str_len, i;

    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;

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

    //소켓 초기화 복습
    // PF_INET: Protocol Family INET,
    // SOCK_STREAM: 스트림소켓 사용, TCP 프로토콜을 사용하겠다는 의미
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == 1)
    {
        error_handling("socket() error");
    }
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr == htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    // bind 실시, 여기서 소켓에 포트를 지정한다. 실패시 에러메시지를 반환 후 종료.
    if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
        error_handling("bind() error");

    // listen 실시, 클라이언트의 접속을 기다리며, 실패서 에러메시지를 반환 후 종료
    if (listen(serv_sock, 5) == -1)
        error_handling("listne() error");

    clnt_adr_sz = sizeof(clnt_adr);

    for (i = 0; i < 5; i++)
    {
        clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);
        if (clnt_sock == -1)
            error_handling("accept() error");
        else
            printf("Connected client %d \n", i + 1);

        while ((str_len = read(clnt_sock, message, BUF_SIZE)) != 0)
            write(clnt_sock, message, str_len);
    }
    close(clnt_sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
  • 클라이언트
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sock;
    char message[BUF_SIZE];
    int str_len;
    struct sockaddr_in serv_adr;

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

    //소켓 초기화, TCP 소켓으로 선언한다.
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == -1)
        error_handling("socket() error");

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    //여기서, 바인딩된 포트와 연결한다. TCP 세션에서 클라이언트의 함수 connect
    if (connect(&serv_adr, 0, sizeof(serv_adr)) == -1)
        error_handling("connect() error");
    else
        puts("Connected.....");

    while (1)
    {
        fputs("Input message(Q to quit): ", stdout);
        fgets(message, BUF_SIZE, stdin);

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

        write(sock, message, strlne(message));
        str_len = read(sock, message, BUF_SIZE - 1);
        message[str_len] = 0;
        printf("Message from server: %s", message);
    }
    close(sock);
    return 0;
}

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

4. 윈도우 기반으로 구현하기


4.1 윈도우 기반 에코 서버
1. 리눅스 기반 예제를 윈도우 기반으로 변경하는 핵심내용

  • WSAStartup(), WSACleanup() 함수 호출을 통한 소켓 라이브러리의 초기화와 해제
  • 자료형과 변수의 이름을 윈도우 스타일로 변경하기
  • 데이터 송수신을 위해서 read, write 함수 대신 recv, send 함수 호출하기
  • 소켓의 종료를 위해서 close 대신 closesocket함수 호출하기
  1. 구현 코드
  • 서버
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sock;
    char message[BUF_SIZE];
    int str_len;
    struct sockaddr_in serv_adr;

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

    //소켓 초기화, TCP 소켓으로 선언한다.
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == -1)
        error_handling("socket() error");

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    //여기서, 바인딩된 포트와 연결한다. TCP 세션에서 클라이언트의 함수 connect
    if (connect(&serv_adr, 0, sizeof(serv_adr)) == -1)
        error_handling("connect() error");
    else
        puts("Connected.....");

    while (1)
    {
        fputs("Input message(Q to quit): ", stdout);
        fgets(message, BUF_SIZE, stdin);

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

        write(sock, message, strlne(message));
        str_len = read(sock, message, BUF_SIZE - 1);
        message[str_len] = 0;
        printf("Message from server: %s", message);
    }
    close(sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
  • 클라이언트
#pragma comment(lib, "ws2_32.lib")
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<winsock2.h>

#define BUF_SIZE 1024;
void ErrorHandling(char* message);

int main(int argc) {
	//WSADATA 객체를 선언한다.
	WSADATA wsaData;
	//SOCKET 객체를 선언한다.
	SOCKET hSocket;
	char message[BUF_SIZE];

	int strLen;
	SOCKADDR_IN servAdr;

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

	//WSAStartup()을 이용해 
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error");

	//socket을 선언한다. protocol family는 IP, 연결방식은 TCP
	hSocket = socket(PF_INET, SOCK_STREAM, 0);

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

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

	if (connect(hSocket, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
		ErrorHandling("connect() error");
	else
		puts("Connect....");

	while (1) {
		fputs("Input message(Q to quit): ", stdout);
		fgets(message, BUF_SIZE, stdin);

		if (!strcmp(message, "q\n") || strcmp(message, "Q\n"))
			break;
		 
		send(hSocket, message, strlen(message), 0);
		strLen = recv(hSocket, message, BUF_SIZE - 1, 0);
		message[strLen] = 0;
		printf("Message from server : %s", message);
	}
	closesocket(hSocket);
	WSACleanup();
	return 0;
}

void ErrorHandling(char* message) {
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
profile
대충 개발자 비슷한거

0개의 댓글