c로 만든 간단한 채팅 서버 소스 이해가 잘 않되서요.

boardholic의 이미지

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <netinet/in.h>
#include <string.h>
#define MAXLINE  512
#define MAX_SOCK 64
 
char *escapechar = "exit";
int getmax(int);
void removeClient(int);
int  maxfdp1;
int  num_chat = 0;
int client_s[FD_SETSIZE];

int main(int argc, char *argv[])  {
	char rline[MAXLINE], my_msg[MAXLINE];
	char *start = "Connected to chat_server \n";
	int i, j, n;
	int s, client_fd, clilen;
 
	fd_set  read_fds;
	struct sockaddr_in  client_addr, server_addr;
 
	if(argc != 2)  {
		printf("Usage : %s port\n", argv[0]); 
		exit(0);
	}
 
	if((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)  {
		printf("Server: Can't open stream socket.");   
		exit(0);
	}
   
	bzero((char *)&server_addr, sizeof(server_addr));  
		server_addr.sin_family = AF_INET;              
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	server_addr.sin_port = htons(atoi(argv[1]));     
  
	if(bind(s,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0) {
		printf("Server: Can't bind local address.\n");
		exit(0);
	}

        if (listen(s, 3) < 0) {
          printf("Server: Listen error.\n");
          exit(0);
        }
	maxfdp1 = s + 1;
  
	while(1) {
		FD_ZERO(&read_fds);
		FD_SET(s, &read_fds);
		for(i=0; i<num_chat; i++)  FD_SET(client_s[i], &read_fds);
			maxfdp1 = getmax(s) + 1;
		if (select(maxfdp1, &read_fds, (fd_set *)0, (fd_set *)0,(struct timeval *)0) < 0) {
			printf("select error <= 0 \n");
			exit(0);
		} 
		if(FD_ISSET(s, &read_fds)) {
			clilen = sizeof(client_addr);
			client_fd = accept(s, (struct sockaddr *)&client_addr, &clilen);
			if(client_fd == -1) {
				printf("accept error\n");
				exit(0);
			}
			client_s[num_chat] = client_fd; 
			num_chat++;
			send(client_fd, start, strlen(start), 0);
			printf("%d번째 사용자 추가.\n", num_chat);
		}
         
		for(i = 0; i < num_chat; i++)  {
			if(FD_ISSET(client_s[i], &read_fds)) {
				if((n = recv(client_s[i], rline, MAXLINE, 0))  <= 0)  {
					removeClient(i);
					continue;
				}	
				if(strstr(rline, escapechar) != NULL) {
					removeClient(i);
					continue; 
				}
				rline[n] = '\0';
				for (j = 0; j < num_chat; j++)  send(client_s[j], rline, n, 0);
				printf("%s\n", rline);
			}
		} 	
	}  		
} 			

void removeClient(int i) {
	close(client_s[i]);
	if(i != num_chat-1) client_s[i] = client_s[num_chat-1];
		num_chat--;
		printf("채팅 참가자 1명 탈퇴. 현재 참가자 수 = %d\n", num_chat);
}

int getmax(int k) {
	int max = k;    
	int r;
	for (r=0; r < num_chat; r++) {
		if (client_s[r] > max ) max = client_s[r];
	}
	return max;
}

select함수 부분쪽에서 전체적인 흐름을 잘 모르겠습니다.

몇 시간 째 뚫어져라 바라보고 이해해 볼라고 해도 중첩if나 for문

이 많아서 눈이 너무 햇갈립니다. 아직 select()함수에 대해 자세

히 이해를 못해서 그런걸가요?

특히 getmax()함수를 지워버리고 maxfdp1=getmax(s)+1

이부분을 maxfdp1=s+1이런식으로 사용해볼려고 했는데

getmax()함수 지우니까 아예 서버쪽에서 받고 보내기 자체가 않

되버리네요. getmax()정의한곳 보면 별거 없는거 같은데요 -.-

select() 함수부분쪽부터 이해할수 있게 주석좀 달아서 전체적인

흐름좀 이해할 수 있게 부탁드립니다.

rasungboy의 이미지

#include <stdio.h> 
#include <fcntl.h> 
#include <stdlib.h> 
#include <signal.h> 
#include <sys/socket.h> 
#include <sys/file.h> 
#include <netinet/in.h> 
#include <string.h> 
#define MAXLINE  512 
#define MAX_SOCK 64 
  
char *escapechar = "exit"; 
int getmax(int); 
void removeClient(int); 
int  maxfdp1; 
int  num_chat = 0; 
int client_s[FD_SETSIZE]; 

int main(int argc, char *argv[])  { 
   char rline[MAXLINE], my_msg[MAXLINE]; 
   char *start = "Connected to chat_server \n"; 
   int i, j, n; 
   int s, client_fd, clilen; 
  
   fd_set  read_fds; 
   struct sockaddr_in  client_addr, server_addr; 
  
   if(argc != 2)  { 
      printf("Usage : %s port\n", argv[0]); 
      exit(0); 
   } 
  
   if((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)  { 
      printf("Server: Can't open stream socket.");    
      exit(0); 
   } 
    
   bzero((char *)&server_addr, sizeof(server_addr));  
      server_addr.sin_family = AF_INET;              
   server_addr.sin_addr.s_addr = htonl(INADDR_ANY); 
   server_addr.sin_port = htons(atoi(argv[1]));      
  
   if(bind(s,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0) { 
      printf("Server: Can't bind local address.\n"); 
      exit(0); 
   } 

        if (listen(s, 3) < 0) { 
          printf("Server: Listen error.\n"); 
          exit(0); 
        } 
   maxfdp1 = s + 1; 
  
   while(1) { 
      FD_ZERO(&read_fds); 
      FD_SET(s, &read_fds); 
      for(i=0; i<num_chat; i++)  FD_SET(client_s[i], &read_fds); 
         maxfdp1 = getmax(s) + 1; 
         
         // input 만 검사합니다. 즉 접속요청이나 데이타가 들어왔는지만 검사..
         // TIMEVAL 값이 널이므로 이벤트가 발생할때까지 BLOCK 합니다.
         // 첫번째 인자는 Max Descriptor + 1 값을 넣으면 됩니다.
         // 보통은 이렇게 돌때마다 셋팅하진 않고
         // 소켓을 생성할때마다 소켓 Descriptor 번호를 검사해서 최고 높은 번호보다
         // 크면 갱신해주면서 사용합니다.
         // 즉 socket = accept(...);
         // if( socket >= maxfds )
         //		maxfds = socket + 1;
         // 이런식으로 말이죠.

      if (select(maxfdp1, &read_fds, (fd_set *)0, (fd_set *)0,(struct timeval *)0) < 0) { 
         printf("select error <= 0 \n"); 
         exit(0); 
      } 
      
      // 서버소켓에서 readset 이 발생하면 접속요청입니다. 접속 처리를 해줍니다.
      if(FD_ISSET(s, &read_fds)) { 
         clilen = sizeof(client_addr); 
         client_fd = accept(s, (struct sockaddr *)&client_addr, &clilen); 
         if(client_fd == -1) { 
            printf("accept error\n"); 
            exit(0); 
         } 
         client_s[num_chat] = client_fd; 
         num_chat++; 
         send(client_fd, start, strlen(start), 0); 
         printf("%d번째 사용자 추가.\n", num_chat); 
      } 
      
      // 차일드 소켓에서 readset 이 발생하면 데이타가 들어온것이므로 recv로 받아줍니다.
      for(i = 0; i < num_chat; i++)  { 
         if(FD_ISSET(client_s[i], &read_fds)) { 
         	
         	// recv 리턴값이 0 이면 greaceful close 입니다.
         	// 만약 비동기 소켓이고 리턴값이 -1 이면 에러값을 검사하여 에러처리를 해주어야 하는데 여기서는
         	// errno 를 검사하여 EAGAIN 이나 EWOULDBLOCK 인 경우는 접속을 끊으면 안됩니다.
            if((n = recv(client_s[i], rline, MAXLINE, 0))  <= 0)  { 
               removeClient(i); 
               continue; 
            }    
            if(strstr(rline, escapechar) != NULL) { 
               removeClient(i); 
               continue; 
            } 
            rline[n] = '\0'; 
            for (j = 0; j < num_chat; j++)  send(client_s[j], rline, n, 0); 
            printf("%s\n", rline); 
         } 
      }     
   }         
}           

void removeClient(int i) { 
   close(client_s[i]); 
   if(i != num_chat-1) client_s[i] = client_s[num_chat-1]; 
      num_chat--; 
      printf("채팅 참가자 1명 탈퇴. 현재 참가자 수 = %d\n", num_chat); 
} 

int getmax(int k) {
   int max = k;    
   int r; 
   for (r=0; r < num_chat; r++) { 
      if (client_s[r] > max ) max = client_s[r]; 
   } 
   return max; 
}

와우 섭다되서 잠시 들렸다가 ^^;;;

전 와우하러 히히

saxboy의 이미지

select()는 생각보다 사용하기가 꽤 어렵습니다. 먼저 getch() 비슷한 함수를 select() 를 이용해서 한번 구현해보신 후에 위의 코드를 다시 보신다면 좀 더 잘 이해가 되지 않을까 싶군요.

boardholic의 이미지

차근차근 중간에 printf()함수를 집어넣어서 돌아가는 과정을 살펴보니까

이제 좀 이해가 가네요^^;

근데 select()함수 가 사용되면 FD_SET() 에서 설정한 파일 지시자는

사라지는건가요? 정확히 어떤 부분에서 사라지는지 궁굼합니다.

그리고 첫번째 클라이언트에서 접속후 두번째 클라이언트가 접속하면

처음에 열린 소켓은 그대로 사용되고 accept()함수부분부터 바로 돌아가는

건가요? 나름대로 분석하다 보니까 그렇게 되는거 같은데 도움 부탁드립니다.

나에게 있어 한계란 없다.
무한한 꿈을향해 나아간다.

litdream의 이미지

에~~ 우선, 다음 튜토리얼을 한번 읽으시길 권고 드립니다.

http://www.ecst.csuchico.edu/~beej/guide/net/

네트웍 프로그래밍에 있어서 그래도 가장 기초를 잘잡아주는 문서가 아닌가
하고요,

그리고, select 와 함께 사용해야할 매크로들(FD_SET 시리즈들..) 을 한번
알아보시길 바랍니다.

select() 같은 함수를 그냥 쉽게 배우려고 하면, 그런 절차를 수십번 겪어서,
결국은 select() 한번 고생한것과 같은량의 시간을 투자하셔야 합니다.
( 어쩌면 제대로 한번딱 고생하고, 잘 이해하는게 더 나을지도 모릅니다. )

삽질의 대마왕...

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.