'소켓'에 해당되는 글 3건

  1. 2014.02.14 윈도우 소켓프로그래밍 C++ 기본 소스 1
  2. 2013.05.14 채팅프로그램 메신저 소스
  3. 2013.04.25 C# TCP 소켓통신
네트워크2014. 2. 14. 16:02
// WinServer.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
// 

#include "stdafx.h"
#include <stdlib.h> 
#include <string.h>
#include <winsock2.h> 

#define PORT 9999
 
void ErrorHandling(char* message);
 
int _tmain(int argc, char** argv)
{ 
    WSADATA        wsaData; 
    SOCKET        hServSock; 
    SOCKET        hClntSock; 
    SOCKADDR_IN    servAddr; 
    SOCKADDR_IN clntAddr; 
    int    szClntAddr; 
    char message[] = "Hello World\n";
 
    /*Windows Socket API(윈속 - 윈도우에서 TCP/IP기반의 소켓 프로그래밍을 지원하기 위해 만든 소켓 함수들의 모음)
      사용하기 위해서는 winsock.dll이 필요한데 이 dll파일을 로딩하기 위해서 WSAStartup함수를 호출해야한다. 
     
        int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData); 
        첫번째 매개변수 - 로드할 윈도우 소켓의 버전 
        두번째 매개변수 - WSAStartup함수가 성공적으로 실행되면 lpWASData에 소켓 정보를 채워서 되돌려준다. 
    */ 
    if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0) 
         ErrorHandling("WSAStartup() error!");
 
    //WORD MAKEWORD(BYTE bLow, BYTE bHigh); 
    //MAKEWORD는 함수가 아닌 매크로다. 입력된 두개의 BYTE값으로 WORD자료구조를 만든다.
 
    //서버 소켓의 생성 
    hServSock = socket(PF_INET, SOCK_STREAM, 0);
    if(hServSock == INVALID_SOCKET) 
        ErrorHandling("socket() error"); 

    memset(&servAddr , 0, sizeof(servAddr));
     servAddr.sin_family = AF_INET; 
    servAddr.sin_addr.S_un.S_addr =htonl(INADDR_ANY);  
    servAddr.sin_port = htons(PORT); 

/*서버 프로그램은 단지 기다리기만 할 뿐 연결 대상이 없어서 INADDR_ANY를 쓴다.
   이 값은 인터넷 주소로 하면 '0.0.0.0'인데, 모든 주소로 대기하겠다는 의미이다.
   모든 주소로부터 대기해야 하는 이유는 하나의 컴퓨터가 여러 인터넷 주소를 가질 수 잇기 때문이다.
   서버 컴퓨터는 둘 이상의 네트워크 인터페이스를 가지는 경우가 흔하다.*/
//소켓에 주소와 Port할당 if(bind(hServSock, (SOCKADDR*) &servAddr,sizeof(servAddr)) == SOCKET_ERROR) ErrorHandling("bind() error"); //'연결 요청 대기 상태'로의 진입 if(listen(hServSock, 5) == SOCKET_ERROR) ErrorHandling("listen() error"); szClntAddr=sizeof(clntAddr); //연결 요청 수락 hClntSock =accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr); if(hClntSock == INVALID_SOCKET) ErrorHandling("accept() error"); //데이터를 클라이언트에 전송한다. send(hClntSock, message, sizeof(message), 0); //연결 종료 closesocket(hClntSock); WSACleanup(); return 0; } void ErrorHandling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } /* sockaddr과 sockaddr_in의 관계 : 모든 소켓 함수들은 소켓의 유형에 관계없이 sockaddr만을 받아들인다. 소켓 유형이 달라지면 구조체 역시 달라진다. 소켓 함수는 이 문제를 해결하기 위해 매개변수를 형변환하고 데이터 크기를 알려준다. struct sockaddr { unsigned short sa_family; char sa_data[14]; } struct sockaddr_in { short sin_family; ->주소 체계를 지정한다. AF_UNIX(시스템 내부영역에서 프로세스와 프로세스간 통신위해 사용) AF_INET(인터넷 영역에서 물리적으로 서로 멀리 떨어진 컴퓨터 사이의 통신 위해 사용) AF_IPX(Novell Internet Protocol..윈도우 비스타부터 지원 안함) AF_INET6(AF_INET과 같으나 IPv6사용) AF_X25(아마추어 라디오용 프로토콜) unsigned short sin_port; ->클라이언트에서는 연결할 서버의 포트번호이고 서버에서는 기다릴 포트 번호이다. struct in_addr sin_addr; ->연결할 인터넷 주소를 지정하기 위해서 사용한다. char sin_zero[8]; } sin_family와 sa_family는 완전히 같은 값이다. socket함수 원형 int socket(int domain, int type, int protocol); 첫번째인자 도메인 : 어떠한 영역에서 통신할 것인지 지정하는 것 두번째인자 타입 : 데이터 통신에서 사용할 프로토콜 유형을 지정하기 위해 사용 SOCK_STREAM - 연결 지향의 TCP/IP기반 통신에서 사용 SOCK_DGRAM - 데이터그램 방식의 UDP/IP기반 통신에서 사용 SOCK_RAW - TCP/IP를 직접 다룰 필요가 있을때 사용하는 프로토콜 세번째인자 프로토콜: 호스트 간 통신에 사용할 프로토콜을 결정 IPPROTO_TCP - TCP프로토콜로 AF_INET도메인과 SOCKET_STREAM 유형과 함께 사용 IPPROTO_UDP - UDP프로토콜로 AF_INET도메인과 SOCKET_DGRAM 유형과 함께 사용 반환값 : -1 실패 0 이상이면 성공 이 int형 값은 '단순한 숫자'가 아닌 소켓 객체를 '가리키는 숫자'다. 때문에 이 반환값을 socket descriptor 혹은 소켓 지정 번호 라고 부른다. 이는 리눅스에서의 socket이고 winsock은 소켓지정번호 대신에 소켓 객체인 SOCKET을 반환하다. */ /*bind함수로 소켓 설정하기 socket함수를 이용해서 만들어진 소켓은 인터넷에서 연결 요청을 받거나 보내기 위한 목적으로 사용한다. 전화기를 전화망에 연결한 상태라고 볼 수 있다. 그런데 아직 전화번호를 할당하지 않았다. 소켓을 생성한 다음 전화번호에 해당하는 IP주소와 서비스를 위한 포트를 할당해야한다. IP주소를 할당해야 원하는 컴퓨터를 찾아갈 수 있고, 포트를 지정해야 원하는 서비스 프로그램에 연결을 시도할 수 있다. bind 함수를 이용해서 소켓에 IP와 포트를 할당할 수 있다. bind함수는 서버 프로그램에 포트를 고정시키기 위해서 사용한다. 클라이언트는 연결할 인터넷 서비스(서버)의 포트 번호만 알고 있으면 된다.(즉, 자신의 포트 번호는 몰라도된다) 그러니 클라이언트에서는 bind함수를 사용할 필요가 없다. int bind(int sockfd, struct sockaddr* my_addr, socklen_t addrlen); 첫번째 인자 sockfd - 앞서 socket함수로 생성된 endpoint(듣기)소켓 두번째 인자 my_addr - IP주소와 port번호를 저장하기 위한 변수가 있는 구조체 세번째 인자 addrlen - 두번째 인자의 데이터 크기 반환값 성공하면 0, 실패하면 -1 */ /*listen함수로 수신 대기열 생성하기. 만약 클라이언트 요청에 대한 처리가 미처 끝나기 전에 새로운 클라이언트가 요청하는 상황이 발생하면 서버는 클라이언트에 연결오류 메시지를 전송하고 연결을 거부해 버릴 것이다. 하지만 요청을 거부하는 것 보다는 앞의 클라이언트의 처리가 끝날때까지 잠시 기다리도록 하는게 나을것이다. 그래서 소켓은 수신 대기열을 만들어서 연결 요청을 관리한다. 클라이언트 요청은 먼저 수신 대기열로 들어가서 잠시 기다리게 된다. 그러면 이전 클라이언트 처리를 끝나친 서버가 대기열의 가장 앞에있는 클라이언트 요청을 가져와서 처리하게 된다. 수신대기열은 FIFO 형태의 자료구조이다. int listen(int sockfd, int backlog) 첫번째 매개변수 sockfd - socket함수를 수행한 결과 얻은 듣기 소켓의 소켓 지정번호이다. 두번째 매개변수 backlog - 수신 대기열의 크기이다. 수신 대기열의 크기는 '이게 정답이다' 라고 정해진 값이 없다.

네트워크 환경에 따라 경험적인 값을 적당히 사용한다. 함수 실행결과 성공하면 0 실패하면 -1을 반환한다. */ /*accept함수로 연결 기다리기 전화기에 해당하는 소켓을 생성했고 IP번호와 포트번호도 할당했다. 거기에 수신 대기열까지 생성했으니 서버는 수신 대기열에 있는 클라이언트 연결 요청만 확인하면 된다. accept함수는 대기중인 연결 요청이 있는지 확인한다. 클라이언트가 연결 요청을 하면 이 연결 요청은 먼저 수신 대기열에 들어가게 되는데 accept함수는 수신 대기열의 가장 앞에있는 연결 요청을 가져온다. int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen); 첫번째 매개변수 sockfd - socket함수를 이용해 생성된 소켓으로 클라이언트의 요청을 받아들이는 듣기 소켓이다. 두번째 매개변수 addr - accept함수가 성공하게 되면 연결된 클라이언트 주소와 포트 정보를 이

구조체에 복사해서 넘겨준다.

addr은 연결한 클라이언트의 정보를 확인하거나 로그를 남기기 위한 목적 등으로 사용할 수 있다. 세번째 매개변수 addrlen - sockaddr구조체의 크기이다. accept함수가 성공적으로 실행되면 0보다 큰 값을 반환한다. 이때 반환값은 '소켓 지정 번호'다. 만약 수신 대기열이 비어있다면, accept함수는 연결 요청이 들어올 때 까지 기다린다. 실제 '클라이언트와의 통신' 은 accept함수를 통해 반환된 이 connected socket(연결소켓)을 통해 이루어진다. 서버 프로그램은 계속해서 클라이언트 요청을 처리하기 때문에 accept함수는 일반적으로 while문 안에 위치한다. */

 

 

-------------------------------------------------------------------------------------------------------------------------------------

 

// WinClient.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
// 

#include "stdafx.h"
#include <stdlib.h> 
#include <string.h>
#include <winsock2.h> 

#define PORT 9999
#define IP     "127.0.0.1"
 
/*127.0.0.1은 loopback 네트워크 접속을 위한 표준 IP어드레스 입니다. 
이 말은 127.0.0.1에 접속하고자 할때 바로 자신의 컴퓨터에 loopback 하게 된다는 말 입니다.     
loopback은 자신에게 데이터를 송신하는 것이나 그와 같은 기능입니다. 
네트워크 카드에는 자신을 가르치는 loopback address가 설정되어 있어 이 주소에 송신된 데이터는  
카드내에 수신쪽에 수신되어 집니다. 기기가 정상으로 가동하고 있는지 아닌지를 확인하기 위해  
시험삼아 데이터를 보낼떼 사용되며 이런 것을 loopback device라고 합니다. 
그럼 127.0.0.1은 왜 필요 할까요?? 
127.0.0.1을 사용하면 네트워크 인터페이스(ethernet)을 사용하지 않고 OS(kernel)에서 직접 처리하게 됩니다. 
소켓 프로그램이란 것이 네트워크 카드를 통해서 서로 다른 시스템 간의 통신에도 사용할 수 있지만 
같은 시스템에서 서로 다른 프로세스간의 통신에도 사용할 수 있습니다. 
이때 굳이 네트워크 카드 필요없이 127.0.0.1을 사용할 수 있습니다! 
즉 로컬 시스템 내에서 프로세스간의 소켓 통신을 위한 IPC 수단으로 존재한다는 것 입니다. 
[출처] 네이버 검색 127.0.0.1| 작성자 ahnsh09*/ 

void ErrorHandling(char* message);
 
int _tmain(int argc, char** argv)
 { 
    WSADATA wsaData; 
    SOCKET hSocket; 
    char message[30];
     int    strLen; 
    SOCKADDR_IN    servAddr; 

    if(WSAStartup(MAKEWORD(2,2), &wsaData) !=0)
         ErrorHandling("WSAStartup() error!");
 
    hSocket = socket(PF_INET, SOCK_STREAM, 0);
     if(hSocket == INVALID_SOCKET) 
        ErrorHandling("hSocket() error"); 

    memset(&servAddr, 0 , sizeof(servAddr));
     servAddr.sin_family = AF_INET; 
    servAddr.sin_addr.S_un.S_addr = inet_addr(IP); 
    servAddr.sin_port = htons(PORT); 

    if(connect(hSocket, (SOCKADDR*)&servAddr,
         sizeof(servAddr)) == SOCKET_ERROR) 
        ErrorHandling("connect() error!"); 

    /* 
    윈도우는 소켓을 파일로 보지 않기 때문에 read,write 같은 파일 입출력 함수가 아닌 
    recv, send 함수를 사용한다. 
    int recv(SOCKET s, char* buf, int len, int flags); 
    int send(SOCKET s, char* buf, int len, int flags); 
    */ 

    strLen = recv(hSocket, message, sizeof(message) -1 ,0);
     if(strLen == -1)
         ErrorHandling("read() error!"); 
    message[strLen] = 0; 
    printf("Message from server : %s \n", message);
 
    closesocket(hSocket); 
    WSACleanup(); 
    return 0;
 
} 

void ErrorHandling(char* message)
 { 
    fputs(message, stderr); 
    fputc('\n', stderr); 
    exit(1); 
} 


'네트워크' 카테고리의 다른 글

환형큐  (0) 2014.09.17
Node.js 시작하기  (0) 2014.04.08
라우팅  (0) 2014.02.12
congestive collapse 대충 컨제스티브 컬랩스  (0) 2014.02.11
소켓 IO overlapped CallBack  (0) 2013.05.22
Posted by violetoz
네트워크2013. 5. 14. 08:48

 http://blog.naver.com/ree31206/46430095

채팅 프로그램(메신저) 소스

 

[WSAAsyncSelcet 모델]

 

WSAAsyncSelect 함수는 핵심적인 역활을 하게 된다.

윈도우 메시지 형태로 소켓과 관련된 네트워크 이벤트를 처리할 수 있다.

 

Point. 모든 소켓과 관련된 메시지는 하나의 윈도우 프로시저로 전달되므로 멀티 스레드를 사용하지 않고도 여러 소켓(다중접속)을 처리 할 수 있다.

// 쓰레드를 사용안하겠다는 말이다...

 

 

* WSAAsyncSelet 모델을 이용한 소켓 입.출력 절차

 

1. WSAAsyncSelect() 함수를 이용하여 소켓을 위한 윈도우 메시지와 처리할 네트워크 이벤트를 등록한다.

   // 소켓을 통해 데이터를 보내거나 받을수 있는 상황이 되면 특정 윈도우 메시지로 알려달라는 내용을 등록한다.

 

2. 등록한 네트워크 이벤트가 발생하면 윈도우 메시지가 발생하고 윈도우 프로시저가 호출된다.

 

3. 윈도우 프로시저에서는 받은 메시지 종류에 따라 적절한 소켓 함수를 호출하여 처리한다.

 

int WSAAsyncSelet(

     SOCKET s,                                    // 처리하고자 하는 소켓

     HWND hWnd,                                 // 메시지를 받을 윈도우를 나타내는 핸들

     unsigned int wMsg,                        // 윈도우가 받을 메시지. 소켓을 위한 메시지는 따로 정의되어 있지 않으므로 사용자 정의 메시지를 이용한다.

     long IEvent                                  // 처리할 네트워크 이벤트 종류를 마스크 조합으로 나타낸다.

) ;

 

 

* 네트워크 이벤트 상수값

 

 

 

 

 

 

 

 

 

* 네트워크 이벤트 상수값 사용 예

 

#define WM_SOCKETEVENT (WM_USER + 1)        // 사용자 정의 윈도우 메시지

 

WSAAsyncSelect( s, hWnd, WM_SOCKETEVENT, FD_ACCEPT ) ;

 

- WSAAsysncSelect() 함수를 사용하면 해당 소켓은 자동으로 넌 블로킹 소켓이 된다.

- 윈도우 메시지를 받으면 적절한 함수를 반드시 호출해야만 한다. 만약 그렇지 않으면 같은 윈도우 메시지가 발생하지 않는다.

  그러므로 윈도우 메시지가 발생하면 네트워크 이벤트에 대응하는 함수를 호출해야 하며 그렇지 않을 경우 직접 메시지를 발생시켜야 한다.

 

 

[소스]

 

#include <iostream>

#include <windows.h>

#include "resource.h"                                                  // 이전에 올라온 리소스 세팅이 끝난상태어야 한다..(이전에 올라온거 참고...)

#include <assert.h>                                                    // assert 사용

#include <list>                                                           // STL list 를 사용하기 위해서..  

#include <set>                                                          // STL set 을 사용하기 위해서...

 

using namespace std ;

 

#define WM_SOCKETEVENT (WM_USER+1)                        // EVENT define 처리.. WM_USER 란건 컴퓨터의 EVENT 수라고 생각하면된다. 그 번호 +1

#define CHATNAMESIZE 100

 

// PACKETFLAG 안에 있는 값의 순서는 상관없다. 이벤트 처리시 사용할려고 변수 생성

enum PACKETFLAG{ PACKET_STRING, PACKET_CLIENTINFO, PACKET_CHATNAME_CHANGE, PACKET_CHATNAME_FIRST};

 

struct REQUESTINFO{

     DWORD dwIp ;

     int nPort ;

} ;

 

struct CLIENTDATA{
      SOCKET hsocket;
      char pChatName[CHATNAMESIZE];
      REQUESTINFO RequestInfo;
};


list<CLIENTDATA*> g_ClientDataList;
list<CLIENTDATA*>::iterator g_it;
set<int> g_SerialSet;

 

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK DialogProc(HWND hwndDlg, UINT uMSG, WPARAM wParam, LPARAM lParam);
BOOL Win_Create(HINSTANCE hInstance, int nCmdShow);
int WinRun();
BOOL OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct);
void OnPaint(HWND hWnd);
BOOL OnDlgInitDialog(HWND hwndDlg, LPARAM lParam);
void OnDlgCommand(HWND hwndDlg, WPARAM wParam);
void OnCommand(HWND hWnd, WPARAM wParam, HWND hwndCtl);
void OnDestroy(HWND hWnd);
void OnSocketEvent(HWND hWnd, SOCKET socket, LPARAM lParam);
void OnAccept(HWND hWnd, SOCKET socket);
void OnRead(HWND hWnd, SOCKET socket);
void OnClose(HWND hWnd, SOCKET socket);
int SendPacket( SOCKET socket, PACKETFLAG PacketFlag, void *pData, int nDataSize);
BOOL ReadPacket(HWND hWnd, SOCKET socket, int *pDataSize, PACKETFLAG *pPacketFlag, char **ppData);
void ProcessServerPacket( SOCKET socket, PACKETFLAG PacketFlag, char* pData, int nDataSize);
void ProcessClientPacket( HWND hWnd, SOCKET socket, PACKETFLAG PacketFlag, char *pData, int nDataSize);
void AddStringToList(HWND g_hwndList, char *szItem);

 

HINSTANCE g_hlnst;
char dwIP[20];
SOCKET g_ServSocket  = NULL ;                      
SOCKET g_ClientSocket = NULL ;    
char *servPort = "9000";   
HWND  g_hWnd;
char g_pChatName[20];

 

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

// WinMain...   WinCreate 윈도우 모드 생성.  API 를 아직 안봤다면 그냥 하얀창을 만들어준다고 생각하면 된다..

// 동기화후 윈도우 모드를 생성하고 WinRun 을 실행한다..  WinRun 이 프로그램이 돌아가는 것이라고 생각하면 된다.. 쉽게 쉽게~~~

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

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevinstance,
       LPSTR IpCmdLine, int nCmdShow)
{
      WSADATA wsaData;
      WSAStartup(MAKEWORD(2, 2), &wsaData);
 
      Win_Create(hinstance, nCmdShow);                        
 
      return WinRun();
 
     WSACleanup();
}

 

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

// CALLBACK 함수는 메시지 처리함수이다. 어떤 메시지(message) 와 메시지에 대한 부가정보(wParam, lParam) 에 맞게

// 각각의 함수들을 호출해 주고 있다. 윈도우 모드에 대해서 발생하는 메시지라고 생각하면 된다.

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

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, 
        WPARAM wParam, LPARAM lParam)
{
     switch ( message )
     { 
          case WM_SOCKETEVENT: OnSocketEvent(hwnd, (SOCKET)wParam, lParam); return 0 ;
          case WM_CREATE: OnCreate( hwnd,(LPCREATESTRUCT)lParam);
          case WM_PAINT: OnPaint(hwnd); return 0 ;
          case WM_COMMAND: OnCommand(hwnd, wParam, (HWND)lParam); return 0 ;
          case WM_DESTROY:  OnDestroy(hwnd); return 0;
     }
     return DefWindowProc (hwnd, message, wParam, lParam);                              // Wndproc 에서 처리하지 않는 기본적인 메시지에 대한 처리
}

 

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

// 실제 윈도우(창) 가 돌아가는 곳이다. WinMain 에서 호출해줬었다. while 문은 TRUE 일때 계속 실행하는 것이다.

// 메시지가 GetMessage 로 들어오게 되는데 프로그램을 종료하라는 WM_QUIT 메시지가 들어오면 FALSE 를 리턴. 나머지는 TRUE 를 리턴한다.

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

int WinRun()
{
     MSG msg;                                                    // 메시지 구조체
 
     while ( GetMessage( &msg, 0, 0, 0 ) )               // GetMessage 는 메시지큐(메시지 임시 저장지역) 에서 메시지를 읽어 들인다.
     {
          TranslateMessage( &msg );                       // 키보드에 값이 들어왔을때 그 메시지를 만드는 역활
          DispatchMessage ( &msg );                       // 메시지 큐에서 꺼낸 메시지를 메시지 처리함수 WndProc 로 전달
     }  
 
     return 0 ;
}

 

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

// 윈도우를 만드는 곳이다.  WinMain 에서 호출하고 있다.

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

BOOL Win_Create(HINSTANCE hInstance, int nCmdShow)
{
     g_hlnst = hInstance;                                     // hInstance : 프로그램의 인스턴스 핸들..   핸들을 다른곳에서 사용하니 전역...
                                                                     // Instance 란 클래스가 메모리에 실제로 구현된 실체를 의미한다. 그냥 쉽게 프로그램 실행되는 것..

     //------------------------------------------------------------------

     // 윈도우 클래스(구조체) 제작. 초기화 .  API 를 공부하자....

     //------------------------------------------------------------------

     WNDCLASSEX wnd;
     wnd.cbSize = sizeof( wnd );                                                  // 윈도우의 사이즈
     wnd.style = CS_HREDRAW | CS_VREDRAW;                                // 윈도우의 스타일 
     wnd.lpfnWndProc = WndProc;                                                // 메시지 처리함수 지정. 
     wnd.cbClsExtra = 0 ;                                                           // 일종의 예약 영역, 특수한 목적에 사용되는 여분의 공간. 보통 0 으로 지정
     wnd.cbWndExtra = 0 ;                                                          // 위와 동일..;;
     wnd.hInstance = hInstance;                                                  // 윈도우 클래스로 등록하는 프로그램의 번호
     wnd.hIcon = LoadIcon(NULL,IDI_APPLICATION);                            // 윈도우가 사용할 아이콘 지정
     wnd.hCursor = LoadCursor( NULL, IDC_ARROW );                        // 윈도우가 사용할 커서 지정
     wnd.hbrBackground = CreateSolidBrush( RGB( 173, 205, 216));    // 윈도우 배경색상
     wnd.lpszMenuName = MAKEINTRESOURCE(IDR_CHATTINGMENU);       // 메뉴. ( 세팅에서 만든 IDR_CHATTINGMENU )
     wnd.lpszClassName = "Hello";                                                 // 클래스 이름
     wnd.hIconSm = NULL;                                  
 

     //------------------------------------------------------------------

     // 운영체제에 클래스 등록

     //------------------------------------------------------------------

     RegisterClassEx( &wnd );
    

    

     //------------------------------------------------------------------

     // 윈도우 만들기.   창의 사이즈 . 메뉴 스타일. 크기등... 인자들을 확인하면 이해할수 있다.

     //------------------------------------------------------------------

     g_hWnd = CreateWindow( "Hello", "Hello",     
                                        WS_SYSMENU | WS_VISIBLE | WS_MINIMIZEBOX,
                                        200, 200, 320, 480, NULL, NULL, hInstance, NULL );  
 
     if( !g_hWnd )
          return FALSE;

     //------------------------------------------------------------------

     // 윈도우 화면에 출력

     //------------------------------------------------------------------

     ShowWindow( g_hWnd, SW_SHOW );
 
     return 0 ;
}

 

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

// 대화 상자 실행( 서버 / 클라이언트 선택 ) 서버와 클라이언트 선택에 맞게 세팅을 해주는 곳이다.

// 하나의 프로그램에서 서버역활과 클라이언트 역활을 할수 있기에 각각에 맞게 설정( if 검사 )

// 여기서 한가지...  IDC_CHAT . IDC_SAVE.  IDC_SEND 를 define 해주어야 한다.  위에서 안되어잇지만.. 영역별로 메시지 처리를 위하여

// 임의로 설정할려고 만든것이니 숫자가 안겹치게 define 해주면 된다.  resourse.h 파일에 가서 다른 IDC_~~  밑에 그 다음 숫자로 추가해준다. 3개 모두...

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

BOOL OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)
{
     DialogBoxParam(g_hlnst, MAKEINTRESOURCE(IDD_SELECT), hWnd, DialogProc, IDD_SELECT);
 
     CreateWindow("listbox", NULL, 
                         WS_CHILD | WS_VISIBLE | WS_VSCROLL,
                         7, 54, 300, 315, hWnd, (HMENU)IDC_SAVE, g_hlnst, NULL);
    
     CreateWindow("edit", NULL,
                         WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | ES_MULTILINE,
                         7, 375, 307-78, 43, hWnd, (HMENU)IDC_CHAT, g_hlnst, NULL);
    
     CreateWindow("button", "보내기",
                         WS_CHILD | WS_VISIBLE | WS_DISABLED,
                         237, 385, 68, 40, hWnd, (HMENU)IDC_SEND, g_hlnst, NULL);
    
     HFONT font = CreateFont( 12, 0, 0, 0, FW_NORMAL, 0, 0, 0, HANGEUL_CHARSET, 3, 2, 1, VARIABLE_PITCH | FF_ROMAN, "돋움체" );
    
     SendMessage(GetDlgItem(hWnd, IDC_CHAT), WM_SETFONT, (WPARAM)font, (LPARAM)TRUE);
     SendMessage(GetDlgItem(hWnd, IDC_SEND), WM_SETFONT, (WPARAM)font, (LPARAM)TRUE);
     SendMessage(GetDlgItem(hWnd, IDC_SAVE), WM_SETFONT, (WPARAM)font, (LPARAM)TRUE);


     // 서버인 경우
     if( !strcmp(dwIP, "127.0.0.1" ) )
     {
          g_ServSocket = socket( PF_INET, SOCK_STREAM, 0 );
  
          SOCKADDR_IN servAddr;
          memset(&servAddr, 0, sizeof(servAddr));
          servAddr.sin_family = AF_INET;
          servAddr.sin_addr.s_addr = INADDR_ANY;
          servAddr.sin_port = htons(atoi(servPort));
  
          bind( g_ServSocket , (SOCKADDR*)&servAddr, sizeof(servAddr));
  
          listen( g_ServSocket, SOMAXCONN );
          // 서버 소켓을 넌 블로킹 소켓으로 지정

          WSAAsyncSelect( g_ServSocket, hWnd, WM_SOCKETEVENT, FD_ACCEPT) ;

     }
 
     g_ClientSocket = socket( PF_INET, SOCK_STREAM, 0);
     // 자기 자신도 연결이 가능하다..
     SOCKADDR_IN servAddr;
     memset(&servAddr, 0, sizeof(servAddr));
     servAddr.sin_family = AF_INET;
     servAddr.sin_addr.s_addr = inet_addr(dwIP);
     servAddr.sin_port = htons(atoi(servPort));
 
     if( connect( g_ClientSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
          MessageBox(hWnd, "클라이언트 실행 오류", "클라이언트 실행 확인", MB_OK );
     // 클라이언트 소켓을 넌 블로킹 소켓으로 전환
     if( WSAAsyncSelect( g_ClientSocket, hWnd, WM_SOCKETEVENT, FD_READ | FD_CLOSE ) == SOCKET_ERROR)
          MessageBox(hWnd, "클라이언트 실행 오류", "클라이언트 실행 확인", MB_OK );
 
     return TRUE;
}

 

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

// 대화상자 메시지 처리함수이다.

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

BOOL CALLBACK DialogProc(HWND hwndDlg, UINT uMSG, WPARAM wParam, LPARAM lParam)
{
     switch(uMSG)
     {
          case WM_INITDIALOG : OnDlgInitDialog(hwndDlg, lParam); return 0 ;
          case WM_COMMAND : OnDlgCommand(hwndDlg, wParam); return 0;
     }
 
     return FALSE;
}

 

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

// 초기화 함수. 위에 CALLBACK 함수에서 실행

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

BOOL OnDlgInitDialog(HWND hwndDlg, LPARAM lParam)
{

    // 처음에 서버와 클라이언트 를 선택하는 대화상자 = IDD_SELECT
    if( lParam == IDD_SELECT)                           
    {
         CheckRadioButton(hwndDlg, IDC_SERVER, IDC_CLIENT, IDC_SERVER );      // 라디오 버튼 설정
  
         SetDlgItemText(hwndDlg, IDC_IPADDRESS, "127.0.0.1" );                       // 기본적으로 IP 127.0.0. 로 세팅
         EnableWindow(GetDlgItem(hwndDlg, IDC_IPADDRESS), FALSE );              // Enablewindow : 윈도우의 활성화 / 비활성화
    }
    return TRUE;
}

 

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

// 대화상자 커멘드에 관련된 처리 함수.

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

void OnDlgCommand(HWND hwndDlg, WPARAM wParam)
{

     //-----------------------------------------------------------------------------------

     // 서버 / 클라이언트 선택 대화상자..  IDD_SELECT

     //-----------------------------------------------------------------------------------
     if( wParam == IDC_SERVER)                                                        // IDC_SERVER 를 선택했을때 
     {
          if( HIWORD(wParam) == BN_CLICKED)                                       // 클릭되었을때 
          { 
               SetDlgItemText(hwndDlg, IDC_IPADDRESS, "127.0.0.1" );         // IP 입력창에 127.0.0.1 로 세팅
               EnableWindow(GetDlgItem(hwndDlg, IDC_IPADDRESS), FALSE); // 비활성화
          }
     }
     else if( wParam == IDC_CLIENT)                                                   // IDC_CLIENT 를 선택했을때 
     {
          if( HIWORD(wParam) == BN_CLICKED )                                       // 클릭되었을때 
          {
               HWND hwndIP = GetDlgItem(hwndDlg, IDC_IPADDRESS);            // IP 주소를 얻는다.
               SetDlgItemText(hwndDlg, IDC_IPADDRESS, "");                       //  127.0.0.1 로 되어있는것 삭제.
   
               EnableWindow(hwndIP, TRUE);                                            // 활성화. IP 를 입력할수 있다.
               SetFocus(hwndIP);
          }
     }

     else if( wParam == IDC_SELECTOK)                                                 // 처음 대화상자 OK 버튼 눌렀을시 처리
     {
          GetDlgItemText( hwndDlg, IDC_IPADDRESS, dwIP, 15);                    
  
          if( IsDlgButtonChecked(hwndDlg, IDC_CLIENT) != FALSE )
          {
               if( !strcmp("", dwIP) )
               {
                    MessageBox(hwndDlg, "IP 주소를 잘못 입력하셨습니다.", "에러", MB_OK );
                    return;
               }
          }
  
          EndDialog(hwndDlg, 0);                                                            // 대화상자 종료
     }

 

     //-----------------------------------------------------------------------------------

     // 대화명 변경 대화상자  IDD_CHATNAME    메뉴에서 실행시킬수 있다.

     //-----------------------------------------------------------------------------------
     if( wParam == IDC_CHATNAMEOK )                                                    // OK 버튼을 눌렀을때 
     {
          char pChatName[CHATNAMESIZE];
          GetDlgItemText( hwndDlg, IDC_CHATNAME, pChatName, sizeof( pChatName ) );

          //비어 있는지 확인
          if( lstrlen( pChatName ) == 0 )
          {
               MessageBox( hwndDlg, "대화명 없음", "에러", MB_OK );
               return;
          }

          //이전 대화명과 비교
          if( lstrcmp( pChatName, g_pChatName ) == 0 )
          {
               if( MessageBox( hwndDlg, "현재 대화명과 동일합니다.", "에러", MB_YESNO | MB_ICONERROR ) == IDYES )
               EndDialog( hwndDlg, 0 );

               return;
          }

          if( pChatName[0] == '*' )
          {
               MessageBox( hwndDlg, "대화명 *로 시작할수 없습니다. 오류", "오류", MB_OK );

               return;
          }

          // 데이터를 보낸다.

          SendPacket( g_ClientSocket, PACKET_CHATNAME_CHANGE, pChatName, lstrlen(pChatName) );

          EndDialog( hwndDlg, 0 );
     }
     else if( wParam == IDC_CHATNAMECANCEL )                                       // Cancel 을 선택했을때 대화 상자 종료
     {
          EndDialog( hwndDlg, 0 );
     }

 

     //-----------------------------------------------------------------------------------

     // 클라이언트 정보 대화상자  IDD_CLIENTINFO 

     //-----------------------------------------------------------------------------------

     if( wParam == IDC_CLIENTOK )                                                         // OK 선택시
     {
          char pClientName[CHATNAMESIZE];
          GetDlgItemText( hwndDlg, IDC_CLIENTCHATNAME, pClientName, sizeof(pClientName) );

          if( lstrlen( pClientName ) == 0 )
          {
               MessageBox( hwndDlg, "대화명을 입력하지 않았군요", "에러", MB_OK );
               return;
          }

          // 데이터를 보낸다.

          SendPacket( g_ClientSocket, PACKET_CLIENTINFO, pClientName, lstrlen(pClientName) );

          EndDialog( hwndDlg, 0 );
     }

     else if( wParam == IDC_CLIENTCANCEL )                                            // Cancel 선택시 대화상자 종료
     {
          EndDialog( hwndDlg, 0 );
     }
}

 

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

// 채팅프로그램(메신저) 를 꾸며주는 곳이다. API 를 배웠다면 좀더 멋지게 꾸밀수 있다. 비트맵을 이용하여 그림을 넣는다던지...

// 여기선 간단히 형태만...^^  이 내용의 설명은 Pass ~   만약 API 를 안배웠다면 여기서는 대충 주석잡으면서 눈으로 확인을 해서 이해하면 된다.

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

void OnPaint(HWND hWnd)
{
      PAINTSTRUCT ps;
      HDC hdc = BeginPaint(hWnd, &ps);
      RECT rt;
      HRGN hRgn;
      HBRUSH hBrush;
      HFONT font, oldfont;
      {
           GetClientRect(hWnd, &rt);
           RoundRect(hdc, (rt.left+2), (rt.top+2), (rt.right-2), (rt.bottom-385), 10, 10);
           RoundRect(hdc, (rt.left+2), (rt.top+52), (rt.right-2), (rt.bottom-70), 10, 10);
           RoundRect(hdc, (rt.left+2), (rt.top+370), (rt.right-2), (rt.bottom), 10, 10);
  
           hBrush = CreateSolidBrush(RGB( 214, 223, 247));
           hRgn = CreateRoundRectRgn((rt.left+3), (rt.top+3), (rt.right-2), (rt.bottom-385), 10, 10);
           FillRgn(hdc, hRgn, hBrush);
  
           font = CreateFont(15, 0, 0, 0, FW_NORMAL, 0, 0, 0, HANGEUL_CHARSET, 3, 2, 1,
           VARIABLE_PITCH | FF_ROMAN, "돋움체" );
           oldfont = (HFONT)SelectObject(hdc, font);
           SetBkColor(hdc, RGB(214, 223, 247));
           SetTextColor(hdc, RGB(0, 0, 0));
           TextOut(hdc, 78, 8, "GameD 채팅 프로그램", 20);
           SetTextColor(hdc, RGB(255, 0, 0));
           TextOut(hdc, 42, 25, "http://blog.naver.com/gamed21", 30);
           DeleteObject(oldfont);
      }
 
      SetFocus(GetDlgItem(hWnd, IDC_CHAT));
      EndPaint(hWnd, &ps);
}

 

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

// 윈도우창의 커맨드 관련된 처리

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

void OnCommand(HWND hWnd, WPARAM wParam, HWND hwndCtl)
{
    switch(LOWORD(wParam))
    {  

         //-----------------------------------------------------------------------------------

         // 채팅창 (글씨 입력하는 곳)     

         //-----------------------------------------------------------------------------------

         case IDC_CHAT:
              if( HIWORD( wParam) == EN_CHANGE)                               // 변할때 CHANGE .. 글씨가 써질때마다라고 생각하면 된다...
              {
                   char pChatString[128];
                   GetDlgItemText(hWnd, IDC_CHAT, pChatString, sizeof(pChatString));
                   int nLen = lstrlen( pChatString );
   
                   HWND hwndSend = GetDlgItem( hWnd, IDC_SEND );
   
                   EnableWindow(hwndSend, nLen);                               // 글이 입력되면 (보내기 버튼) 이 활성화 된다. 
                   // 그냥 엔터를 쳤을때의 처리....  엔터만 쳐보면 알겠지만 메시지가 전송되면서 다음줄로 내려간다...
                   if( strstr( pChatString, "\r\n" ) != NULL )
                   {
                        pChatString[nLen-2] = '\0';
                        SetWindowText( hwndCtl, pChatString);
    
                        SendMessage(hWnd, WM_COMMAND, (WPARAM)IDC_SEND, (LPARAM)hwndSend);
                   }   
              }
              break;

 

         //-----------------------------------------------------------------------------------

         // 보내기   IDC_SEND

         //-----------------------------------------------------------------------------------

         case IDC_SEND:
              if( HIWORD(wParam) != BN_CLICKED )
                   return ;
  
              char pChatStr[512];
              GetDlgItemText(hWnd, IDC_CHAT, pChatStr, sizeof(pChatStr));
  
              char pSendStr[576];
              wsprintf( pSendStr, "[%s]: %s", g_pChatName, pChatStr);                            // sprintf 와 동일하다. Windows......
              SendPacket( g_ClientSocket, PACKET_STRING, pSendStr, lstrlen(pSendStr));   // 데이터 전송
  
              SetDlgItemText(hWnd, IDC_CHAT, "");
              SetFocus(GetDlgItem(hWnd, IDC_CHAT));
  
              EnableWindow(hwndCtl, FALSE);
              break;

 

         //-----------------------------------------------------------------------------------

         // 메뉴에서 종료를 선택시

         //-----------------------------------------------------------------------------------

         case ID_APPEXIT:
              OnDestroy(hWnd);
              break;
 

         //-----------------------------------------------------------------------------------

         // 메뉴에서 대화명 변경 선택시

         //-----------------------------------------------------------------------------------

         case ID_CHANGECHATNAME:
              DialogBoxParam(g_hlnst, MAKEINTRESOURCE(IDD_CHATNAME), hWnd, DialogProc, IDD_CHATNAME);
              break;

 

         //-----------------------------------------------------------------------------------

         // 메뉴에서 클라이언트 정보 선택시

         //-----------------------------------------------------------------------------------

         case ID_CLIENTINFO:
         DialogBoxParam(g_hlnst, MAKEINTRESOURCE(IDD_CLIENTINFO), hWnd, DialogProc, IDD_CLIENTINFO);
         break;
    }    // switch(LOWORD(wParam))
}

 

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

// 종료

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

void OnDestroy(HWND hWnd)
{
     PostQuitMessage(0);
}

 

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

// 패킷 전송(데이터 전송)

// 패킷 구성을 잘 살펴보면 int + PacketFlag의 사이즈 + 글자수

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

int SendPacket( SOCKET socket, PACKETFLAG PacketFlag, void *pData, int nDataSize)
{
     static char pPacket[1024];
     int nPacketSize = sizeof(int) + sizeof(PacketFlag) + nDataSize;
 
     if( lstrlen(pPacket) >= sizeof(pPacket))
     return 0;
     // memmove 는 지정한 크기만큼 메모리를 옮기는 함수이다. 
     memmove( pPacket, &nPacketSize, sizeof(int));
     memmove(pPacket+ sizeof(int), &PacketFlag, sizeof(PacketFlag));
     memmove(pPacket+sizeof(int)+ sizeof(PacketFlag), pData, nDataSize);
 
     pPacket[nPacketSize] = '\0';
 
     return send(socket, pPacket, nPacketSize, 0);
}

 

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

// WndProc 에서 WM_SOCKETEVENT 메시지가 발생할때 실행된다.

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

void OnSocketEvent(HWND hWnd, SOCKET socket, LPARAM lParam)
{
      switch(LOWORD(lParam))
      {
      case FD_ACCEPT: OnAccept(hWnd, socket); break;          // 클라이언트가 접속하면 메시지 발생. ( 대응하는 함수 accept )
      case FD_READ: OnRead(hWnd, socket); break;                // 데이터 수신이 가능하면 메시지 발생 ( 대응하는 함수 recv. recvfrom)
      case FD_CLOSE: OnClose(hWnd, socket); break;              // 상대가 접속을 종료하면 메시지 발생 ( 대응하는 함수 없음)
      }
}

 

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

// 클라이언트가 접속하면 메시지 발생   STL 은 따로 공부가 많이 필요한 부분이다...  공부하자~~~

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

void OnAccept(HWND hWnd, SOCKET socket)
{
      SOCKADDR_IN PeerAddr;
      int AddrLen = sizeof(PeerAddr);
      SOCKET hClientSocket = accept( socket, (SOCKADDR*)&PeerAddr, &AddrLen );
 
      WSAAsyncSelect( hClientSocket, hWnd, WM_SOCKETEVENT, FD_READ | FD_CLOSE );


      // STL
      set<int>::iterator it = g_SerialSet.begin();                      
      for( int i = 0; it != g_SerialSet.end(); i++, it++)
      {
           if( i != *it)
           break;
      }


      g_SerialSet.insert(i);                                       // 삽입
 
      char pNewName[CHATNAMESIZE];
      wsprintf( pNewName, "*^%d^*", i);                      // 기본 아이디 *^(넘버)^*  
      
      SendPacket( hClientSocket, PACKET_CHATNAME_FIRST, pNewName, lstrlen(pNewName));

      // CLIENTDATA  ( 소켓, 아이디, IP, Port 가 저장되어 있는 구조체 )   현재 접속된 클라이언트에 대한 정보를 저장한다..

      CLIENTDATA* pClientData = new CLIENTDATA;
    
      pClientData->hsocket = hClientSocket;               

      lstrcpy( pClientData->pChatName, pNewName);      // lstrcpy 나 strcpy 나 같다..  다른 것들도 마찬가지....

      pClientData->RequestInfo.dwIp = PeerAddr.sin_addr.s_addr ;
      pClientData->RequestInfo.nPort = PeerAddr.sin_port;
 
      g_ClientDataList.push_back(pClientData);             // 현재 저장되어 있는 클라이언트 정보를 List 에 등록한다. 
}

 

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

// 데이터 수신이 가능하면 메시지가 발생된다.

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

void OnRead(HWND hWnd, SOCKET socket)
{
     int nDataSize;
     PACKETFLAG packetFlag;
     char* ppData = NULL;
    

     // 변수에 어떤 값도 없는데 ReadPacket 으로 보내고 있다.. ppData 의 경우는 할당도 안했고..... 맨 아래를 보게 되면 ppData 의 해제도 보이게 된다.

     // 변수 데이터의 크기(nDataSize) 와 패킷 플래그(packetFlag)  데이터(ppData)를 ReadPacket 에서 크기를 할당받는 것이다.

     // Call-By-Reference 의 개념.....  
     if( ReadPacket( hWnd, socket, &nDataSize, &packetFlag, &ppData) == FALSE )
          return;
    

     // 들어온 socket 에 따라 처리를 해준다..

     if( socket != g_ClientSocket)                  
          ProcessServerPacket( socket, packetFlag, ppData, nDataSize);
     else                                                   
          ProcessClientPacket(hWnd, socket, packetFlag, ppData, nDataSize);
 
     delete[] ppData;            
}

 

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

// 데이터의 크기를 할당한다. OnRead 에서 받은 nDataSize , packetFlag, ppData의 Call-By-Reference 개념으로 크기를 할당한다.

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

BOOL ReadPacket(HWND hWnd, SOCKET socket, int *pDataSize, PACKETFLAG *pPacketFlag, char **ppData)
{
     int pHeader[2];
     int nRecvSize = 0, nTotalRecvSize = 0;
    

     // 데이터를 받는다. recv
     while( nTotalRecvSize < sizeof(pHeader))
     {
          nRecvSize = recv( socket, (char*)pHeader + nTotalRecvSize, sizeof( pHeader) - nTotalRecvSize, 0);
          if( nRecvSize != SOCKET_ERROR)
          {
               nTotalRecvSize += nRecvSize;
               continue;                                   // 에러가 아니라면 데이터를 계속 받는다. 그러다가 while 문이 FALSE 되면 탈출
          }
          return FALSE;                                  // 에러일경우 return FALSE
     }

    

     int nDataSize = pHeader[0] - nTotalRecvSize;
     char *pPacketData = new char[nDataSize +1 ];
     pPacketData[nDataSize] = '\0';
 
     nTotalRecvSize = 0;                               // 다시 사용하기 위해 0 으로 초기화
 
     while (nTotalRecvSize < nDataSize)
     {
          nRecvSize = recv( socket, pPacketData + nTotalRecvSize, nDataSize - nTotalRecvSize, 0 );
  
          if( nRecvSize != SOCKET_ERROR)
          {
               nTotalRecvSize += nRecvSize;
               continue;
          }
          if( WSAGetLastError() != WSAEWOULDBLOCK)            // WSAGetLastError  가장 최근에(마지막) 실패한 소켓연산의 에러를 얻는 함수
          {
               return FALSE;
          }
     }
 

     // 할당..... 
     *pDataSize = nDataSize;               
     *pPacketFlag = (PACKETFLAG)pHeader[1];
     *ppData = pPacketData;

   

     return TRUE;
}

 

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

// 서버 패킷 처리

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

void ProcessServerPacket( SOCKET socket, PACKETFLAG PacketFlag, char* pData, int nDataSize)
{
     static int i = 0 ;
 
     switch( PacketFlag )
     {

     //-----------------------------------------------------------------------------------

     // Oncommand 에서 IDC_SEND (메시지를 보냈을때...) 를 할때 발생한다...

     // 현재 메세지를 채팅프로그램에 접속되어 있는 모든(begin ~ end)곳에 SendPacket...

     //-----------------------------------------------------------------------------------

     case PACKET_STRING:
          for( g_it = g_ClientDataList.begin() ; g_it != g_ClientDataList.end(); g_it++ )
               SendPacket( (*g_it)->hsocket, PACKET_STRING, pData, nDataSize );
          break;
 

     //-----------------------------------------------------------------------------------

     // 클라이언트 정보 대화상자에서 OK( IDC_CLIENTOK) 를 선택할때 발생한다..

     //  클라이언트에 대한 정보를 채팅프로그램에 접속되어 있는 모든(begin ~ end)곳에 SendPacket...

     //-----------------------------------------------------------------------------------

     case PACKET_CLIENTINFO:
          for( g_it = g_ClientDataList.begin() ; g_it != g_ClientDataList.end(); g_it++ )
          {
               if( lstrcmp( (*g_it)->pChatName, pData ) == 0)          // 입력한 정보가 있는지 검사... 있다면 탈출
                    break;
          }
       

          if( g_it != g_ClientDataList.end())                                 // 끝이 아닐경우 데이터 전송
               SendPacket(socket, PACKET_CLIENTINFO, &(*g_it)->RequestInfo, sizeof( (*g_it)->RequestInfo ) );
          else                                                                       // 끝일경우 NULL...
               SendPacket(socket, PACKET_CLIENTINFO, NULL, 0);
  
          break;

 

     //-----------------------------------------------------------------------------------

     // 대화명 변경 대화상자에서 OK(IDC_CHATNAMEOK) 선택시 발생한다.

     // 내용은 위에 것과 같은 원리이다... 쉽게 분석 가능

     //-----------------------------------------------------------------------------------

     case PACKET_CHATNAME_CHANGE:
          for( g_it = g_ClientDataList.begin(); g_it != g_ClientDataList.end(); g_it++)
          {
               if( lstrcmp( (*g_it)->pChatName, pData) == 0)
                    break;
          }
              

          if( g_it != g_ClientDataList.end() )
          {
               SendPacket(socket, PACKET_CHATNAME_CHANGE, "", 0);
               break;
          }
  
          assert( g_it == g_ClientDataList.end() );                          // assert 함수는 assert.h 에 있는 함수로 에러를 체크하는 것이다.. 
  
          for( g_it = g_ClientDataList.begin(); g_it != g_ClientDataList.end(); g_it++)
          {
               if( (*g_it)->hsocket == socket)
                    break;
          }
  
          if( (*g_it)->pChatName[0] == '*' )
          {
               int nSerial = atoi( &(*g_it)->pChatName[2] );
               g_SerialSet.erase(nSerial);
          }
  
          lstrcpy((*g_it)->pChatName, pData);
          SendPacket(socket, PacketFlag, pData, nDataSize );
          break;


     default:
          assert(FALSE);
     } // switch( PacketFlag )
}

 

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

// 클라이언트 패킷처리

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

void ProcessClientPacket( HWND hWnd, SOCKET socket, PACKETFLAG PacketFlag, char *pData, int nDataSize)
{
     switch(PacketFlag)
     {
     case PACKET_STRING:
          AddStringToList( GetDlgItem(hWnd, IDC_SAVE), pData);               // 채팅창(화면 : IDC_SAVE)에 출력
          break;
 
     case PACKET_CHATNAME_FIRST:
     case PACKET_CHATNAME_CHANGE:
          if( nDataSize == 0)
          {
               MessageBox( hWnd, "이미 사용중인 대화명입니다.", "에러", MB_OK);
               break;
          }
          char pOldChatName[CHATNAMESIZE];
          lstrcpy(pOldChatName, g_pChatName);
          lstrcpy(g_pChatName, pData);
          assert( g_pChatName[0] != '\0');
  
          SetWindowText( hWnd, g_pChatName);
  
          char pNotifyMsg[128];
          if( PacketFlag == PACKET_CHATNAME_FIRST)
               wsprintf(pNotifyMsg, "[%s]님이 입장했습니다.", g_pChatName);
          else
               wsprintf(pNotifyMsg, "[%s]님의 대화명이 [%s](으)로 변경되었습니다.",  pOldChatName, g_pChatName);
          // 대화명이 변경되었으니 바뀐 대화명을 접속해 있는 모든곳에 알려준다.
          SendPacket(socket, PACKET_STRING, pNotifyMsg, lstrlen( pNotifyMsg));
          break;
  
     case PACKET_CLIENTINFO:
          char pClientMsg[128];
  
          if( nDataSize == 0)
               wsprintf( pClientMsg, "요청하신 대화명이 없습니다.");
          else
          {
               REQUESTINFO* pInfo = (REQUESTINFO*)pData;
   
               in_addr* paddr = (in_addr*)&pInfo->dwIp;
               wsprintf( pClientMsg, "주소 : %s, 포트번호 : %d", inet_ntoa(*paddr), pInfo->nPort);
          }
          AddStringToList(GetDlgItem(hWnd, IDC_SAVE), pClientMsg);
          break;
     } // switch(PacketFlag)
}

 

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

// 화면에 글씨 출력

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

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);
}

 

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

// SOCKET_EVENT FD_CLOSE 메시지 발생..

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

void OnClose(HWND hWnd, SOCKET socket)
{
     closesocket(socket);
     // 소켓이 서버가 아니라면....
     if( socket != g_ClientSocket)
     {
          for( g_it = g_ClientDataList.begin(); g_it != g_ClientDataList.end(); g_it++)
          {
               if((*g_it)->hsocket == socket)
               break;
          }
   

          assert(g_it != g_ClientDataList.end());
  
          if( (*g_it)->pChatName[0] == '*')
          {
               int nSerial = atoi( &(*g_it)->pChatName[2] );
               g_SerialSet.erase(nSerial);
          }
  
          char pNotifyMsg[CHATNAMESIZE + 30];
          wsprintf( pNotifyMsg, "[%s]님이 퇴장셨습니다.", (*g_it)->pChatName);
  
          delete (*g_it);
          g_ClientDataList.erase( g_it);
           
          for( g_it = g_ClientDataList.begin(); g_it != g_ClientDataList.end(); g_it++)
               SendPacket( (*g_it)->hsocket, PACKET_STRING, pNotifyMsg, lstrlen(pNotifyMsg));

     } //  if( socket != g_ClientSocket)

     else{              
          char pMsg[] = "서버가 종료 되었습니다. 모든 프로그램을 종료합니다.";
          MessageBox(hWnd, pMsg, "알림", MB_OK);
  
          SendMessage(hWnd, WM_DESTROY, 0, 0 );
     }
}

 

좀 길기때문에 cpp 를 분리해야 하지만..;;  그냥 여기선 쭉~~~ 작성해버렸다... .exe 파일을 여러개 실행하여 하나는 서버를 선택하고

나머지들은 (자신의 컴퓨터에서 테스트를 해야하기 때문에) ip 주소를 127.0.0.1 로 작성하여 클라이언트를 선택해서 실행하면 된다..

반드시 이전에 올라온 세팅이 끝난상태여야 한다..(대화상자라던가.. 메뉴라던가...  이전에 올라온것 참고...)

 

패킷에대한 것은 따로분석하여 공부를 하는 것이 좋다.. 패킷에 대한 처리를 공부하고 간단한 게임을 멀티가 되게 만들어 보는 것도 좋은 방법이라 생각된다..

'네트워크' 카테고리의 다른 글

IOCP 구현  (0) 2013.05.22
WSAAsyncSelect 사용하기  (0) 2013.05.14
c# 비동기 방식의 콜백함수  (0) 2013.05.14
C# 비동기 클라이언트 소켓서버  (0) 2013.05.14
C# TCP 소켓통신 Server  (0) 2013.04.25
Posted by violetoz
네트워크2013. 4. 25. 20:49

using System;

using System.Net;

using System.Net.Sockets;

using System.Text;


namespace ToyClient

{

    class Class1

    {

        [STAThread]

        static void Main(string[] args)

  {

   try

   {

    //TCP Echo Client


    //1. 종단점 생성

    IPAddress ip = IPAddress.Parse("127.0.0.1");

    IPEndPoint endPoint = new IPEndPoint(ip,5000);


    //2. TCP Socket 생성

    Socket socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);


    //3. 연결하기

    Console  .Write  ("엔터를 치시면 서버와 연결됩니다.");

    Console.ReadLine();

    socket.Connect(endPoint);


    //4. 데이터 송수신

    if (socket.Connected)//서버측에서 소켓이 만들어져 있는지 확인

    {

     while(true)

     {

      Console.WriteLine("상대방종점값 : "+socket.RemoteEndPoint.ToString());

      Console.WriteLine("자신의종점값 : "+socket.LocalEndPoint.ToString());


      Console.Write  ("보낼 문자열 : ");

      string input = Console.ReadLine();

      byte[] inputBuffer = Encoding.UTF8.GetBytes(input);

      //송신

      socket.Send(inputBuffer,0,inputBuffer.Length,SocketFlags.None);


      Console.WriteLine("에코 기다리는중..");


      //수신

      byte[] receiveBuffer = new byte[512];

      socket.Receive(receiveBuffer,0,receiveBuffer.Length,SocketFlags.None);


      string result = Encoding.UTF8.GetString(receiveBuffer,0,receiveBuffer.Length);

      Console.WriteLine("받은 문자열 : " + result);

     }

     socket.Close();

    }

    else

    {

     Console.WriteLine("소켓 연결 실패!!");

    }

   }


   catch(Exception e)

   {

    Console.WriteLine("오류 발생 : " + e.Message);

   }

  }

    }

}


Posted by violetoz