윈도우 채팅 프로그램(서버)


이전에 Mutex 를 이용하여 만들었던 채팅 프로그램의 업그레이드(?)..

윈도우 모드로 서버 제작


[윈도우 채팅 서버 세팅]
















처음 프로젝트 생성시 Win32 Application 모드를 선택한다.  여기서 프로젝트 이름은 Server.  cpp 는 Main.cpp 생성


사실 API 나 MFC 를 조금이라도 안봤다면 여기 나오는 부분이 좀 이해가 안갈수도 있다. 여기선 그냥 이런 역활을 하는 구나 라고만 이해하면 될거 같다.













단축키 [CTRL] + [R]  /* Menu - Insert - Resource */ 를 누르게 되면 이와 같은 창이 나온다. Dialog 창을 선택하고 New 를 눌러준다.


















이런 화면이 나오게 된다. 왼쪽에 네모난게 Dialog .. 즉 대화상자이다. API 나 MFC 부분에서 좀더 자세히 공부를 하면 된다. 


서버는 하얀색창에서 접속된 클라이언트. 종료된 클라이언트를 출력하기 위함이니 단순하게 만들면 된다. ( 좀더 멋지게 꾸며도 상관없다.. )

OK 와 Cancel 을 선택한뒤 Delete 해주자.

Dialog 가 선택된 곳에서 단축키 [ALT] + [Enter]  /* 마우스 오른쪽 버튼 - Properties */ 를 눌러준다.







ID 는 이 대화상자의 이름이다. 여기선 IDD_CHATSERVER 로 설정.    Caption 은 대화상자에 위에 나오는 글씨이다. Server 로 설정(아무거나 해주면 된다)

Font 에서 원하는 글꼴을 설정해주어도 된다.. ( 항상 습관적으로 돋움 체를 선택하고 있다.. )


그리고 오른쪽을 보면  가로 2줄과 세로로 길게 아이콘이 정렬되어 있는 것이 있다. 거기에서 ListBox 란 아이콘을 선택한다. (여기선 오른쪽 위에서 5번째였다. 아닐수도...)

ListBox를 선택하고 Dialog 에 클릭을 하면 아래와 같이 된다.


















ListBox 역시 단축키 [ALT] + [Enter] 로 이름을 설정해주자.







ID : IDC_LISTCHAT 으로 설정


Dialog 와 ListBox의 크기를 간단한 드래그로 원하는 대로 조정해주면 된다.


* 서버 최종 모습













서버는 단순하니 요정도만..^^    저장을 누르게 되면 다른이름으로 저장 이 뜨면서 기본으로 Script1.rc 로 저장할건지 물어본다.

이때 프로젝트를 생성한 폴더가 맞는지 확인하고 프로젝트에 생성한 폴더에 그대로 저장을 하면 된다.









리소스는 이와 같이 저장을 하게 된다. 앞으로 다른 리소스를 만들게 되면 계속 저기에 저장이 될것이다. 이거는 API 때 자세히 공부하면 된다.

저장을 했다고 끝이 아니다. WorkSpace 창을보면 Source Files 에는 Main.cpp 가 있다. Header Files 폴더를 클릭하면 아무것도 나오질 않는다.










Header Files 에서 마우스 우클릭을 하고 Add Files to Folder 를 선택한다.










Insert Files into Project     을 보면 resource.h 파일이 생겼다. 이 헤더파일에 전에 만든 Dialog 정보가 들어있다.

resource.h 파일을 선택하고 다시 Add Files to Folder 를 선택해 Script1.rc(Resource Files 폴더)도 선택해주고 저장하면 된다.












단축키 [ALT] + [F7] /* Menu - Project - Settings */

C/C++ - Category : Code Generation                    설정

           Use run-time library : Multithreaded DLL    설정












단축키 [ALT] + [F7]  /* Menu - Project - Settings */

Link - Obect/library modules : WS2_32.lib 추가


이걸로 기본 세팅이 끝난거다..



[윈도우 채팅 서버 소스]


서버 의 기본골격은 도스 기반에서 만든 Mutex 방식과 비슷하다. 다만 윈도우 모드를 사용하면서 필요한 함수들이 있다.

이런 함수들은 API 나 MFC 에서 보면 공부하면 된다. 여기선 그냥 간단히 이해하면 될 것 같다.


#include <stdio.h>

#include <stdlib.h>

#include <process.h>

#include <windows.h>

#include "resource.h"                                    // Header Files 에 추가한 resource.h 를 선언해주어야 한다.


#define BUFSIZE 1024


SOCKET g_ServSocket  = NULL ;                      

SOCKET g_ClientSocket = NULL ;                     

SOCKET g_ClntSock[10] ;                                // 지금 서버에 접속하는 클라이언트는 최고 10개 까지 접속할수 있다. (늘려줘도 상관없다.)


HWND g_hwndList = NULL ;                              // 대화상자에서 만든 하얀색(ListBox)부분.


SOCKADDR_IN servAddr ;

SOCKADDR_IN clientAddr ;

HANDLE hThread1, hThread2 ;

HANDLE hMutex ;

DWORD dwThreadID1, dwThreadID2 ;


int clientAddrSize ;

int clntNum = 0 ;


BOOL CALLBACK DlgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) ;   // 메시지 처리함수

void AddStringToList( HWND hWndList, char *szItem ) ;                                            // 화면에 글을 출력(printf 로 출력하는게 아니다...)

void OnClose( HWND hWnd ) ;                                                                              // 대화상자 종료

BOOL OnInitDialog( HWND hWnd, HWND hWndFocus, LPARAM lParam ) ;                          // 대화상자 초기화

unsigned int __stdcall ThreadMain( void *arg ) ;                                                     // 쓰레드 메인( 이전 도스에서 main 함수이다.)

void SendMsg( char *str, int len ) ;                                                                      // 모든 클라이언트에게 메시지를 전송한다.

DWORD WINAPI ClinetConn(void *arg);                                                                    // 메시지를 받으면 SendMsg 함수 호출. 종료 처리


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// WinMain :  윈도우 모드는 프로그램의 시작점이 main 이 아니라 WinMain 이다.

// hInstance  : 프로그램의 인스턴스 핸들이다. 인스턴스는 클래그사 메모리에 실제로 구현된 실체를 의미한다.

//                  자세한건 API 에서 공부하고 여기선 그냥 프로그램이 실행될때 이를 인스턴스라 생각하자.

// hInstance 는 많은 함수들이 요구하기 때문에 g_hlnst(전역) 에 값을 넣은후 g_hlnst를 사용한다. 여기선 pass ~ 나중에 공부..^^

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,

                                          LPSTR lpCmdLind, int nCmdShow )

{

     WSADATA wsaData ;

     WSAStartup( MAKEWORD( 2, 2 ), &wsaData ) ;                       

     // 쉽게 대화상자를 나오게 한다고 하자...

     DialogBox( hInstance, MAKEINTRESOURCE( IDD_CHATSERVER ), NULL, DlgProc ) ;        // DlgProc .. CALLBACK 메시지 처리함수이다.


     WSACleanup();

     return 0;

}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// 이전에 도스에서 main 역활을 하던 것이다. main 자체가 쓰레드화 되서 계속 실행되는 것이다.

// 이 함수는 OnInitDialog 함수에서 호출한다.(쓰레드 생성)    내용은 전에 main 과 동일하다.

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

unsigned int __stdcall ThreadMain(void *arg)

{

     char *servPort = "9000";

     char ServMsg[100];


     hMutex = CreateMutex( NULL, FALSE, NULL);                                                      // Mutex 생성
     if( hMutex == NULL)     AddStringToList(g_hwndList, "CreateMutex error");              // AddStringToList 함수는 대화상자에 출력해주는 함수


     g_ServSocket = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
     if( g_ServSocket == INVALID_SOCKET )     AddStringToList(g_hwndList, "socket error");
   
     memset( &servAddr, 0, sizeof( servAddr));
     servAddr.sin_family = AF_INET;
     servAddr.sin_addr.s_addr = htonl( INADDR_ANY );
     servAddr.sin_port = htons(atoi( servPort ));


     if(bind( g_ServSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)    AddStringToList(g_hwndList, "bind error");

     if( listen( g_ServSocket, 5) == SOCKET_ERROR )     AddStringToList(g_hwndList, "listen error" );
   
     AddStringToList(g_hwndList, "서버 대기중입니다.");
 
     while(1)
     {
          clientAddrSize = sizeof(clientAddr);
          g_ClientSocket = accept( g_ServSocket, (SOCKADDR*)&clientAddr, &clientAddrSize );
 
          WaitForSingleObject(hMutex, INFINITE);
          g_ClntSock[clntNum++] = g_ClientSocket;
          ReleaseMutex(hMutex);


          sprintf(ServMsg, "접속된 클라이언트 : ");
          strcat(ServMsg, inet_ntoa( clientAddr.sin_addr) );
          AddStringToList(g_hwndList, ServMsg);

          // 쓰레드 . ClinetConn . 

          hThread1 = (HANDLE)_beginthreadex( NULL, 0, (unsigned int(__stdcall *)(void*))ClinetConn,
                                                                  (void*)g_ClientSocket, 0, (unsigned*)&dwThreadID1);
     }
 
     closesocket(g_ServSocket);
     return 0 ;
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// 메시지 처리함수란 메시지가 발생할때 프로그램의 반응을 처리하는 것이다. 

// uMsg 는 어떤 메시지인가..  wParam 과 lParam 은 메시지에 대한 부가정보이다.

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

BOOL CALLBACK DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
      switch(uMsg)
      {
           case WM_INITDIALOG: OnInitDialog( hWnd, hWnd, lParam );  break;         // 초기화
           case WM_CLOSE:  OnClose(hWnd); break;                                        // 대화상자 종료
      }
      return FALSE;
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// 대화상자 종료. 이게 없으면 대화상자에서 오른쪽 맨위 X 표시를 눌러도 안닫힌다..;;

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void OnClose(HWND hWnd)
{
      closesocket(g_ServSocket);                                
      EndDialog(hWnd, 0);
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// 대화상자(ListBox)에 글씨를 출력한다.

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void AddStringToList(HWND g_hwndList, char *szItem)
{
     SendMessage(g_hwndList, LB_ADDSTRING, 0, (LPARAM)szItem);
     int nCount = (int)SendMessage(g_hwndList, LB_GETCOUNT, 0, 0);        // 칸을 밀리게 함.
 
     SendMessage(g_hwndList, LB_SETTOPINDEX, nCount-1, 0);
}

BOOL OnInitDialog( HWND hWnd, HWND hWndFocus, LPARAM lParam )
{
     //윈도우 핸들 구하기
     g_hwndList = GetDlgItem(hWnd, IDC_LISTCHAT);                               // 대화상자 핸들

     // 쓰레드 메인 함수.. 여기서 만들어 주고 있다.

     hThread2 = (HANDLE)_beginthreadex( NULL, 0, (unsigned int(__stdcall *)(void*))ThreadMain,
                            (void*)g_ServSocket, 0, (unsigned*)&dwThreadID2);
 
     return TRUE;
}


DWORD WINAPI ClinetConn(void *arg)
{
     SOCKET temp = (SOCKET)arg ;
     int strLen = 0;
     char message[BUFSIZE];
     char ServMsg[100];

     while((strLen = recv( temp, message, BUFSIZE, 0)) > 0)                          // recv  를 받게 되면
          SendMsg(message, strLen);                                                         // SendMsg 란 함수를 호출한다.
 
     WaitForSingleObject( hMutex, INFINITE);                                               // 임계영역
     for( int i = 0 ; i < clntNum; i++)
     {
          if( temp == g_ClntSock[i])                                                            // 종료되는 클라이언트
          {
               sprintf( ServMsg, "%d", i);
               strcat( ServMsg, " 번 클라이언트 종료");
               AddStringToList(g_hwndList, ServMsg);
     
               for( ; i < clntNum -1 ; i++)
                    g_ClntSock[i] = g_ClntSock[i+1];                                          

               break;
          }
     }
     clntNum--;                                                                                     // 전체 클라이언트 갯수를 줄여준다.

     ReleaseMutex(hMutex);
     closesocket(temp);
     return 0 ;
}


void SendMsg(char *str, int len)
{
     WaitForSingleObject( hMutex, INFINITE);

     for( int i = 0 ; i < clntNum ; i++)                                                          // Clientconn 함수에서 호출.  받은 메시지를
          send( g_ClntSock[i], str, len, 0 );                                                  // 전체 클라이언트에게 전송한다.

     ReleaseMutex(hMutex);
}

Posted by 나비:D
:
BLOG main image
by 나비:D

공지사항

카테고리

분류 전체보기 (278)
Programming? (0)
---------------------------.. (0)
나비의삽질 (5)
Application (177)
SQL (51)
Web (27)
etc. (14)
Omnia (0)
---------------------------.. (0)

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

달력

«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Total :
Today : Yesterday :