[쓰레드 소켓 프로그래밍] 무엇이 잘 못된건지 절대 모르겠습니

kash0921의 이미지

얼마전에 segmentation fault때문에 질문 올렸으나 그때 제 컴터에 pthread library function이 없다는 걸 몰라서 그랬으나 이제는 해결이 됬습니다.

허나 이번에는 thread 사용에서 이상한 문제가 발생하여 고수분들께 도움을 요청 합니다.

우선 이 프로그램을 잠시 설명하겠습니다 (사실 설명하는게 아니라 이렇게 되야 되는겁니다.)

클라이언트: command line을 통해 서버에 파일을 요청하며 그 파일을 stdout의 형식으로 뿌려 주게 됩니다. 이때 stdout으로 출력되는 mp3 파일을 "mpg123 -" 라는 mp3 플래이어로 pipe in 하여 재생하게 하는게 목적입니다.

서버: 당연히 요청한 파일을 클라이언트에게 보내주는 일을 하게됩니다. 이때 여러 클라이언트의 접속을 허용하기위해 thread를 사용하게 됩니다.

아무래도 client에서 phread_create를 통해 data를 보낼때 sockfd 와 file_size를 동시에 보내려고 하면서 에러가 뜨는건지 쓰레드를 잘못 써서 그런건지 잘 모르겠습니다.

참고로 컴파일 방법은 gcc -o client client.c serveraddr filename.mp3 -lpthread 입니다. 서버는 그냥 보통이랑 같습니다 마지막에 -lpthread만 붙여주시고여.

client.c

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

#define PORT 6001
#define BUFFER_LEN (1024 * 1024)

void *receive_file(void *data)
{
  int sockfd, file_size, num_byte, total_num_byte;
  char buf[BUFFER_LEN];

  /*  pthread_detach(pthread_self()); */

  memcpy((void *)&sockfd, (char *)data, sizeof(int));
  memcpy((void *)&file_size, (char *)data+sizeof(int),sizeof(file_size));

  total_num_byte = 0;

  while(total_num_byte < file_size) {
    num_byte = recv(sockfd, buf, BUFFER_LEN, 0);
    if(num_byte == -1) {
      perror("recv");
      exit(1);
    }

    write(1, buf, num_byte);

    total_num_byte += num_byte;
  }

  printf("File size = %d\n", total_num_byte);

  close(sockfd);
  return (NULL);
}

int main(int argc, char *argv[])
{
  int sockfd, len, byte_recv, file_size;
  char *mp;
  char th_data[256];

  pthread_t threads;

  struct hostent *he;
  struct sockaddr_in server_addr;

  if(argc != 3) {
    fprintf(stderr,"Usage: ./client hostname file_name");
    exit(1);
  }

  mp = argv[2];

  if((he = gethostbyname(argv[1])) == NULL) {
    herror("gethostbyname");
    exit(1);
  }

  /* creating socket descriptor */
  if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
    perror("socket");
    exit(1);
  }

  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(PORT);
  server_addr.sin_addr = *((struct in_addr *)he->h_addr);
  memset(&(server_addr.sin_zero), '\0', 8);

  /* connect to server */
  if(connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) {
    perror("connect");
    exit(1);
  }

  len = strlen(mp);

  /* send the file_name to server */
  if(send(sockfd, mp, len, 0) == -1) {
    perror("send");
    exit(1);
  }

  byte_recv = 0;
  
  byte_recv = recv(sockfd, &file_size, sizeof(file_size), 0);
  if(byte_recv == -1) {
    perror("recv");
    exit(1);
  }

  file_size = ntohl(file_size);

  memcpy(th_data,(void *)&sockfd, sizeof(sockfd));
  memcpy(th_data+sizeof(sockfd),(void *)&file_size,sizeof(file_size));

  /* receive data from server */
  if(pthread_create(&threads, 0, receive_file, (void *)th_data) == -1) {
    perror("pthread_create");
    exit(1);
  }

  pthread_kill(threads, 0);
  return 0;
} 

server.c

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

#define MYPORT 6001
#define FILENAME_LEN 256
#define BUFFER_LEN (1024 * 1024)
#define BACKLOG 10
#define THREAD_NUM 10

void *serving_request(void *);

int main()
{
  pthread_t threads[THREAD_NUM];
  struct sockaddr_in server_addr, client_addr;

  int sockfd, new_fd,sin_size, thread_id, yes, i; 

  /* creating sock descriptor */
  if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    perror("socket");
    exit(1);
  }

  setsockopt(sockfd, SOL_SOCKET,SO_REUSEADDR, (void *)&yes, sizeof(int));

  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(MYPORT);
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  memset(&(server_addr.sin_zero), '\0', 8);

  if(bind(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1){
    perror("bind");
    exit(1);
  }

  if(listen(sockfd, BACKLOG) == -1) {
    perror("listen");
    exit(1);
  }
  printf("Socket listening..\n");

  sin_size = sizeof(struct sockaddr_in);
  
  i = 0;

  while(1) {
    if((new_fd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size)) == -1) {
      perror("accept");
      exit(1);
    }
    printf("Server: Got connection from %s\n", inet_ntoa(client_addr.sin_addr));
    
    thread_id = pthread_create(&threads[i], 
			       0,
			       serving_request, 
			       (void *)new_fd);
    if(thread_id < 0) {
      perror("pthread_create");
      exit(1);
    }
    /*    serving_request((void *)&new_fd); */
    pthread_detach(threads[i]);
  }
  i++;
  close(sockfd);
  return 0;
}

void *serving_request(void *data)
{
  int new_fd, file_requested, num_bytes, file_size;
  int byte_read, byte_written, retval;
  char file_name[FILENAME_LEN];
  char buf[BUFFER_LEN];

  struct sockaddr_in client_addr;
  struct stat file_stat;

  new_fd = (int)data;

  memset(file_name,'\0', FILENAME_LEN);    
  num_bytes = recv(new_fd, file_name, FILENAME_LEN, 0);

  if(num_bytes == 0) {
    close(new_fd);
    return (NULL);
  } else if(num_bytes < 0) {
    perror("recv");
    close(new_fd);      
    return (NULL);
  }
    
  file_name[num_bytes] = '\0';
  printf("File name: %s\n", file_name);
  /* open with readonly with filename that include path */
  file_requested = open(file_name, O_RDONLY);

  /* open function error checking */
  if(file_requested == -1) {
    perror("open");
    close(file_requested);
    close(new_fd);
    return (NULL);
  }

  fstat(file_requested, &file_stat);

  file_size = 0;
  file_size = htonl(file_stat.st_size);

  if(write(new_fd, &file_size, sizeof(file_size)) == -1) {
    perror("write");
    close(file_requested);
    close(new_fd);
    return (NULL);
  }
  
  byte_read = 0;
  byte_written = 0;

  while(byte_read < file_stat.st_size) {
    retval = read(file_requested, buf, BUFFER_LEN);
    printf("retval 1: %d\n", retval);
    if(retval == -1) {
      perror("read");
      return (NULL);
    }
    
    byte_read += retval;
    printf("Byte read : %d\n",byte_read);
    
    retval = write(new_fd, buf, byte_read - byte_written);
    printf("retval 2: %d\n", retval);
    if(retval == -1) {
      perror("send");
      return (NULL);
    }
    byte_written += retval;
    memset(buf, '\0', BUFFER_LEN);
    printf("Byte written : %d\n",byte_written);
    printf("Actual sent : %d\n", byte_read - byte_written);
  }
  printf("File size %d\n",ntohl(file_size));
  printf("Sent all\n");
  close(file_requested);
  close(new_fd);
  return (NULL);
}
익명 사용자의 이미지

:roll:

님께서 작성하신 프로그램을 보고 몇가지 개선점을 발견했습니다.
우선 클라이언트쪽 코드 구조를 보면, 사실 클라이언트 쪽에서 서버로 부터 파일의 이진 데이터를 수신하여 표준출력으로 보내 주는 코드는 쓰레드로 나눌 필요가 없는 부분입니다. 어차피 클라이언트가 서버에서 파일을 받는 동안에는 블록킹 되어있어야 합니다. 그렇다면 이 부분에서 쓰레드를 분기시킬 필요가 없는 것이지요. 또한 분기시켰다 하더라도, pthread_kill() 함수를 사용하는 것은 해당 쓰레드를 무조건 즉시 종료시키는 것입니다. 님께서 목적하시는 바를 구현 하시려면 phtread_kill() 함수 보다는 pthread_join() 함수를 사용하셔야 할것입니다. pthread_join() 함수을 사용하면 새로운 쓰레드를 분기시킨 부모 쓰레드는 자식 쓰레드가 작업을 종료할 (묵시적이든 명시적이든 pthread_exit() 함수가 호출되는 것) 때까지 대기합니다.

서버쪽에서는 파일전송 서비스를 위해 쓰레드를 분기시킨후 디태치 시켰는데, 굳이 그럴 필요는 없습니다. 하나의 쓰레드 그룹이라도 힙은 공유하지만 스택은 쓰레드별로 따로 사용합니다. 함수내의 자동 변수와 함수 전달 인수는 모두 쓰레드별로 각자의 스택을 사용하는 것이지요.

전체 구조상의 문제점을 먼저 짚어 보신 후, 디버그 코드를 이용하여 테스트를 해보시기 바랍니다. 쓰레드를 이용한 동시성 프로그램을 개발할 때는 디버그 코드가 들어가는 것 자체가 전반적인 동작에 큰 영향을 주기 때문에 먼저 정확하고 명료한 논리 구조를 작성하시는 것이 가장 중요합니다.

kash0921의 이미지

:D

지적 정말로 감사드립니다.

클라이언트 부분:

역시 쓰레드를 통해 클라이언트에 데이터를 보내는 쪽에서 쓰래드를 나누지 않아야 하는거 같습니다. 아무래도 제가 문제 해석을 잘못한거 같습니다. 이 글을 읽고 다시 살펴 보니 하나의 쓰레드를 만들어서 반복적으로 recv를 하여 버퍼에 데이타를 집어넣어주는 걸 만드는 것과 그와 동시에 다른 쓰레드는 데이터를 버퍼에서 받아 stdout으로 써주는 것을 요구 하는더라구요 -_-;; (해석이 넘 딸려서 커억..)

그런데 질문은 지금 while을 통해 데이터를 받고 그와동시에 write를 하여 문제가 없는거 같은데 굳이 쓰레드 두개를 나누어야 하는건가여?

서버 부분:

설명해주신 부분 정말 감사드립니다. 또 추가해서 질문을 드리자면 지금 제 서버 버전에서는 accept 이후에 쓰레드를 만들어 여러 프로세스를 핸들하고 있지만. 숙제에서는 하나의 쓰레드로 서버소켓을 만들어야 한다고 합니다. (accept 뒤에도 하나의 쓰레드가 있어야 하고요.) 그렇다면 처음 socket()을 할때 부터 쓰레드를 만들어 줘야 하는 건가여?

김경태의 이미지

급하게 숙제하시느라 어쩔수 없다는 생각도 들지만
귀하께서는 socket, listen, accept등의 socket용 server function에 대한 이해가 많이 부족하신 듯 합니다.
steven 아저씨 책 보시고 좀더 깊이 연구하신 후 프로그래밍 하시는게 더 빠를 듯 싶네요.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.