[TCP/IP Socket]Chapter 05 - TCP 기반 서버/클라이언트 2

Lee Jeong Min·2021년 2월 4일
0

네트워크

목록 보기
5/17
post-thumbnail

05-1 에코 클라이언트의 완벽 구현!

전 에코 클라이언트의 문제점

전 에코 클라이언트의 소스코드에서 받아오는 부분의 입출력 문장은 다음과 같다.

wrtie(sock, messag, strlen(message));
str_len = read(sock, message, BUF_SIZE -1);

이 부분에서 문제가 있다고 볼 수 있는데 이 문장의 의도는 read 함수호출을 통해서 자신이 전송한 문자열 데이터를 한방에 수신하기를 원하고 있다는 것이다. 이를 이치에 맞게 조정하려면 문자열 데이터가 전송되었을 때 이를 모두 읽어서 출력되도록 바꾸면 된다.

echo_client2.c

#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, recv_len, recv_cnt;
    struct sockaddr_in serv_adr;

    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_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]));

    if(connect(sock, (struct sockaddr*)&serv_adr, 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;

        str_len = write(sock, message, strlen(message));

        recv_len = 0;
        while(recv_len < str_len)
        {
            recv_cnt = read(sock, &message[recv_len], BUF_SIZE-1);
            if(recv_cnt == -1)
                error_handling("read() error!");
            recv_len += recv_cnt;
        }
        message[recv_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);
}

입출력 부분에서 while문을 삽입하며 문자열을 받은 길이가 자신이 보낸 문자열 만큼 길어졌을 때까지 반복을 진행하여 문자열 데이터가 모두 받았을 때, 문자열을 출력하도록 만들고 있다.

어플리케이션 프로토콜의 정의 --> 계산기 서버, 클라이언트

  • 클라이언트는 서버에 접속하자마자 피연산자의 개수정보를 1바이트 정수형태로 전달한다.
  • 클라이언트가 서버에 전달하는 정수 하나는 4바이트로 표현된다.
  • 정수를 전달한 다음에는 연산의 종류를 전달한다. 연산정보는 1바이트로 전달한다.
  • 문자 +, -, * 중 하나를 선택해서 전달한다.
  • 서버는 연산결과를 4바이트 정수의 형태로 클라이언트에게 전달한다.
  • 연산결과를 얻은 클라이언트는 서버와의 연결을 종료한다.

op_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define BUF_SIZE 1024
#define RLT_SIZE 4
#define OPSZ 4

void error_handling(char* message);

int main(int argc, char* argv[])
{
    int sock;
    struct sockaddr_in serv_adr;
    char opmsg[BUF_SIZE];
    int result, opnd_cnt, i;

    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_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(argv[2]);

    if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
        error_handling("conect error!");
    else
        printf("Connected.....................");

    fputs("Operand count: ", stdout);
    scanf("%d", &opnd_cnt);

    opmsg[0] = (char)opnd_cnt;

    for(i = 0; i<opnd_cnt; i++)
    {
        printf("Operand %d: ", i+1);
        scanf("%d", (int*)&opmsg[i*OPSZ+1]);
    }
    
    fgetc(stdin);
    fputs("Operator: ", stdout);
    scnaf("%c", &opmsg[opnd_cnt*OPSZ+1]);
    write(sock, opmsg, opnd_cnt*OPSZ+2);
    read(sock, &result, RLT_SIZE);

    printf("Operation result: %d", result);

    close(sock);
    return 0;
}

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

op_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
#define OPSZ 4

void error_handling(char* message);
int calculate(int opnum, int opnds[], char oprator);

int main(int argc, char* argv)
{
    int serv_sock, clnt_sock;
    char opinfo[BUF_SIZE];
    int result, opnd_cnt, i;
    int recv_cnt, recv_len;

    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;

    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_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(argv[1]);

    if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
        error_handling("bind() error!");

    if(listen(serv_sock, 5) == -1)
        error_handling("listen() error!");

    clnt_adr_sz = sizeof(clnt_adr);

    for(int i = 0; i<5; i++)
    {
        opnd_cnt = 0;
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
        read(clnt_sock, &opnd_cnt, 1);

        recv_len = 0;
        while((opnd_cnt*OPSZ+1)>recv_len)
        {
            recv_cnt = read(clnt_sock, &opinfo[recv_len], BUF_SIZE-1);
            recv_len += recv_cnt;
        }
        result= calculate(opnd_cnt, (int*)opinfo, opinfo[recv_len-1]);
        wrtie(clnt_sock, (char*)&result, sizeof(result));
        close(clnt_sock);
    }

    close(serv_sock);
    return 0;
}

int calculate(int opnum, int opnds[], char op)
{
    int result = opnds[0], i;
    switch (op)
        {
        case '+':
            for(int i = 1; i<opnum; i++)
                result += opnds[i];
            break;
        
        case '-':
            for(int i = 1; i<opnum; i++)
                result -= opnds[i];
            break;

        case '*':
            for(int i = 1; i<opnum; i++)
                result *= opnds[i];
            break;

        }
        return result;
}

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

05-2 TCP의 이론적인 이야기!

TCP 소켓에 존재하는 입출력 버퍼

그림에서 보는 것과 같이, write 함수가 호출되면 출력버퍼라는 곳에 데이터가 전달되고, 상황에 맞게 데이터를 입력버퍼로 전송한다. 입출력 버퍼의 특성은 다음과 같다.

  • 입출력 버퍼는 TCP 소켓 각각에 대해 별도로 존재한다.
  • 입출력 버퍼는 소켓생성시 자동으로 생성된다.
  • 소켓을 닫아도 출력버퍼에 남아있는 데이터는 계속해서 전송이 이뤄진다.
  • 소켓을 닫으면 입력버퍼에 남아있는 데이터는 소멸되어버린다.

TCP 슬라이딩 윈도우 프로토콜

TC가 데이터의 흐름까지 컨트롤 해주어 입력버퍼의 크기를 초과하는 분량의 데이터 전송은 발생시키지 않는다.

TPC의 내부 동작원리1: 상대 소켓과의 연결

Three way handshaking

  • 상대소켓과의 연결
  • 상대 소켓과의 데이터 송수신
  • 상대 소켓과의 연결종료

그림은 다음과 같은 순서로 이루어진다.

  1. (SYN) SEQ: 1000, ACK: -

    "내가 지금 보내는 이 패킷에 1000이라는 번호를 부여하니, 잘 받았다면 다음에는 1001번 패킷을 전달하라고 내게 말해달라!"

  2. (SYN+ACK) SEQ: 2000, ACK: 1001
    "내가 지금 보내는 이 패킷에 2000이라는 번호를 부여하니 잘 받았다면 다음에는 2001번 패킷을 전달하라고 내게 말해달라!" (SEQ 2000과 관련)
    "좀 전에 전송한 SEQ가 1000인 패킷은 잘 받았으니, 다음 번에는 SEQ가 1001인 패킷을 전송하기 바란다!" (ACK 1001과 관련)

  3. (ACK) SEQ: 1001, ACK: 2001
    "좀 전에 전송한 SEQ가 2000인 패킷은 잘 받았으니, 다음 번에는 SEQ가 2001인 패킷을 전송하기 바란다!"

TCP의 내부 동작원리2: 상대 소켓과의 데이터 송수신

ACK 번호 -> SEQ 번호 + 전송된 바이트 크기 + 1

TCP 소켓은 중간에 문제가 발생하여 SEQ에 대한 ACK메시지를 받지 못하였을 때 데이터의 손실에 대한 재전송을 위해 타이머를 동작시켜 Time out! 되었을 때 패킷을 재전송한다.

TCP의 내부 동작원리3: 상대 소켓과의 연결종료

Four-way handshaking


05-3 윈도우 기반으로 구현하기

op_client_win.c

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

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

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hSocket;
	char opmsg[BUF_SIZE];
	int result, opndCnt, i;
	SOCKADDR_IN servAdr;

	if (argc != 3)
	{
		printf("Usage : %s <IP> <port> \n", argv[0]);
		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(&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("Connected..........");

	fputs("Operand count: ", stdout);
	scanf("%d", &opndCnt);
	opmsg[0] = (char)opndCnt;

	for (i = 0; i < opndCnt; i++)
	{
		printf("Operand %d : ", i + 1);
		scanf("%d", (int*)opmsg[i * OPSZ + 1]);
	}
	fgetc(stdin);
	fputs("Operator: ", stdout);
	scnaf("%c", &opmsg[opndCnt * OPSZ + 1]);
	send(hSocket, opmsg, opndCnt * OPSZ + 2, 0);
	recv(hSocket, &result, RLT_SIZE, 0);

	printf("Operation result: %d \n", result);
	closesocket(hSocket);
	WSACleanup();
	return 0;
}

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

op_server_win.c

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

#define BUF_SIZE 1024
#define OPSZ 4
void ErrorHandling(char* message);
int calculate(int opnum, int opnds[], char oprator);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hServSock, hClntSock;
	char opinfo[BUF_SIZE];
	int result, opndCnt, i;
	int recvCnt, recvLen;
	SOCKADDR_IN servAdr, clntAdr;
	int clntAdrSize;
	if (argc != 2)
	{
		printf("Usage: %s <port> \n", argv[0]);
		exit(1);
	}

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

	hServSock = socket(PF_INET, SOCK_STREAM, 0);
	if (hServSock == INVALID_SOCKET)
		ErrorHandling("socket() error!");

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

	if (bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
		ErrorHandling("bind() error!");
	if (listen(hServSock, 5) == SOCKET_ERROR)
		ErrorHandling("listen() error!");
	clntAdrSize = sizeof(clntAdr);

	for (i = 0; i < 5; i++)
	{
		opndCnt = 0;
		hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, clntAdrSize);
		recv(hClntSock, &opndCnt, 1, 0);

		recvLen = 0;
		while ((opndCnt * OPSZ + 1) > recvLen)
		{
			recvCnt += recv(hClntSock, &opinfo[recvLen], BUF_SIZE - 1, 0);
			recvLen += recvCnt;
		}
		result = calculate(opndCnt, (int*)opinfo, opinfo[recvLen - 1]);
		send(hClntSock, (char*)&result, sizeof(result), 0);
		closesocket(hClntSock);
	}
	closesocket(hServSock);
	WSACleanup();
	return 0;
}

int calculate(int opnum, int opnds[], char op)
{
	int result = opnds[0], i;

	switch (op)
	{
	case '+':
		for (i = 1; i < opnum; i++) result += opnds[i];
		break;
	case '-':
		for (i = 1; i < opnum; i++) result -= opnds[i];
		break;
	case '*':
		for (i = 1; i < opnum; i++) result *= opnds[i];
		break;
			
	}
	return result;
}

void ErrorHandling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글