ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 시스템 프로그래밍 실습 12주차 : Concurrent Programming
    System Programming/Ubuntu Linux 2021. 11. 15. 17:53
    728x90
    시스템 프로그래밍 실습 12주차 : Concurrent Programming

     

     

    먼저 10주차 Socket 내용 복습! 

     

    https://asidefine.tistory.com/109

     

    시스템 프로그래밍 실습 10주차 : Sockets

    시스템 프로그래밍 실습 10주차 : Sockets [목차] - Internet Connections - Client-Server Model Client Server - Socket  TCP / UDP  socket 함수들 ( socket(), connect(), bind(), listen(), accept()..

    asidefine.tistory.com

     

     

     

    [목차]

    - Process-based

    - Thread-based

    - Event-based(I/O multiplexing) 

     

     

     

     

     

    네트워크에서 의미하는 서버와 클라이언트간의 통신은 기본적으로 1개의 서버에 여러개의 클라이언트가 연결된 모델을 말한다. 하지만 앞선 포스팅에서 다루었던 여러가지 시스템 콜 및 연결방법은 클라이언트도 1개, 서버도 1개인 1:1 연결 상황에서만 적용가능한 것이다.

     

     

     

     

    그러나 서버를 돌리는 과정도 결국엔 프로세스로 이루어지므로, 서버 프로세스는  client 1과 connection이 연결된 동안 다른 client에서 오는 요청을 받을 수 없다. 즉, 여러 클라이언트가 서버에 접속할 때 동시에 request를 처리할 수 있는 능력이 없다. 이는 다시 말해 서버의 동시성(Concurrency)이 없다는 것을 말한다.

     

     

     

     


    서버에 동시성을 주기 위한 프로그래밍을 Concurrent Programming이라고 하는데, 다수의 클라이언트를 동시에 다루기 위해 크게 3가지 방법을 사용한다.

     


    1. Process-based : 커널이 복수의 logical flow를 생성한다. 즉, 클라이언트로부터 요청이 올때마다 서버 프로세스를 하나 더 만든다.
    2. Thread-based : 커널이 복수의 logical flow를 생성한다. 하지만 각각의 logical flow는 별개의 프로세스가 아닌 하나의 프로세스 안의 주소에서 데이터를 공유한다.
    3. Event-based(I/O multiplexing) : 프로그래머가 직접 여러개의 logical flow를 생성한다. 이하 Thread-based와 같다.

     

     

     

    1. Process-based

     

     

     

    Process-based로 concurrent server를 만들기 위해선, 서버 프로세스가 동작하는 도중 client의 request(connect 함수)를 받았을 경우 그 즉시 프로세스 하나를 더 생성해서 해당 프로세스에서 request가 온 클라이언트와의 connection을 수행하면 된다. 이 컨셉은 프로세스를 새롭게 생성해주는 fork()라는 시스템 콜 함수로 구현할 수 있는데, 사용례는 다음과 같다.

     

    int main(int argc, char *argv[]) {
      ...
      signal(SIGCHLD, handler);  // reaping zombies
      while(1) {
        connfd = accept(listenfd, (struct sockaddr*)&caddr, &caddrlen);
        if(fork() == 0) {      // child process
          close(listenfd);
          while((n = read(connfd, buf, MAXLINE)) > 0) {
            printf("got %d bytes from client.\n");
            write(connfd, buf, n);
          }
          close(connfd);   // termination of child process
          exit(0);         // exit
        }    // parent process
        close(connfd);
      }
    }

     

     

    fork() 시스템 콜이 호출되면 결과값으로 int값을 리턴하는데, 이 값이 0이면 child process(fork로 인해 새로 생성된 프로세스), 0이 아니면 parent process(fork() 시스템 콜을 호출한 프로세스)이다. 위 소스코드에서 기억해야 할 점은, fork() == 0인 중괄호 안, 즉 특정 클라이언트와의 connection을 담당하는 child process에선 listenfd를 사용할 일이 없으므로 close해준다는 것과, 반대로 parent process에서는 connection을 할 일이 없으므로 connfd를 close해줘야 한다는 것이다.

     

     

     

    * zombie childern 무조건 죽여야 한다 by signal(SIGCHLD, handler);

    * connfd의 복사본을 무조건 닫아야 한다 

     

     

     

    이 과정을 단계별로 설명하자면 다음과 같다.

     

     

    1. Client에서 connection request를 보낸다. 서버에서는 listenfd가 이 request를 받는다.
    2. accept로 request를 받는 직후, 서버는 fork를 통해 child process를 생성한다. fork 이후 parent process에서는 connfd를 쓸 일이 없으므로, connfd를 닫는다.
    3. child process는 call되기 이전의 데이터와 주소를 다 복사한다. 따라서 child process에서도 accept 이후로 client와 최종적으로 연결된 connfd를 사용할 수 있다.
    4. child에선 listenfd를 사용할 일이 없으므로 listenfd를 닫는다.
    5. client는 server child process와 소통한다.

     


    이렇게 Process-based Server로 다수 클라이언트와의 연결을 지원할 수 있다. 하지만 이 방법에는 고려해야 할 점이 있는데, 앞선 과정에서 parent와 child 모두 안쓰는 파일 디스크립터를 닫아주었는데, 이렇게 하지 않으면 메모리 누수가 발생할 위험이 높기 때문이다. 또한, Parent process는 반드시 connfd를 닫아주어야 한다. fork가 일어나면 프로세스의 모든 데이터가 복사되어서 전달되는데, 이는 parent에도 connfd가, client에도 connfd가 있다는 것을 의미한다. 이 때 connfd를 닫아주지 않으면 커널은 이 file discriptor를 계속 열린것으로 간주하고 connection을 끊지 않기 때문이다.

     

     

     

    // signal handler, called when child processes are terminated : SIGCHLD
    void handler(int sig) {
      pid_t pid;
      int stat;
      while((pid = waitpid(-1, &stat, WNOHANG)) > 0);
      return;
    }

     

    zombie 프로세스를 관리하는 방법은, child 프로세스가 terminate 되었을 때 부모에게 child가 종료되었다는 신호를 주면 된다. 이 신호가 왔을때, 부모 프로세스는 child 프로세스의 남아있는 resource가 다 지워질 때 까지 기다리고 있다가 지워지면 재개한다.

    결국 서버를 돌리기 위해선 프로세스가 여러개가 돌아가야 한다는 건데, 싱글코어 환경에선 기본적으로 한번에 한 개의 프로세스만 돌릴 수 있다. 멀티프로세싱이 가능하다는 것도, 사실 아주 각 프로세스에 할당하는 시간을 아주 잘게 잘라서 마치 한번에 실행되는것 '처럼'보이게 하는 것이다. 멀티프로세싱을 지원하기 위해서는 아주 빠른 시간 내에 Process A가 사용하고 있었던 control 권한을 Process B에게 넘겨주는 과정이 필요하다.

     

     

     

     

    예시 1) 

     

     

     

     


    Process-based design의 장단점

     

     

    장점 : 다수의 connection을 동시에 다룰 수 있고, 서로 다른 프로세스라는 독립적인 공간을 할당하므로 data protection 및 sharing model이 깔끔하다. 그리고 무엇보다 구현하기 쉽다.

     

    단점 : 프로세스를 생성하고, 종료하고, 프로세스간 context switch하는 과정이 overhead가 심하다. 이미 생성된 각 프로세스별로 데이터를 공유하기가 어렵다.

     

     

     

    2. Thread-based 

     

     

     

     


    Thread는 멀티프로세스 환경에서 발생할 수 있는 overhead를 줄이기 위해 Unix 시스템이 어느정도 자리를 잡은 후 나중에 나온 개념이다. Thread란 특정 프로세스 안에서 돌아가는 logical flow를 의미한다. 따라서 한 프로세스 안에 있는 여러 개의 쓰레드는 프로세스가 가지고 있는 가상 메모리 주소 전체를 공유하고, 커널에 의해 스케줄링딘다.

     

     

    즉, 쓰레드는 메모리의 code와 date 영역을 공유할 수 있지만, 프로세스는 완전 독립적으로 공유할 수 없다는 점에서 차이가 있다.

     

     

    Thread-based server를 구현하기 위해 POSIX Thread interface를 사용하는데, C 프로그래밍을 할때 쓰레드 생성, id, 종료, 동기화의 여러 기능을 수행하기 위해 call할 수 있다. posix thread를 사용하기 위해서는 리눅스 gcc옵션의 -lpthread를 추가해주어야 한다.

     

     

    void *thread(void *vargp);       // 쓰레드 호출시 부를 함수.
    int main() {
      pthread_t tid;
      pthread_create(&tid, NULL, thread, NULL);  
      // create(쓰레드 id, 쓰레드 옵션, call routine 함수, 해당 함수의 인자값)
      pthread_join(tid, &NULL);
      // 생성한 쓰레드가 끝날 때 까지 기다림.
      exit(0);
    }
    
    void *thread(void *vargp) {
      printf("hello world!\n");
      return NULL;
    }

     

     

     

    쓰레드도 프로세스와 마찬가지로 종료된 이후에도 잠시동안 해당 쓰레드의 resource가 시스템에 남아있다. 하지만 프로세스와 달리 쓰레드는 부모와 자식간의 관계가 아예 없기 때문에, main thread에 생성한 peer thread가 끝났다는 것을 알려줘야 할 필요가 있다. 이 때 pthread_join() 함수를 사용한다.

     

     

     

     

     

     

     

     

    위 hello world의 execution flow를 그려보면 다음과 같다. 이제 이 Thread를 사용해 concurrent server를 만들어보도록 하겠다.

     

     

    void *thread_main(void *arg);
    
    int main(int argc, char *argv[]) {
      int *connfdp;
      pthread_t pid;
      ...
      while(1) {
        connfdp = (int*)malloc(sizeof(int));
        *connfdp = accept(listenfd, (struct sockaddr*)&saddr, &caddrlen);
        pthread_create(&tid, NULL, thread_main, connfdp);
      }
    }
    
    void *thread_main(void *arg) {
      int n;
      char buf[MAXLINE];
      int connfd = *((int*)arg);
      pthread_detach(pthread_self());
      free(arg);
    
      while((n = read(connfd, buf, MAXLINE)) > 0) write(connfd, buf, n);
      close(connfd);
      return NULL;
    }

     

    이번에는 join 대신 pthread_detach라는 새로운 함수를 사용해주었다. detach는 해당 쓰레드가 끝났을 때 쓰레드가 resource들을 즉시 다 제거해버리도록 하는 상태로 바꿔주는 역할을 한다. 따라서 detach 함수를 사용할 경우, 메모리 누수를 피할 수 있다. 
    // 즉, joinable thread는 pthread_join을 이용해 memory resource를 free해줘야 하지만, detached thread는 이거랑은 관계없이 무조건 thread가 끝나면 resource를 자동으로 제거한다.

     

     

     

    예시 1) 

     

     

     

     


    Thread-based design의 장단점

     

     

    장점 : thread간 데이터를 공유하기가 쉽다. 프로세스보다 더 효율적이다.

     

    단점 : 의도치 않은 데이터 공유가 에러를 만들 수 있다.

     

     

     

     

    https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=scy7351&logNo=221419762700 

     

    (시스템 프로그램) Concurrent Programming

    네트워크에서 의미하는 서버와 클라이언트간의 통신은 기본적으로 1개의 서버에 여러개의 클라이언트가 연...

    blog.naver.com

     

     

    https://velog.io/@tonyhan18/%EC%8B%9C%EC%8A%A4%ED%85%9C-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-12-2-Concurrent-Programming

     

    시스템 프로그래밍 12-2 (Concurrent Programming)

    ¢ The human mind tends to be sequential¢ The notion of time is often misleading¢ Thinking about all possible sequences of events in a computer system

    velog.io

     

     

    https://velog.io/@tonyhan18/%EC%8B%9C%EC%8A%A4%ED%85%9C-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-13-1-Concurrent-Programming

     

    시스템 프로그래밍 13-1 (Concurrent Programming)

    Server maintains set of active connectionsArray of connfd’sRepeat:Determine which descriptors (connfd’s or listenfd) have pending inputse.g., using se

    velog.io

     

     

     

    3. Event-based (I/O multiplexing) 

     

     

    I/O multiplexing이란 여러개의 I/O request가 왔을 때 하나를 선택하여 처리하는 것을 뜻한다.

     

     

     

    먼저 fd_set과 FD_ZERO, FD_SET을 이해해야 한다! 

     

    https://hahaite.tistory.com/290

     

    fd_set 구조체 값을 변경시키는 select 함수

    select() 함수를 예제보고 가져다 쓰기만 하다가 뭔가 수정만 하면 잘 안되어 이대로는 안되겠다 싶어 개념을 좀 더 파악하기로 하였다. ♣ fd_set 구조체 fd_set 구조체는 File Descriptor (이하 FD) 를 저

    hahaite.tistory.com

    https://reakwon.tistory.com/117

     

    [리눅스] select 개념과 설명, 간단한 예제

    아래와 같은 상황을 생각해볼까요? 어떤 프로세스가 다음과 같이 파일 3개를 처리하는데 그 중 입력이 있는 파일을 프로세스에 처리하는 상황을 어떻게 구현할 수 있을까요? 이때 우리는 쓰레드

    reakwon.tistory.com

    https://mintnlatte.tistory.com/313

     

    9. 다중입출력 - select()

    ■ 다중 입출력 서버를 구현하기 위해 select() 함수로 파일디스크립터의 변화를 확인. (1) select() - 다중 입출력 : 여러개의 파일 디스크립터를 동시에 관찰하고, 변화가 있을 시 해당 파일 디스크

    mintnlatte.tistory.com

     

    보통 2가지 함수가 있는데 select과 epoll 함수이다.

    이 두가지 함수들을 통해 Concurrent server를 구현할수 있는데 둘다 single process를 기반으로 한다. 

     

     

    1) select() 

     

     

    select는 특징이 fd_set이란것을 가진다. 간단히 소켓을 가진 배열이다.

    외부에서 I/O request가 오면 우선 request가 온 해당 소켓에 대해 배열에 표시를 하고

    나중에 그 배열을 순차적으로 검사하여 표시된 소켓에 대해 I/O처리를 하는 것이다.

    우선 select 함수를 보면 이러한 형태를 가지고 있다.

     

     

    #include <unistd.h>
    #include <sys/types.h>//fd_set
    #include <sys/time.h>//timeval
     
    int select (int n, fd_set *read-fds, fd_set *write-fds, fd_set *except-fds, struct timeval *timeout)

     

    - 인자로 n은 해당 fd_set에 들어가 있는 소켓의 file descriptor 중 가장 큰 것의+1 한 값이다.

    간단히 보면 fd_set 검사시 어디 까지 검사 할것인지를 정하는 것으로 보면 된다.

     

    - 다음 세 fd_set은 read, write, excpetion 상황에 대해 만든 fd_set이고 실제 select함수 쓸때는 셋 중

    하나만 쓰면 된다. 

     

    - 마지막은 time으로 해당 시간 동안 select 함수에서 기다리는 것이다.

    구체적인 시간을 설정할수도 있고 0을 줘서 nonblock형태로도 쓸수 있다.

     

    - select 함수를 쓸때는 fd_set을 다루기 위한 여러 다른 함수들도 존재 한다.

    크게 4가지로 FD_ZERO,FD_CLR, FD_SET,FD_ISSET이 있다.

     

     

    이 함수들을 사용하면 접속된 클라이언트들 중에서 하나 선택하여 서비스를 제공할수 있을 뿐만

    아니라 서버에 클라이언트들이 접속해 오는 것도 처리 가능하다. (이 역시 I/O request의 일종이기에)

    이 함수들이 사용되는 flow를 보면 대략 이러하다.

     

    먼저 FD_ZERO를 통해 선언한 fd_set을 초기화 한다.

    그리고 FD_SET를 통해 검사할 소켓을 fd_set에 넣는다. 이따 서버가 클라이언트들이 접속해 오는 것을 처리 하기 위해서는 file descriptor (fd)로 서버의 소켓의 fd를 주면 된다.

     

    그 다음 select 함수에 넣을 timeval을 설정한 다음에 select 함수를 써서 설정한 시간 동안 기다린다. select함수는 fd_set내부에서 1이 몇개 있나를 리턴하기 때문에 0이면 계속 반복하여 select 함수를 수행하고 0보다 크면 FD_ISSET에 인자로 서버 소켓의 fd를 주어서 서버에게 I/O request가 왔는지 즉 클라이언트에게 접속 요청이 들어왔는지 검사 한다. 요청이 들어온 것이면 (리턴값이 1이면 ) accept처리하고 만약 아니라면 이미 접속한 클라이언트들 중에 하나에게 서비스를 제공하면 된다.

     

    다소 복잡해 보이지만 차분히 읽어보고 예제를 통해 보면 이해는 쉽다.

     

     

     

    예제 1) 

     

    while (running)
        {
                
            /* Zero socket descriptor vector and set for server sockets */
            /* This must be reset every time select() is called */
            FD_ZERO(&sockSet);
           
             FD_SET(servSock, &sockSet);
            
            /* Timeout specification */
            /* This must be reset every time select() is called */
            selTimeout.tv_sec = timeout;       /* timeout (secs.) */
            selTimeout.tv_usec = 0;            /* 0 microseconds */
     
            int tmp=0;
            for(i=0;i<MAXCLNT;i++){
                tmp=clntSock[i];
    //            printf("tmp: %d i:%d\n",tmp,i);
            if(i==0)
                printf("\n");                
                if(tmp>0){
                    printf("fd_set info:%d i:%d\n",tmp,i);
                    FD_SET(tmp,&sockSet);
                }
                if(tmp>maxDescriptor)
                    maxDescriptor=tmp;
            }//for-fill clntSock    
                    
            /* Suspend program until descriptor is ready or timeout */
            if (select(maxDescriptor + 1, &sockSet, NULL, NULL, &selTimeout) == 0)
                printf("No echo requests for %ld secs...Server still alive\n", timeout);
            else//not zero 
            {
                time(&st_t);
        
     
                if (FD_ISSET(servSock, &sockSet))
                 {
                        printf("Request on Client\n ");
                        newSock=AcceptTCPConnection(servSock);
            
                        for(i=0;i<MAXCLNT;i++){
                            if(clntSock[i]==0){
     
                                clntSock[i]=newSock;
                                printf("Client sock information is added on list:(%d)\n",i);
                                break;
                            }
     
                        }//for-clntSock
                      time(&fn_t);
                    time_t dif=fn_t-st_t;
                    printf("%ld min %ld sec spent\n",dif/60,dif%60);
                 
                  }//if- FD_ISSET-servSock
                else    {
                    for(i=0;i<MAXCLNT;i++){
     
                        tmp=clntSock[i];
                        if(FD_ISSET(tmp,&sockSet)){
                    //        sleep(5);                
                            HandleTCPClient(tmp); //send
                            clntSock[i]=0;
                            }//if                    
                    }//for
                }//clnt-I/O operation -else
            }//else -select-
     
        }//while

     

     

    예제 2) 

     

     

     

     

    Event-based Design의 장단점

     

     

     

     

     

     

    https://www.crocus.co.kr/542?category=204622 

     

    소켓 프로그래밍 - (26) I/O Multiplexing Select 개념 및 소스코드

    - 본 내용은 Linux (Ubuntu 14.04 lts)를 기반으로 제작되었습니다. - - I/O Multiplexing 예를들어 네트워크 프로그램을 구현하다 보면, scanf중에는 printf가 되지 않는 현상들을 볼 수 있다. 즉, I/O가 병행적..

    www.crocus.co.kr

    https://www.crocus.co.kr/543?category=204622 

     

    소켓 프로그래밍 - (27) I/O Multiplexing Select를 이용한 통신

    - 본 내용은 Linux (Ubuntu 14.04 lts)를 기반으로 제작되었습니다. - I/O Multiplexing Select 개념 및 예제 코드 :: http://www.crocus.co.kr/542 I/O Multiplexing Select를 이용하여 간단한 에코 서버를 제작..

    www.crocus.co.kr

     

    https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=sanghun0318&logNo=220350642598 

     

    I/O multiplexing 을 이용한 Concurrent 서버 구현 1 -select

    우선 Multiplexing이라 함은 간단하게 말해서 여러개 중에 하나를 선택하는 것이다. (다수의 저수준 채널 ...

    blog.naver.com

     


    두 예제를 같이 보는 것이 도움이 된다! 

     

     

    * 얘는 Multi client chatting (대신 client에서 select 함수 사용 안함)

    https://rehu.tistory.com/25

     

    3. I/O 멀티플렉싱 서버와 멀티프로세스 클라이언트를 이용한 채팅 프로그램

    (윤성우 저, 'TCP/IP 소켓 프로그래밍' - 11장 관련 내용입니다) 책에 기술 된 Multiplexing 서버와 Multiprocessing 클라이언트를 일부 수정하여 채팅 프로그램을 작성하는 문제입니다. 다중 접속을 지원

    rehu.tistory.com

     

     

    # server.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <sys/time.h>
    #include <sys/select.h>
     
    #define BUF_SIZE 100
    void error_handling(char *buf);
     
    int main(int argc, char *argv[])
    {
        //소켓, timeout값, fd_set값 등을 저장할 변수 선언
        int serv_sock, clnt_sock;
        struct sockaddr_in serv_adr, clnt_adr;
        struct timeval timeout;
        fd_set reads, cpy_reads;
        socklen_t adr_sz;
        int fd_max, str_len, fd_num, i;
        char buf[BUF_SIZE];
     
        //입력 포맷이 맞지 않으면 오류 출력 후 종료
        if(argc!=2) {
            printf("Usage : %s <port>\n", argv[0]);
            exit(1);
        }
     
        //서버의 소켓을 생성
        serv_sock=socket(PF_INET, SOCK_STREAM, 0);
        
        //서버의 주소정보를 저장, 이때 네트워크 바이트 순서로 저장한다
        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(atoi(argv[1]));
        
        //bind 함수를 통해 소켓에 서버의 주소정보를 할당
        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");
     
        //fd_set형 변수 reads의 모든 비트를 0으로 초기화
        FD_ZERO(&reads);
        //소켓 디스크립터 정보를 등록
        FD_SET(serv_sock, &reads);
        //소켓의 번호를 저장
        fd_max=serv_sock;
     
        while(1) {
            cpy_reads=reads;
            //타임아웃 시간 설정
            timeout.tv_sec=5;
            timeout.tv_usec=5000;
            
            //소켓 포함 모든 파일디스크립터를 대상으로 '수신된 데이터의 존재여부' 검사
                //오류가 발생했다면
            if((fd_num=select(fd_max+1, &cpy_reads, 0, 0, &timeout))==-1) {
                break;
            }
                //Timeout이 발생했다면
            if(fd_num==0) {
                continue;
            }
                //변화가 발생했다면
            for(i=0; i<fd_max+1; i++) {
                if(FD_ISSET(i, &cpy_reads)) {
                    // 연결요청이 발생했다면
                    if(i==serv_sock) {
                        //연결요청 수락    
                        adr_sz=sizeof(clnt_adr);
                        clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
                        
                        //해당 연결을 감시하도록 FD_SET을 통해 설정
                        FD_SET(clnt_sock, &reads);
                        //연결을 통해 clnt_sock 값이 증가했다면 fd_max 재설정
                        //(연결종료 된 클라이언트 < 새로 연결된 클라이언트)이라면 
                        if(fd_max<clnt_sock)
                            fd_max=clnt_sock;
                        printf("connected client: %d \n", clnt_sock);
                    }
                    else {
                        //메세지를 읽어들임   
                        str_len=read(i, buf, BUF_SIZE);
                        //종료 요청이 왔다면 연결 종료, fd_set값 초기화, 해당 소켓 종료
                        if(str_len==0) {    
                            FD_CLR(i, &reads);
                            close(i);
                            printf("closed client: %d \n", i);
                        }
                        //메세지가 왔다면
                        else {
                            //write(i, buf, str_len);    // echo!
                            //메세지를 모든 사람에게 전송 (4가 첫번째 소켓)
                            for(int j=4; j < fd_max + 1; j++) { 
                                write(j, buf, str_len);
                            }
                        }
                    }
                }
            }
        }
        close(serv_sock);
        return 0;
    }
     
    void error_handling(char *buf) {
        fputs(buf, stderr);
        fputc('\n', stderr);
        exit(1);
    }

     

    #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 100
    #define NAME_SIZE 20
     
    void error_handling(char *message);
    void read_routine(int sock, char *buf);
    void write_routine(int sock, char *buf);
     
    char name[NAME_SIZE] = "[NULL]";
    char buf[BUF_SIZE];
     
    int main(int argc, char *argv[]) {
        //소켓, pid값 등을 저장할 변수선언
        int sock;
        pid_t pid;
        char buf[BUF_SIZE];
        struct sockaddr_in serv_adr;
        
        //입력 포맷이 맞지 않으면 오류 출력 후 종료
        if(argc!=4) {    
            printf("Usage : %s <IP> <port> <name>\n", argv[0]);
            exit(1);
        }
        //데이터를 형식에 맞추어 쓰드록 지정
        sprintf(name, "[%s]", argv[3]);
     
        //클라이언트 소켓 설정
        sock=socket(PF_INET, SOCK_STREAM, 0);  
        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]));
        
        //connect함수를 통해 서버와 연결 시도
        if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
            error_handling("connect() error!");
     
        //fork 함수를 통해 입력, 출력을 처리하는 프로세스를 각각 만듦 
        pid=fork();
        if(pid==0)
            write_routine(sock, buf);
        else 
            read_routine(sock, buf);
     
        close(sock);
        return 0;
    }
     
    //서버로부터 메세지를 읽어들이는 함수
    void read_routine(int sock, char *buf) {
        char total_msg[NAME_SIZE + BUF_SIZE];
        while(1) {
            //서버로부터 메세지를 읽어들임
            int str_len=read(sock, total_msg, NAME_SIZE + BUF_SIZE);
            
            //close를 받았다면 종료
            if(str_len==0)
                return;
     
            //데이터 출력
            total_msg[str_len]=0;
            fputs(total_msg, stdout);
            //printf("Message from server: %s", buf);
        }
    }
     
    //서버로 메세지를 전송하는 함수
    void write_routine(int sock, char *buf) {
        char total_msg[NAME_SIZE + BUF_SIZE];
        while(1) {
            //입력값을 받아들임
            fgets(buf, BUF_SIZE, stdin);
            
            //만약 'q'나 'Q' 문자가 입력되면 종료 
            if(!strcmp(buf,"q\n") || !strcmp(buf,"Q\n")) {    
                shutdown(sock, SHUT_WR);
                return;
            }
            
            //데이터를 형식에 맞추어 쓰드록 지정
            sprintf(total_msg, "%s %s", name, buf);
            
            //서버로 입력한 데이터 전송
            write(sock, total_msg, strlen(total_msg));
        }
    }
     
    void error_handling(char *message) {
        fputs(message, stderr);
        fputc('\n', stderr);
        exit(1);
    }

     

     

     

     

     

    * 얘는 echo server (대신 client에서 select 함수 사용! )

    https://rehu.tistory.com/24

     

    2. Select 함수를 사용하는 I/O 멀티플렉싱 에코 클라이언트 작성

    (윤성우 저, 'TCP/IP 소켓 프로그래밍' - 10장 관련 내용입니다) Select 함수를 이용하여 주어진 서버와 통신하는 I/O 멀티플렉싱 클라이언트를 작성하는 문제입니다.  - 일정 시간 내에 데이터를 입

    rehu.tistory.com

     

     

    #server.c 

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <sys/time.h>
    #include <sys/select.h>
     
    #define BUF_SIZE 100
    void error_handling(char *buf);
     
    int main(int argc, char *argv[])
    {
        int serv_sock, clnt_sock;
        struct sockaddr_in serv_adr, clnt_adr;
        struct timeval timeout;
        fd_set reads, cpy_reads;
     
        socklen_t adr_sz;
        int fd_max, str_len, fd_num, i;
        char buf[BUF_SIZE];
        if(argc!=2) {
            printf("Usage : %s <port>\n", argv[0]);
            exit(1);
        }
     
        serv_sock=socket(PF_INET, SOCK_STREAM, 0);
        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(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");
     
        FD_ZERO(&reads);
        FD_SET(serv_sock, &reads);
        fd_max=serv_sock;
     
        while(1)
        {
            cpy_reads=reads;
            timeout.tv_sec=5;
            timeout.tv_usec=5000;
     
            if((fd_num=select(fd_max+1, &cpy_reads, 0, 0, &timeout))==-1)
                break;
            
            if(fd_num==0)
                continue;
     
            for(i=0; i<fd_max+1; i++)
            {
                if(FD_ISSET(i, &cpy_reads))
                {
                    if(i==serv_sock)     // connection request!
                    {
                        adr_sz=sizeof(clnt_adr);
                        clnt_sock=
                            accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
                        FD_SET(clnt_sock, &reads);
                        if(fd_max<clnt_sock)
                            fd_max=clnt_sock;
                        printf("connected client: %d \n", clnt_sock);
                    }
                    else    // read message!
                    {
                        str_len=read(i, buf, BUF_SIZE);
                        if(str_len==0)    // close request!
                        {
                            FD_CLR(i, &reads);
                            close(i);
                            printf("closed client: %d \n", i);
                        }
                        else
                        {
                            write(i, buf, str_len);    // echo!
                        }
                    }
                }
            }
        }
        close(serv_sock);
        return 0;
    }
     
    void error_handling(char *buf)
    {
        fputs(buf, stderr);
        fputc('\n', stderr);
        exit(1);
    }

     

     

     

    #client.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <sys/time.h>
    #include <sys/select.h>
     
    #define BUF_SIZE 30
    void error_handling(char *message);
    void read_routine(int sock, char *buf);
    void write_routine(int sock, char *buf);
     
    int main(int argc, char *argv[]) {
        int sock;
        char buf[BUF_SIZE];
        struct sockaddr_in serv_adr;
     
        //타이머를 계산하기 위해 timeval형 변수 timeout 설정
        struct timeval timeout;
        //fd_set형 변수 선언하여 파일 디스크립터 정보 등록
        fd_set reads, cpy_reads;
        //검사할 파일 디스크립터의 수
        int fd_max, fd_num;
     
        if(argc!=3) {
            printf("Usage : %s <IP> <port>\n", argv[0]);
            exit(1);
        }
     
        sock=socket(PF_INET, SOCK_STREAM, 0);
        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!");
     
        //fd_set형 변수 reads의 모든 비트를 0으로 초기화
        FD_ZERO(&reads);
        //입력 및 소켓 디스크립터 정보를 등록
        FD_SET(0, &reads);
        FD_SET(sock, &reads);
        //소켓의 번호를 저장
        fd_max = sock;
        while(1) {
            cpy_reads = reads;
            //타임아웃 시간 설정
            timeout.tv_sec=5;
            timeout.tv_usec=0;
            //소켓 포함 모든 파일디스크립터를 대상으로 '수신된 데이터의 존재여부' 검사
            fd_num=select(fd_max+1, &cpy_reads, 0, 0, &timeout);
            //오류가 발생했다면
            if(fd_num==-1) {
                error_handling("select() error\n");
            }
            //Timeout이 발생했다면
            else if (fd_num == 0) {
                puts("Time-out!");
                continue;
            }
            //변화가 발생했다면
            else {
                //만약 입력 디스크립터에 변화가 발생했다면 (사용자로부터 데이터를 입력받았다면)
                if(FD_ISSET(0, &cpy_reads)) {
                    //입력된 내용을 서버로 전송하는 함수
                    write_routine(sock, buf);
                    //파일 디스크립터 정보 삭제
                    FD_CLR(0, &cpy_reads);
                }
                //만약 포트에 변화가 발생했다면(서버로부터 데이터를 받았다면)
                if(FD_ISSET(sock, &cpy_reads)) {
                    //받은 데이터를 출력하는 함수
                    read_routine(sock, buf);
                    //파일 디스크립터 정보 삭제
                    FD_CLR(sock, &cpy_reads);
                }
            }
        }
        close(sock);
        return 0;
    }
     
    void read_routine(int sock, char *buf)
    {
        int str_len=read(sock, buf, BUF_SIZE);
        if(str_len==0)
            return;
     
        buf[str_len]=0;
        printf("Message from server: %s", buf);
    }
    
    void write_routine(int sock, char *buf)
    {
        fgets(buf, BUF_SIZE, stdin);
        if(!strcmp(buf,"q\n") || !strcmp(buf,"Q\n"))
        {
            shutdown(sock, SHUT_WR);
            exit(0);
        }
        write(sock, buf, strlen(buf));
    }
    
    void error_handling(char *message)
    {
        fputs(message, stderr);
        fputc('\n', stderr);
        exit(1);
    }

     

     

     

    728x90
Designed by Tistory.