아래 소스는 인터넷에 떠돌고 있는 리눅스용 select 서버를 가져와서 윈도우 용으로 수정하였습니다.

출처는 : http://vessel.tistory.com/95 입니다.

 

리눅스에선 서버만 만들어 놓고 클라이언트는 telnet으로 접속이 가능합니다만.

저는 이걸 윈도우 용으로 전환하면서 클라이언트 프로그램도 그냥 만들었습니다… orz

윈도우도 telnet 이 있긴하지만.. 이건 좀

 

하여간 소스 들어갑니다.

 

Server.cpp

#include <winsock2.h>

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

 

#define MAXLINE 512

#define MAX_SOCK 64

char *escapechar = "exit";

int maxfdp1;

int num_chat = 0;                                                                               // 채팅참가자수

int client_s[MAX_SOCK];

 

// 채팅탈퇴처리

void removeClient(int)                              // i번째참가자탈퇴

{

        closesocket(client_s[i]);                   // 해당소켓닫기

        if(i !=num_chat -1)

               client_s[i] = client_s[num_chat -1]; 

        num_chat --;                                // 채팅참가자수1명줄이기

        printf("채팅참가자1명탈퇴. 현재참가자수= %d\n",num_chat);

}

 

// client_s[]내의최대소켓번호얻기(k는초기치)

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;

}

 

int main()

{

        char rline[MAXLINE], my_msg[MAXLINE];               // buffer 생성

        char *start = "Connetctd to chat_server\n";         // 최초연결시보내는메세지

        int i,j,n;

        SOCKET s, client_fd, clilen;

        fd_set read_fds;

        struct sockaddr_in client_addr, server_addr;        // 소켓주소구조체선언

        WSADATA wsa;                                        // 윈소켓

 

        // 윈속초기화

        if(WSAStartup(MAKEWORD(2,2), &wsa) != 0)

               exit(1);

 

        // 초기소켓생성

        if((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)

        {

               printf("Server: Can't open stream socket.");

               exit(0);

        }

        // server_addr 구조체의내용세팅

        ZeroMemory(&server_addr, sizeof(server_addr));       //초기화(0으로채운다)

        server_addr.sin_family = AF_INET;

        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

        server_addr.sin_port = htons(9000);

         
        // bind()
       
if(bind(s,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0 )     
        {

               printf("Server: Can't bind local address.\n");

               exit(0);

        }

 

        //클라이언트로부터연결요청을기다림

        listen(s,SOMAXCONN);                    // listen 접속대기(큐크기5)

        maxfdp1 = s + 1;                        // 최대소켓번호+1

        while(1)

        {

               FD_ZERO(&read_fds);              // fd_set  초기화(0으로세팅) 

               FD_SET(s,&read_fds);       // 소켓에해당하는file discripter1로세팅

               for(i=0; i<num_chat; i++)        // ""

                       FD_SET(client_s[i], &read_fds);

               maxfdp1 = getmax(s) +1;         // maxfdp1 재계산

 

               if(select(maxfdp1,&read_fds, (fd_set  *)0, (fd_set *) 0, (struct timeval *) 0) < 0)     // select setting

               {

                       printf("select error =< 0 \n");

                       exit(0);

               }

               if(FD_ISSET(s, &read_fds))  // read_fds s에해당하는비트가세팅되있다면

               {              

                       int addrlen;        // 의미없음ㅡㅡ.. 단순주소길이저장변수

                       addrlen = sizeof(client_addr);

                      

                       // == 연결요청이있다면

                       clilen = sizeof(client_addr);

                       client_fd = accept(s, (struct sockaddr *)&client_addr, &addrlen);                 
                       
//  accept()

                       if(client_fd == -1)

                       {

                              printf("accept error \n");

                              exit(0);

                       }

 

                       // 채팅클라이언트목록에추가

                       client_s[num_chat] = client_fd;

                       num_chat++;                       // 채팅참가자수1 증가

                   
    // "Connetctd to chat_server" 를접속한클라이언트에보냄                    
                       
send(client_fd,start,strlen(start),0);     
                   
   printf("%d번째사용자추가,\n",num_chat);

               }

 

               //클라이언트가보낸메시지를모든클라이언트에게방송

               for(i = 0; i<num_chat; i++)              // 모든클라이언트검색

               {

                       memset(rline,'\0',MAXLINE);      // buffer 초기화

                       if(FD_ISSET(client_s[i],&read_fds)) 
                     
// read
해당소켓에서read 할것이있는지확인

                       {

                              if((n = recv(client_s[i],rline,MAXLINE, 0 )) <= 0)

                              {

                                      removeClient(i);

                                      continue;

                              }

                              // 종료문자처리

                              if(strstr(rline,escapechar) != NULL)
                              //"exit"
가입력되면종료시키기

                              {

                                      removeClient(i);

                                      continue;

                              }

                              // 모든채팅참가자에게메시지방송

                              rline[n] = '\0';

                              for(j = 0; j < num_chat; j++)

                                      send(client_s[j],rline,n,0);

                              printf("%s\n",rline);

                       }

               }

        }

        closesocket(s);                                                                                                                                                        // closesocket()

        WSACleanup();                                                                                                                                                  // 윈속종료

        return 0;

}

 

 

아래는 클라이언트 소스입니다.

Client.cpp

/***********************************************

        스레드를사용한채팅클라이언트

   원래루트는recv 전용이고

   스레드를생성해서사용자입력대기하다가,

   입력받게되면send 해주는클라이언트임.

***********************************************/

 

#include <winsock2.h>

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

 

#define MAXLINE     512

 

DWORD WINAPI ProcessInputSend(LPVOID arg);

 

char *escapechar = "exit";

char name[10]; // 채팅에서사용할이름

char line[MAXLINE], message[MAXLINE+1];

struct    sockaddr_in   server_addr;

SOCKET s;      // 서버와연결된소켓번호

 

int main()

{

        WSADATA wsa;                          // 윈속

 

        // 채팅참가자이름구조체초기화

        printf("채팅ID 입력: ");

        scanf("%s",name);

       

        // 윈속초기화

        if(WSAStartup(MAKEWORD(2,2), &wsa) != 0)

               exit(1);

 

        // 소켓생성

        if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)

        {

               printf("Client : Can't open stream socket.\n");

               exit(0);

        }

 

        // 채팅서버의소켓주소구조체server_addr 초기화

        ZeroMemory(&server_addr, sizeof(server_addr));

        server_addr.sin_family = AF_INET;

        server_addr.sin_port = htons(9000);

        server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

       

        // 연결요청

        if(connect(s, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)

        {

               printf("Client : Can't connect to server.\n");

               exit(0);

        }

        else

        {

               printf("서버에접속되었습니다. \n");

        }

 

        // 스레드생성

        HANDLE hThread;

        DWORD ThreadId;

        hThread = CreateThread(NULL, 0, ProcessInputSend, 0, 0, &ThreadId);

        if(hThread == NULL)

               printf("[오류] 스레드생성실패!\n");

        else

               CloseHandle(hThread);

 

        while(1)

        {

               ZeroMemory(message, sizeof(message));

               int size;

               if((size = recv(s, message, MAXLINE, 0)) == SOCKET_ERROR)

               {

                       printf("recv()");

                       exit(0);

               }

               else

               {

                       message[size] = '\0';

                       printf("%s \n", message);

               }

        }

        closesocket(s);                       // closesocket()

        WSACleanup();                 // 윈속종료

}

 

 

// 사용자입력부분

DWORD WINAPI ProcessInputSend(LPVOID arg)

{

        while(true)

        {

               if(fgets(message, MAXLINE, stdin))

               {

                       sprintf(line, "[%s] %s", name, message);

                       if (send(s, line, strlen(line), 0) < 0)

                              printf("Error : Write error on socket.\n");

                       if (strstr(message, escapechar) != NULL )

                       {

                              printf("Good bye.\n");

                              closesocket(s);

                              exit(0);

                       }

               }

        }

        return 0;

}

 

아래의 클래스를 사용해서 뻘짓을 하는 쓰레드 서버보단 이쪽이 훨씬 직관적이지 않습니까?

>_<..

여튼 이것도 좀 여러군데 손을 봐야 하긴 하지만..

이걸로 간단한 채팅이 되는 소스입니다.

 

클라이언트에서 쓰레드를 나눈이유는
recv처리와 send 처리를 동시에 하기 위해서 랍니다. 쓰레드를 나눔으로서
윈도우 프로그램으로 보면, 언제던지 server로부터 메시지를 받을수 있는 recv 프로세서와
언제던지 사용자가 키보드 입력을 받을수 있는 send 프로세서가 동시에 실행이 되는셈이죠

자세한 설명은.. 네트워크 프로그래밍 책의 대부분 뒷부분에 있는 select 설명을 보시는게 낳으실껍니다 >_<;;

+ Recent posts