상세 컨텐츠

본문 제목

Chattting Project

프로그래밍/시스템 프로그래밍

by wlwwlwwlw 2016. 12. 17. 11:04

본문

무엇을 만들었는가?

 

라즈베리파이를 이용하여 채팅 서버를 실행하고 epoll, select, thread를 사용해 보았다.

 

Github

모든 소스 코드는 https://github.com/wlwwlwwlw/adv-sys-programming에 업로드 하였으며 chat.c는 원래의 채팅 버젼 epoll_thread_chat.c는 epoll과 thread를 이용한 채팅 버전이다.

 

chat.c의 주요 함수

 

기본적으로 주어진 chat.c의 주요함수와 기능을 알아보도록 하겠다.

chat.c는 기본적으로 하나의 서버와 하나의 클라이언트가 채팅할 수 있는 구조로 만들어졌다.

아래 그림과 같이 네개의 함수가 주가되어 동작한다.

 

 

launch_chat

 

launch_chat 함수는 채팅서버의 클라이언트를 담당하는 함수이다. 앞의 포스팅에서 얘기한 select 함수를 이용하여 클라이언트를 구현하였다.

 

클라이언트 소켓의 생성과정은 서버 소켓의 생성과정에 비해 상대적으로 간단하다. 기본적으로 소켓을 여는 socket() 함수와 서버에 연결요청을 하는 connect() 함수로 이루어져 있다.

 

우선 socket() 함수를 이용하여 socket을 열고 주소값을 설정한 후 connect() 함수를 이용하여 서버로 연결요청을 한다.

 

// socket

if ((ret = clientSock = socket(PF_INET, SOCK_STREAM, 0)) == -1) {        

        perror("socket");
        goto leave;
    }

  serverAddr.sin_family = AF_INET;
  serverAddr.sin_addr.s_addr = inet_addr(IP); // ip주소 할당
  serverAddr.sin_port = htons(PORT); // 포트번호 할당

 

// connect

if ((ret = connect(clientSock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)))) {
        perror("connect");
        goto leave1;
    } 

 

launch_server

 

launch_server 함수는 채팅서버의 서버를 담당하는 함수이다. 

서버 소켓의 생성과정은 socket(), bind(), listen(), accept()로 이루어져있다.

 

   // socket

   if ((ret = serverSock = socket(PF_INET, SOCK_STREAM, 0)) < 0) { 
        perror("socket");
        goto leave;
    }

    setsockopt(serverSock, SOL_SOCKET, SO_REUSEADDR, (void *)&i, sizeof(i));

    Addr.sin_family = AF_INET;
    Addr.sin_addr.s_addr = INADDR_ANY;
    Addr.sin_port = htons(PORT);

 

    if ((ret = bind(serverSock, (struct sockaddr *)&Addr,sizeof(Addr)))) { // bind

        perror("bind"); 
        goto error;
    }

    if ((ret = listen(serverSock, 1))) { // listen
        perror("listen");
        goto error;
    }

    if ((acceptedSock = accept(serverSock, (struct sockaddr*)&Addr, &AddrSize)) < 0) { // accept
        perror("accept");
        ret = -1;
        goto error;
    } 

 

 

epoll을 이용한 launch_server

 

 

select 함수의 단점을 극복한 epoll을 이용하여 서버를 구현 해 보았다.

 

select 방식에서는 함수호출 시 전달한 fd_set형 변수의 변화를 통해서 관찰대상의 상태변화를 확인하지만, epoll 방식에서는 epoll_event라는 구조체를 기반으로 상태변화가 발생한 파일 디스크립터가 별도로 묶인다.

 

 struct epoll_event *ep_events; 
 struct epoll_event event; // event occured fd set
 int epfd, event_cnt;

 

epoll_create() 함수를 이용하여 epoll 인스턴스를 생성하고 epoll_ctl() 함수로 인스턴스 epfd에 파일 디스크립터 serverSock을 등록한다. 이벤트가 발생한 파일디스크립터 중 서버의 소켓과 같은것을 찾아 accept() 함수를 실행한다. 메세지를 주고 받은 후 인스턴스 epfd에서 해당 파일디스크립터를 삭제한다.

 

 epfd = epoll_create(EPOLL_SIZE); //epoll 인스턴스 생성
 ep_events = malloc(sizeof(struct epoll_event)*EPOLL_SIZE); // 이벤트가 발생한 파일디스크립터가 채워질 버퍼

 event.events = EPOLLIN; // data to receive exists 수신할 데이터가 존재하는 상황 발생시
 event.data.fd=serverSock;
 epoll_ctl(epfd, EPOLL_CTL_ADD, serverSock, &event); // epfd에 serverSock 등록

 

 while(1)
 {   // select 함수에 대응하는 epoll_wait,  관찰 대상의 정보를 매번 전달할 필요가 없음

     event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); // number of file discripter event occured
     if(event_cnt == -1)
     {
         puts("epoll_wait() error");
         break;
     }

 

  for(j=0; j<event_cnt; j++)
  {
      if(ep_events[j].data.fd == serverSock) 
      {
          if ((acceptedSock = accept(serverSock, (struct sockaddr*)&Addr, &AddrSize)) < 0) // accept
          {
              perror("accept");
              ret = -1;
              goto error;
           }
      event.events = EPOLLIN;
      event.data.fd = acceptedSock;
      epoll_ctl(epfd, EPOLL_CTL_ADD, acceptedSock, &event);
      printf("[SERVER] Connected to %s\n", inet_ntoa(*(struct in_addr *)&Addr.sin_addr));
  }
  else
  {
      if (!(ret = count = recv(acceptedSock, data, MAX_DATA, 0))) // 0,transmission end
      {
          epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL); // epfd에서 파일디스크립터를 삭제 
          close(ep_events[i].data.fd);
          fprintf(stderr, "Connect Closed by Client\n");
          goto error;

          break;
      } 

 

thread를 이용한 launch_clients

 

다중 클라이언트를 실행하기 위하여 thread를 이용해 보았다.

pthread_create로 num_client수만큼 launch_chat() 함수를 실행하여 client를 여러개 실행한다. pthread_join으로 전달되는 ID의 thread가 종료될 때까지, 이 함수를 호출한 프로세스를 대기상태에 둔다.

 

 int launch_clients(int num_client)
{
    int sock;
    int i =0;
    pthread_t thread[20];
    void * thread_return;

    sock = socket(PF_INET,SOCK_STREAM,0);
    for(i=0;i<num_client;i++)
    {
        pthread_create(&thread[i],NULL,(void*)launch_chat,(void*)&sock);
    }
    for(i=0;i<num_client;i++)
    {
        pthread_join(thread[i],&thread_return);
    }
    close(sock);
    return 0;
}

'프로그래밍 > 시스템 프로그래밍' 카테고리의 다른 글

실행과 성능측정  (0) 2016.12.17
Thread  (0) 2016.12.17
select 와 epoll  (0) 2016.12.16

관련글 더보기