S/C based on Iterative

with MK·2020년 8월 17일
0

소켓 프로그래밍

목록 보기
7/13
post-thumbnail

Iterative 기반의 서버, 클라이언트 구현

에코 서버와 에코 클라이언트를 구현
에코 서버는 클라이언트가 전송하는 데이터를 그대로 재전송하는, 말 그대로 echo(메아리)시키는 서버이다.

Iterative 서버의 구현

계속해서 들어오는 클라이언트의 연결 요청을 수락하기 위해서 서버는 accept를 반복적으로 수행할 수 있어야 한다.
While{
accept()
read() / write()
close(client)
}
하나의 클라이언트에 대한 서비스를 완료하면 또 다른 클라이언트의 요청을 수락해줘야 한다.

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

실행 할 Iterative 서버와 클라이언트 프로그램의 기본 동작 방식은 다음과 같다.

  • 서버는 한 순간에 하나의 클라이언트와 연결되어 에코 서비스를 제공한다.
  • 서버는 총 다섯 개의 클라이언트에게 순차적으로 서비스를 제공하고 종료한다.
  • 클라이언트는 프로그램 사용자로부터 문자열 데이터를 입력 받아서 서버에 전송한다.
  • 서버는 전송 받은 데이터를 클라이언트에게 재전송한다.(echo)
  • 서버와 클라이언트간의 문자열 에코는 클라이언트가 Q를 입력할 때까지 계속한다.

echo_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#incldue <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_hadling(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);
    }
    
    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 = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(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(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);
    }
    close(serv_sock);
	return 0;
}

void error_handling(char *message){
	fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
  • 50, 51행 : 에코 서비스가 이루어진다.
  • 53행 : close가 호출되면, 연결되어있던 상대방 소켓에 EOF가 전달된다.

실행 결과는 다음과 같다.

$ gcc echo_server.c -o eserver
$ ./eserver 2929
Connected client 1
Connected client 2
Connected client 3

echo_client.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;
    struct sock_addr_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;
        
        write(sock, message, strlen(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);
}

$ gcc echo_clinet.c -o eclient
$ ./eclient 127.0.0.1 2929
Connected.....
Input message(Q to quit) : Good
Message from server : Good
Input message(Q to quit) : Hi
Message from server : Hi
Input message(Q to quit) : q
$

에코 클라이언트의 문제점

에코 클라이언트의 다음 코드는 잘못된 가정이 존재한다.
write(sock, message, strlen(message));
str_len = read(sock, message, BUF_SIZE - 1);
message[str_len] = 0;
printf("Message from server : %s", message);
위 코드에서는 read, write 함수가 호출될 때마다 문자열 단위로 실제 입출력이 이뤄진다고 가정하고 있다.
TCP의 경우 데이터의 경계가 존재하지 않기 때문에 둘 이상의 write 함수 호출로 전달된 문자열 정보가 묶여서 한번에 서버로 전송될 수 있다. 그러한 경우 클라이언트는 한번에 둘 이상의 문자열 정보를 서버로부터 받기 때문에 원하는 결과를 얻지 못할 수 있다.

즉, 서버는 한번의 write로 데이터 전송을 하지만, 데이터의 크기가 크다면 운영체제가 내부적으로 이를 여러 조각으로 나누어 클라이언트에게 전송할 수도 있다. 이 과정에서 모든 조각이 오지 않았음에도 불구하고 클라이언트는 read()를 호출하게 된다.

0개의 댓글