Iterative 서버 모델


  • 우리가 흔이 아는 게임 서버와 파일 서버와 같은 서버 프로그램은 동시에 여러 클라이언트에게 서비스를 제공한다. 이러한 서버 모델을 Concurrent(동시) 서버 모델이라고 한다.

  • 동시 서버 모델은 나중에 다루기로 하고, 여기서는 Concurrent 서버 모델처럼 여러 클라이언트를 처리하지는 못하지만 순차적으로 여러 클라이언트에게 서비스를 제공할 수 있는 Iterative(반복) 서버 모델을 작성할 것이다.

  • 그림에서 보이는 상태 다이어그램에서, accept() ~ close()를 반복하여 클라이언트의 요청을 처리하는 것이다.

TCP 서버 프로그램

  • 가장 기본이 되는 에코 클라이언트 / 에코 서버 프로그램을 작성해볼 예정이다.

  • 에코 서버는 에코 클라이언트가 전송한 문자열을 그대로 클라이언트에게 전송하는 프로그램이다. 에코 클라이언트는 사용자에게 입력받은 문자열을 에코 서버에게 전달하는 프로그램이다.

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>

void error_proc();

int main(int argc, char *argv[]) {
  int server_sd, client_sd;
  struct sockaddr_in server_addr, client_addr;
  int client_addr_len, read_len, str_len;
  char read_buff[BUFSIZ];

  if (argc != 2) {
    printf("Usage: %s [port] \n", argv[0]);
    exit(1);
  }
  printf("server start...\n");
  // 듣기 소켓으로 사용할 소켓을 생성한다.
  server_sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (server_sd == -1) error_proc();

  // 소켓에 설정할 주소 정보를 작성한다.
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(atoi(argv[1]));

  // 작성한 주소 정보를 소켓에 생성한다.
  if (bind(server_sd, (struct sockaddr *) &server_addr, sizeof(server_addr)) == -1) error_proc();
  // 소켓을 듣기 소켓으로 만들어서, 클라이언트의 요청을 받을 준비를 한다.
  if (listen(server_sd, 2) < 0) error_proc();

  client_addr_len = sizeof(client_addr);

  while (1) {
    // 대기열에서 ESTABLISHED 상태인 TCP 연결을 가져온다.
    client_sd = accept(server_sd, (struct sockaddr*) &client_addr, &client_addr_len);
    if (client_sd == -1) error_proc();
    printf("client %s: %d is connected...\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

    while(1) {
      // 클라이언트에서 전송한 문자열을 가져온다.
      read_len = read(client_sd, read_buff, sizeof(read_buff) - 1);
      if (read_len == 0) break;
      read_buff[read_len] = '\0';
      printf("client (%d): %s\n", ntohs(client_addr.sin_port), read_buff);
      // 클라이언트에서 전송한 문자열을 다시 클라이언트에게 전송한다.
      write(client_sd, read_buff, strlen(read_buff));
    }
  }
  close(server_sd);
  return 0;
}

void error_proc() {
  fprintf(stderr, "error: %s\n", strerror(errno));
  exit(1);
}

클라이언트 프로그램

  • 시작 인자로 서버의 IP 주소 및 포트 번호를 입력 받는다.
  • 사용자에게 입력 받은 문자열을 서버 프로그램으로 전송한다.
  • 서버의 응답을 화면에 출력한다.
  • 사용자에게 END 문자열을 입력 받으면 그 문자열 전송을 마지막으로 소켓을 종료한다.
  • END 문자열 전송 이후 프로그램을 종료한다.
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
  int client_sd;
  // 서버의 주소 정보를 struct sockaddr_in 구조체에 저장한다.
  struct sockaddr_in client_addr;
  int client_addr_len, read_len, receive_byte, max_buff;
  char write_buff[BUFSIZ];
  char read_buff[BUFSIZ];

  if (argc != 3) {
    printf("usage: %s [IP Address] [Port]\n", argv[0]);
  }
  // 접속을 시도할 소켓을 생성한다.
  client_sd = socket(AF_INET, SOCK_STREAM, 0);
  if (client_sd == -1) error_proc();
  printf("==== client program ====\n");

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

  // 서버에 연결을 시도한다.
  if (connect(client_sd, (struct sockaddr *)&client_addr, sizeof(client_addr)) == -1) {
    close(client_sd);
    error_proc();
  }

  // 연결된 이후 입출력 작업을 시도한다.
  while(1) {
    fgets(write_buff, BUFSIZ - 1, stdin);
    read_len = strlen(write_buff);
    if (read_len < 2) continue;
    receive_byte = 0;
    max_buff = BUFSIZ - 1;
    // 서버로부터 데이터를 받을 때 이미 받을 데이터의 크기를 알고 있기 때문에, >그 만큼의 데이터를 받을 때까지 루프를 실행한다.
    do {
      receive_byte += read(client_sd, read_buff, max_buff);
      max_buff -= receive_byte;
    } while (receive_byte < (read_len -1));

  read_buff[receive_byte] = '\0';
    printf("server: %s\n", read_buff);
    write_buff[read_len -1] = '\0';
    if (!strcmp(write_buff, "END")) break;
  }

  printf("END^^\n");
  close(client_sd);
  return 0;
}

void error_proc() {
  fprintf(stderr, "error: %s\n", strerror(errno));
  exit(errno);
}

참고 문헌


>> Home