728x90
반응형

[클라이언트에서 서버로 데이터 전송]

 

 

[서버]

// 서버 프로그램
// 0128_3.cpp : 애플리케이션에 대한 진입점을 정의합니다.

#include <winsock2.h>
#include <process.h>
#include <atlstr.h>

#include "framework.h"
#include "0128_3.h"

#define MAX_LOADSTRING 100
#define WM_USER_SOCKET (WM_USER + 1)

#pragma warning(disable:4996)

// 전역 변수:
HINSTANCE hInst;                                // 현재 인스턴스입니다.
WCHAR szTitle[MAX_LOADSTRING];                  // 제목 표시줄 텍스트입니다.
WCHAR szWindowClass[MAX_LOADSTRING];            // 기본 창 클래스 이름입니다.

// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

// 서버측 프로그램 전역 변수
HWND hWnd;
bool LoopConnectThread = true;
SOCKET MainSocket;
//SOCKET hSocket; // 메인 소켓 아님
// 0x0401
SOCKET hSocket[100];
struct USERINFO
{
	SOCKET hSocket;
	int id;
	int x, y;
};
struct FORMAT1 {
	short mx;
	short my;
};
#define FORMATID1 1234
#define FORMATID2 2
#define FORMATID3 3
#define FORMATID4 4

unsigned _stdcall ClientThread(void *pArg) 
{
	USERINFO* userInfo = (USERINFO*)pArg;
	// 이벤트 관리할 이벤트 핸들 생성
	WSAEVENT hRecvEvent = WSACreateEvent(); // 발생한 이벤트를 가져오는 객체를 생성한다.
	// 감지할 이벤트 설정한다. (어떤 이벤트를 설정할 것인가...)
	WSAEventSelect(userInfo->hSocket, hRecvEvent, FD_READ | FD_CLOSE); // 상대방이 접속을 종료할 수 있으니
	
	while (true) 
	{
		// 이 함수는 Blocking 함수이다.(scanf)
		// WSAEventSelect << 함수에서 설정한 이벤트가 발생하면 Blocking이 풀린다.
		// >>>> 여기 Blocking
		WSAWaitForMultipleEvents(1, &hRecvEvent, false, WSA_INFINITE, false);
		// Blocking을 푼 이벤트가 어떤 이벤트인지 확인...
		//MessageBox(hWnd, L"WSAEventSelect", L"WSAEventSelect", MB_OK);
		WSANETWORKEVENTS WhatEvent;
		WSAEnumNetworkEvents(userInfo->hSocket, hRecvEvent, &WhatEvent);
		
		int index = 0;
		char recvBuffer[4096] = { 0, };
		int RecvSize;
		if ((WhatEvent.lNetworkEvents & FD_READ) == FD_READ) 
		{
			while (true) // recv: 4byte 모두 수신한다는 보장이 없다.
			{
				RecvSize = recv(userInfo->hSocket, &recvBuffer[index], 4096, 0);
				if (RecvSize > 0)
				{
					index += RecvSize;
				}else{break;}
			}
		}
		
		//MOUSEDATA mouseData;
		// 데이터 가공 한다.
		int pos = 0;
		short mx, my;
		while (index)
		{
			int formatNum = *((int*)recvBuffer + pos);	// 1. send를 한번 받을수도 있고
			pos += sizeof(int);							// 2. send를 한번 이상으로 받을수도 있다.
			switch (formatNum)
			{
			case FORMATID1:
			{
				mx = ((FORMAT1*)(recvBuffer + pos))->mx;
				my = ((FORMAT1*)(recvBuffer + pos))->my;
				pos += sizeof(FORMAT1);
				index -= 8; // 받은 데이터가 8Byte
			}break;

			case FORMATID2: {}break;
			case FORMATID3: {}break;
			case FORMATID4: {}break;
			}

			CString s;
			s.Format(L"formatNum: %d " L"mx:%d mx:%d", formatNum, mx, my);
			SetWindowText(hWnd, s);
		}

		if ((WhatEvent.lNetworkEvents & FD_CLOSE) == FD_CLOSE)
		{
			closesocket(userInfo->hSocket);
			// 메모리 해제 코드
			delete userInfo;
			// 접속자 수를 한명 감소시킨다.
			MessageBox(hWnd, L"클라이언트 접속 종료", L"FD_CLOSE", MB_OK);
			break;
		}

		struct PACKDATA {
			short sum;
		};
		PACKDATA packData;
		packData.sum = mx + my;

		char sendBuffer[512] = { 0, };
		((PACKDATA*)sendBuffer)->sum = packData.sum;

		/*for (int i = 0; i < 반복횟수; i++)
		{
			send(받는 대상의 소켓번호, sendBuffer, 2, 0);
		}*/
		send(userInfo->hSocket, sendBuffer, 2, 0);
	}
	_endthreadex(0);
	return true;
}
unsigned _stdcall ConnectThread(void * pArg) 
{
	//OutputDebugString();
	SetWindowText(hWnd, L"Server_Start");
	// 네트워크 관련 초기화 작업
	WORD version = MAKEWORD(2, 2);  // 네트워크 버전 설정
	WSADATA wsaData;
	// 네트워크와 관련된 (함수)범용 동적라이브러리를 로드
	int err;
	err = WSAStartup(version, &wsaData); //윈도우가 가지고 네트워크 라이브러리(.DLL)을 끌어오는 함수
											 // 비용 절감 효과
											 // 단, 초기 로딩 시간이 길다
	if (err) {
		SetWindowText(hWnd, L"Error : WSAStartup");
		return false;
	}
	//0x3456; LOBYTE << 56
	//wsaData.wVersion != 0x0202 아것도 가능
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
		SetWindowText(hWnd, L"Error : Version");
		WSACleanup();
		return false;
	}
	
	// 소켓 생성
	MainSocket = socket(AF_INET, SOCK_STREAM, 0); // TCP(데이터 손실 없음), UDP(손실이 있을 수 있다.)
	if (MainSocket == INVALID_SOCKET) {
		SetWindowText(hWnd, L"Error : Socket Create");
		WSACleanup();
		return false;
	}
	// 윈도우에게 현재 시스템의 통신 상태를 등록한다.
	struct sockaddr_in sa;
	ZeroMemory(&sa, sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_addr.s_addr = htonl(INADDR_ANY); // inet_addr("192.168.1.39);
	sa.sin_port = htons(3000);    // 데이터 송수신 통로 => port
	
	// 바인딩: 윈도우에게 정보를 묶어 접수하는 것;
	err = bind(MainSocket, (struct sockaddr*)&sa, sizeof(struct sockaddr));
	if (err == SOCKET_ERROR) {
		SetWindowText(hWnd, L"Error : Bind");
		return false;
	}
	// 대기 인원을 설정한다.
	err = listen(MainSocket, 10);
	if (err == SOCKET_ERROR) {
		SetWindowText(hWnd, L"Error : Listen");
		return false;
	}
	//SetWindowText(hWnd, L"OK!!");
	static int num = 0;
	while (LoopConnectThread) 
	{
		ZeroMemory(&sa, sizeof(sa));
		int len = sizeof(sa);

		// 클라이언트의 접속을 기다림
		// Blocking 함수이다. scanf()함수와 같다
		// 프로그램이 진행하지 않는다.
		// 누군가가 접속하면 풀린다.
		//SetWindowText(hWnd, L"100");
		//SetWindowText(hWnd, L"200");
		SOCKET hSocket = accept(MainSocket, (struct sockaddr*) &sa, &len);
	
		USERINFO* userInfo = new USERINFO;
		userInfo->hSocket = hSocket;
		userInfo->id = num;
		userInfo->x = num * 3;
		userInfo->y = num * 4;

		num++;
		CString s;
		s.Format(L"%04d", num);
		SetWindowText(hWnd, s.GetBuffer());

		UINT threadID;
		_beginthreadex(NULL, 0, ClientThread, userInfo, 0, &threadID);
	}
	_endthreadex(0);
	return true;
}
void InitData() {
	UINT threadID;
	_beginthreadex(NULL, 0, ConnectThread, NULL, 0, &threadID);
}

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: 여기에 코드를 입력합니다.

    // 전역 문자열을 초기화합니다.
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_MY01283, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // 애플리케이션 초기화를 수행합니다:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MY01283));

    MSG msg;

	InitData();
    // 기본 메시지 루프입니다:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}


ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MY01283));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_MY01283);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}


BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.

   hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
	   0, 0, 400, 300, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_PAINT:
	{
		PAINTSTRUCT ps;
		HDC hdc = BeginPaint(hWnd, &ps);
		// TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
		EndPaint(hWnd, &ps);
	}
	break;
	case WM_DESTROY:
		PostQuitMessage(0);
		LoopConnectThread = false;
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

[클라이언트]

// WindowsProject2.cpp : 애플리케이션에 대한 진입점을 정의합니다.
// 클라이언트

#pragma warning(disable:4996)
#include <process.h>
#include <atlstr.h>
#include <winsock2.h>
#include "framework.h"
#include "WindowsProject2.h"

#define MAX_LOADSTRING 100


// 전역 변수:
HINSTANCE hInst;                                // 현재 인스턴스입니다.
WCHAR szTitle[MAX_LOADSTRING];                  // 제목 표시줄 텍스트입니다.
WCHAR szWindowClass[MAX_LOADSTRING];            // 기본 창 클래스 이름입니다.

HWND hWnd;
// 클라이언트 프로그램 측 전역변수
SOCKET ClientSocket;
WORD mx, my;
#define WM_USER_SOCKET ((WM_USER) + 1)

// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

struct PACKDATA {
	short mx;
	short my;
};
PACKDATA packData;


bool InitData()
{
	SetWindowText(hWnd, L"Client_Start");
	// 네트워크 관련 초기화 작업.
	WORD version = MAKEWORD(2, 2);          // 버전
	// 네트워크와 관련된 범용(동적) 라이브러리(DLL)를 로드.
	WSADATA wsaData;
	int err;
	err = WSAStartup(version, &wsaData);    // 네트워크 관련 DLL을 로드해주는 함수. // 단, 초기 로딩 시간이 길다
	
	if (err) 
	{ 
		SetWindowText(hWnd, L"Error : WSAStartup"); 
		return false; 
	}
	
	// 버전 확인.
	if (LOBYTE(wsaData.wVersion) != 2 ||
		HIBYTE(wsaData.wVersion) != 2)
	{
		SetWindowText(hWnd, L"Error : version"); 
		WSACleanup(); return false;
	}

	// 소켓 생성.
	ClientSocket = socket(AF_INET, SOCK_STREAM, 0);       // 인터넷, TCP, 사용하지 않음.
	
	if (ClientSocket == INVALID_SOCKET) // 실패 시 -1 리턴.
	{                 
		SetWindowText(hWnd, L"Error : socket create");
		WSACleanup();
		return false;
	}

	// 소켓 id와 윈도우 핸들을 연결
	WSAAsyncSelect(ClientSocket, hWnd, WM_USER_SOCKET, FD_CONNECT);
	SOCKADDR_IN SocketAddressIn;
	ZeroMemory(&SocketAddressIn, sizeof(SocketAddressIn));
	SocketAddressIn.sin_family = AF_INET;                            // 인터넷 통신을 사용하겠다는 설정.
	SocketAddressIn.sin_addr.s_addr = inet_addr("192.168.123.102");             // 통신 IP를 얻어온다.
	SocketAddressIn.sin_port = htons(3000);
	// 접속.
	// 비동기 함수.
	err = connect(ClientSocket, (LPSOCKADDR)&SocketAddressIn, sizeof(SocketAddressIn));
	
	if (err == SOCKET_ERROR)
	{
		int ErrCode = WSAGetLastError();    // 현재 네트워크 상에서 가장 최근의 에러.
		
		if (ErrCode != WSAEWOULDBLOCK)       // 에러 코드가 우드 블록이 아니면 에러.
		{
			SetWindowText(hWnd, L"Error : connect");
			return false;
		}
	}
	return true;
}

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: 여기에 코드를 입력합니다.

    // 전역 문자열을 초기화합니다.
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_WINDOWSPROJECT2, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // 애플리케이션 초기화를 수행합니다:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT2));

    MSG msg;

	InitData();

    // 기본 메시지 루프입니다:

    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}


ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT2));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT2);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}


BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.

   hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      1100, 300, 400, 300, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

struct FORMAT1
{
	short mx;
	short my;
};
//FORMAT1 format1;

struct FORMAT2
{
	short data;
};

struct FORMAT3
{
	short x, y, z;
};

struct FORMAT4
{
	short ar[10];
};


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_USER_SOCKET:
	{
		UINT event = WSAGETSELECTEVENT(lParam);    // 네트워크에서 발생한 이벤트를 얻는 함수.
		switch (event)
		{
		case FD_CONNECT:
		{
			WSAAsyncSelect(ClientSocket, hWnd, WM_USER_SOCKET, FD_READ);
			// MessageBox(hWnd, L"WM_USER_SOCKET", L"", MB_OK);
		}break;
		
		case FD_READ:
		{
			// 데이터를 받는다.
			char recvBuffer[4096] = { 0, };
			recv(ClientSocket, recvBuffer, 4096, 0);
			short sum = (short)*(short*)recvBuffer;
			
			// 데이터를 출력한다.
			CString s;
			s.Format(L"%d", sum);
			SetWindowText(hWnd, s);
		}break;
		
		default: 
			break;
		}
	}break;
	
	case WM_MOUSEMOVE:
	{
		mx = LOWORD(lParam);
		my = HIWORD(lParam);
		CString s;
		s.Format(L"mx: %d my: %d", mx, my);
		//SetWindowText(hWnd, s.GetBuffer());
	}break;
	
	case WM_LBUTTONDOWN:
	{
		// 통신 시 데이터가 4096 byte일 때 가장 안정적이다.
		const char sendBuffer[512] = {0, };    // send()할 데이터.
		int formatNum = 1234;
		*((int*)sendBuffer + 0) = formatNum;

		((FORMAT1*)(sendBuffer + 4))->mx = mx; // 수동 임의 data값: 0x5678;
		((FORMAT1*)(sendBuffer + 4))->my = my; // 수동 임의 data값: 0x6666;

		send(ClientSocket, sendBuffer, 4 + sizeof(FORMAT1), 0);   // 소켓 id, 보낼 데이터, 사이즈, 0
		//send(ClientSocket, sendBuffer, 4, 0);
	}break;
	
	case WM_RBUTTONDOWN:
	{
		if (ClientSocket != 0)
		{
			closesocket(ClientSocket);
			ClientSocket = 0;
		}
		
	}break;
	
	case WM_PAINT:
	{
		PAINTSTRUCT ps;
		HDC hdc = BeginPaint(hWnd, &ps);
		// TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
		EndPaint(hWnd, &ps);
	}break;
	
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

728x90
반응형

+ Recent posts