멀티 프로세스 에코 서버


  • 커널은 파일 디스크립터에 대한 정보를 파일 디스크립터 테이블에 관리한다. 파일 디스크립터 테이블은 프로세스당 하나씩 생성된다.

  • 파일 디스크립터 테이블은 프로세스당 하나씩 생성된다. 또한 커널은 시스템 상에 열려 있는 모든 열린 파일에 대한 정보를 저장하고 있으며, 시스템당 하나만 존재한다.

  • 파일 디스크립터 테이블은 열린 파일 테이블에 대한 링크를 가지고 있다.

  • 프로세스가 fork()를 이용하여 자식 프로세스를 생성하면 커널은 자식 프로세스를 위한 파일 디스크립터 테이브을 생성하고, 이때 모든 값이 복사되기 때문에 두 파일 디스크립터 테이블은 같은 값을 가지게 된다.

  • 커널의 열린 파일 테이블 입장에서보면, 하나의 열린 파일 엔트리를 가리키는 파일 디스크립터 테이블의 엔트리가 두개 가 된다.

  • 이 상황에서, 하나의 프로세스가 close() 함수를 실행하면 열린 파일을 가리키고 있는 파일 디스크립터 테이블의 엔트리가 존재하는 한 close 함수는 열린 파일 디스크립터 테이블의 해당 엔트리만 삭제한다.

#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>
#include <string.h>

void error_proc();
void error_print();

int main(int argc, char* argv[]) {
  int server_sd, client_sd;
  struct sockaddr_in server_addr, client_addr;

  int client_addr_len, read_len;
  char read_buffer[BUFSIZ];
  pid_t pid;

  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, 5) < 0) error_proc();

  client_addr_len = sizeof(client_addr);

  while(1) {
    client_sd = accept(server_sd, (struct sockaddr *)&client_addr, &client_addr_len);
    if (client_sd == -1) {
      error_print();
      continue;
    }
    printf("client %s: %d is connected...\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
    pid = fork();
    if (pid == 0) { /* child process */
      close(server_sd);
      while (1) {
        read_len = read(client_sd, read_buffer, sizeof(read_buffer) - 1);
        if (read_len == 0) break;
        read_buffer[read_len] = '\0';
        printf("client (%d): %s\n", ntohs(client_addr.sin_port), read_buffer);
        write(client_sd, read_buffer, strlen(read_buffer));
      }
      printf("client (%d) : is disconnected\n", ntohs(client_addr.sin_port));
      close(client_sd);
      return 0;
    } else if (pid == -1) error_proc("fork");
    else {
      close(client_sd);
    }
  }
  close(server_sd);
  return 0;
}

간단한 UDP 멀티 프로세스 채팅 프로그램


채팅 서버의 역할은 다음과 같다.

  1. 클라이언트의 접속 처리
  2. 클라이언트가 전송하는 문자열을 다른 클라이언트들에게 전송
  3. 클라이언트의 접속 종료 처리
  • 이 중에서, 클라이언트가 전송하는 문자열을 다른 클라이언트들에게 전송하는 기능은 여러 클라이언트들의 데이터를 하나의 소켓으로 처리할 수 있다.

  • recvfrom 함수를 통해서 전달받은 문자열을 sendto 함수로 전송한 클라이언트를 제외한 모든 클라이언트 들에게 전송하면 작업을 처리할 수 있다.

채팅 서버

#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>

#define MAX_CLIENT 10

void error_proc(const char*);

int check_sock_list(struct sockaddr_in *entry, struct sockaddr_in *list, int count);

int main(int argc, char* argv[]) {
  int my_sock, read_len, n_recv, res;
  char buff[BUFSIZ];
  char name_buffer[50];
  char *str_addr;
  struct sockaddr_in src_addr, dest_addr;

  socklen_t addr_len;
  int n_client = 0, i = 0, port;
  struct sockaddr_in sockets[MAX_CLIENT];

  if (argc != 2) {
    fprintf(stderr, "usage: %s port", argv[0]);
    return 0;
  }

  memset(&sockets, 0, sizeof(sockets[0])*MAX_CLIENT);
  my_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if (my_sock == -1) error_proc("socket");

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

  res = bind(my_sock, (struct sockaddr*)&src_addr, sizeof(src_addr));
  if (res == -1) error_proc("bind");
  addr_len = sizeof(dest_addr);

  while (1) {
    n_recv = recvfrom(my_sock, buff, BUFSIZ - 1, 0,
        (struct sockaddr *) &dest_addr, &addr_len);
    if (n_recv == -1) error_proc("recvfrom");
    res = check_sock_list(&dest_addr, sockets, n_client);

    if (res == n_client) {
      if (res == MAX_CLIENT) continue;
      else {
        memcpy(&sockets[res],
            &dest_addr, sizeof(dest_addr));
        n_client++;
      }
    }
    printf("n_client: %d\n", n_client);
    str_addr = inet_ntoa(dest_addr.sin_addr);
    sprintf(name_buffer, "%s: %d >> ", str_addr, ntohs(dest_addr.sin_port));

    for (i = 0; i < n_client; i++) {
      if (i == res) continue; // sender == receiver skip
      sendto(my_sock, name_buffer, strlen(name_buffer), 0, (struct sockaddr *) &sockets[i], addr_len);
      sendto(my_sock, buff, n_recv, 0, (struct sockaddr *) &sockets[i], addr_len);
    }
  }
  return 0;
}

채팅 클라이언트

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

void error_proc(const char*);

int main(int argc, char* argv[]) {
  int my_sock, read_len, n_sent, n_recv;
  char buff[BUFSIZ];
  char str_addr;
  struct sockaddr_in dest_addr;
  socklen_t addr_len;
  pid_t pid;

  my_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

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

  pid = fork();
  if (pid == -1) error_proc("fork");
  if (pid == 0) {
    while (1) {
      n_recv = recvfrom(my_sock, buff, BUFSIZ - 1, 0, (struct sockaddr*) &dest_addr, &addr_len);
      if (n_recv == -1) error_proc("read");
      write(1, buff, n_recv);
    }
  } else {
    while (1) {
      fgets(buff, BUFSIZ - 1, stdin);
      read_len = strlen(buff);
      if (read_len == 0) error_proc("fgets");
      n_sent = sendto(my_sock, buff, read_len, 0, (struct sockaddr*) &dest_addr, addr_len);
      if (n_sent == -1) error_proc("write");
      buff[read_len - 1] = '\0';
      if (!strcmp(buff, "END")) break;
    }
    kill(pid, SIGTERM);
  }
  return 0;
}

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

서버 화면

클라이언트1

클라이언트2

  • 클라이언트 간의 통신이 이루어지는 것을 확인할 수 있다.

참고 문헌


>> Home