티스토리 뷰

책 : TCP/IP 윈도우 소켓 프로그래밍

 

간단하게 WINAPI를 활용하여 GUI 환경에서 TCP 소켓 프로그래밍에 대한 코드를 작성하였습니다. 물론 예제에 있는 코드입니다.

작동형식은 아래의 사진과 같이 작동하고, 각 서버와 클라이언트 프로젝트 파일에서 리소스 Dialog를 생성하여야 합니다.

추가 -> 새항목 -> 리소스 -> 리소스 파일 ->Dialog 선택 하시면 생성하실 수 있습니다.


클라코드

더보기
//Client

#pragma warning(disable:4996)
#pragma comment(lib, "ws2_32.lib")
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include "resource.h"

#define SERVERIP "127.0.0.1"
#define SERVERPORT 9000
#define BUFSIZE 512


//윈도우 프로시저
BOOL CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);
//편집 함수
void DisplayText(const char* fmt, ...);
//오류 출력 함수
void err_quit(const char* msg);

//사용자 정의 데이터 수신 함수
int recvn(SOCKET s, char* buf, int len, int flags);
//소켓 통신 스레드 함수
DWORD WINAPI ClientMain(LPVOID arg);

SOCKET sock;
char buf[BUFSIZE + 1];
//이벤트 영역
HANDLE hReadEvent, hWriteEvent;

HWND hSendButton;
HWND hEdit1, hEdit2;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	//이벤트 생성
	hReadEvent = CreateEvent(NULL, FALSE, TRUE, NULL);
	if (hReadEvent == NULL) return 1;
	hWriteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	if (hWriteEvent == NULL) return 1;
	
	//소켓 통신 스레드 생성
	CreateThread(NULL, 0, ClientMain, NULL, 0, NULL);

	//대화 상자 생성
	DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC)&DlgProc);

	//이벤트 제거
	CloseHandle(hReadEvent);
	CloseHandle(hWriteEvent);

	closesocket(sock);

	WSACleanup();
	return 0;
}

BOOL CALLBACK DlgProc(HWND hDlg, UINT uMsg, WPARAM wParm, LPARAM lParm) {
	switch (uMsg) {
	case WM_INITDIALOG:
		hEdit1 = GetDlgItem(hDlg, IDC_EDIT1);
		hEdit2 = GetDlgItem(hDlg, IDC_EDIT2);
		hSendButton = GetDlgItem(hDlg, IDOK);
		SendMessage(hEdit1, EM_SETLIMITTEXT, BUFSIZE, 0);
		return TRUE;
	case WM_COMMAND:
		switch (LOWORD(wParm)) {
		case IDOK:
        	//확인 버튼 끄기(충돌 방지)
			EnableWindow(hSendButton, FALSE);
            //이벤트 기다림
			WaitForSingleObject(hReadEvent, INFINITE);
			GetDlgItemText(hDlg, IDC_EDIT1, buf, BUFSIZE + 1);
			SetEvent(hWriteEvent);
			SetFocus(hEdit1);
			SendMessage(hEdit1, EM_SETSEL, 0, -1);
			return TRUE;
		case IDCANCEL:
			EndDialog(hDlg, IDCANCEL);
			return TRUE;
		}
		return FALSE;
	}
	return FALSE;
}


// 편집 컨트롤 출력 함수
void DisplayText(const char* fmt, ...) {
	va_list arg;
	va_start(arg, fmt);

	char cbuf[256];
	vsprintf(cbuf, fmt, arg);
	int nLength = GetWindowTextLength(hEdit2);
	SendMessage(hEdit2, EM_SETSEL, nLength, nLength);
	SendMessage(hEdit2, EM_REPLACESEL, FALSE, (LPARAM)cbuf);

	va_end(arg);
}

void err_quit(const char* msg) {
	...
}

int recvn(SOCKET s, char* buf, int len, int flag) {
	...
}
DWORD WINAPI ClientMain(LPVOID arg) {
	int retVal;

	WSAData wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
		return 1;

	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock == INVALID_SOCKET)
	{
		printf("socket create error\n");
		return 1;
	}

	SOCKADDR_IN sockaddr;
	ZeroMemory(&sockaddr, sizeof(sockaddr));
	sockaddr.sin_family = AF_INET;
	sockaddr.sin_port = htons(SERVERPORT);
	sockaddr.sin_addr.s_addr = inet_addr(SERVERIP);
	if (::connect(sock, (SOCKADDR*)&sockaddr, sizeof(sockaddr))) err_quit("Connet");
	DisplayText("연결 완료\r\n");

	while (1) {
		WaitForSingleObject(hWriteEvent, INFINITE);

		//문자열 길이가 0이면 보내지 않음
		if (strlen(buf) == 0) {
			EnableWindow(hSendButton, TRUE);
			SetEvent(hReadEvent);
			continue;
		}

		//데이터 보내기 
		retVal = send(sock, buf, strlen(buf), 0);
		if (retVal == SOCKET_ERROR) {
			err_quit("Send(()");
			break;
		}

		DisplayText("[TCP 클라이언트] %d바이트를 보냈습니다.\r\n", retVal);

		retVal = recvn(sock, buf, retVal, 0);
		if (retVal == SOCKET_ERROR) {
			err_quit("recv()");
			break;
		}
		else if (retVal == 0)
			break;

		//받은 데이터 출력
		buf[retVal] = '\0';
		DisplayText("[TCP 클라이언트] %d바이트를 받았습니다.\r\n", retVal);
		DisplayText("[받은 데이터] %s\r\n", buf);

		EnableWindow(hSendButton, TRUE);
		SetEvent(hReadEvent);
	}
	
	return 0;
}

서버코드

더보기
//Server

#pragma comment(lib, "ws2_32")
#pragma warning(disable:4996)
#include<WinSock2.h>
#include<stdlib.h>
#include<Windows.h>
#include<stdio.h>

#define SERVERPORT 9000
#define BUFSIZE 512

//윈도우 프로시저
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParm, LPARAM lParm);

//편집 컨트롤 출력 함수
void DisplayText(const char* fmt, ...);

//오류 출력 함수
void err_quit(const char* msg);

//소켓 통신 함수
DWORD WINAPI ServerMain(LPVOID arg);
DWORD WINAPI ProcessClient(LPVOID arg);

HINSTANCE hInst;		// 인스턴스 핸들
HWND hEdit;				// 편집 컨트롤
CRITICAL_SECTION cs;	// 임계영역

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {

	WNDCLASS wndclass;
	InitializeCriticalSection(&cs);
	hInst = hInstance;

	//윈도우 초기화
	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = WndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = hInstance;
	wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndclass.lpszMenuName = NULL;
	wndclass.lpszClassName = "MyWndClass";
	if (!RegisterClass(&wndclass)) return 1;

	//윈도우 생성
	HWND hWnd = CreateWindow("MyWndClass", "WinApp", WS_OVERLAPPEDWINDOW, 0, 0, 600, 200, NULL, NULL, hInstance, NULL);
	if (hWnd == NULL) return 1;
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	//소켓 통신 쓰레드 생성
	CreateThread(NULL, 0, ServerMain, NULL, 0, NULL);

	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParm, LPARAM lParm) {
	//윈도우 프로시저로 GUI 작동하는 실질적 코드
	switch (uMsg) {
    //처음 생성시
	case WM_CREATE:
		hEdit = CreateWindow("edit", NULL, WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE | ES_READONLY,
			0, 0, 0, 0, hWnd, (HMENU)100, hInst, (LPVOID)TRUE);
		DisplayText("간단한 GUI 응용 프로그램이니다.\r\n");
		DisplayText("인스턴스 핸들 값은 %#x입니다.\r\n", hInst);
		return 0;
    //사이즈 변경시
	case WM_SIZE:
		MoveWindow(hEdit, 0, 0, LOWORD(lParm), HIWORD(lParm), TRUE);
		return 0;
	case WM_SETFOCUS:
		SetFocus(hEdit);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hWnd, uMsg, wParm, lParm);
}

// 편집 컨트롤 출력 함수
void DisplayText(const char* fmt, ...) {
	va_list arg;
	va_start(arg, fmt);

	char cbuf[BUFSIZE+256];
	
    //임계영역 설정
	EnterCriticalSection(&cs);
	vsprintf(cbuf, fmt, arg);
	int nLength = GetWindowTextLength(hEdit);
    //마지막끝으로 포인터 이동
	SendMessage(hEdit, EM_SETSEL, nLength, nLength);
    //글쓰기
	SendMessage(hEdit, EM_REPLACESEL, FALSE, (LPARAM)cbuf);
	LeaveCriticalSection(&cs);

	va_end(arg);
}
void err_quit(const char* msg) {
	,,,
}

DWORD WINAPI ServerMain(LPVOID arg) {
	int retVal;

	WSADATA wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
		return 1;

	SOCKET listenSock = socket(AF_INET, SOCK_STREAM, 0);
	if (listenSock == INVALID_SOCKET) err_quit("socket()");
	//연결과정은 같음.
	SOCKADDR_IN serveraddr;
	ZeroMemory(&serveraddr, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(SERVERPORT);
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	retVal = bind(listenSock, (SOCKADDR*)&serveraddr, sizeof(serveraddr));
	if (retVal == SOCKET_ERROR) err_quit("listen()");

	retVal = listen(listenSock, 10);
	if (retVal == SOCKET_ERROR) err_quit("listen()");

	//데이터 통신에 사용할 변수
	SOCKET clientsock;
	SOCKADDR_IN clientaddr;
	int addrlen;
	HANDLE hThread;

	while (1) {
		//accept
		addrlen = sizeof(clientaddr);
		clientsock = ::accept(listenSock, (SOCKADDR*)&clientaddr, &addrlen);
		if (clientsock == INVALID_SOCKET) {
			err_quit("accept()");
			break;
		}

		//클라 정보 출력
		DisplayText("[TCP서버] 클라이언트 접속 IP : %s %d\r\n",
			inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
		
        //연결된 클라를 상대할 쓰레드 Client 생성
		hThread = CreateThread(NULL, 0, ProcessClient, (LPVOID)clientsock, 0, NULL);
		if (hThread == NULL) closesocket(clientsock);
		else CloseHandle(hThread);
	}
	closesocket(listenSock);
	WSACleanup();
	return 0;
}
DWORD WINAPI ProcessClient(LPVOID arg) {
	SOCKET clientfd = (SOCKET)arg;
	SOCKADDR_IN clientaddr;
	int addrlen;
	int len;
	char buf[BUFSIZE + 1];

	//클라 정보 얻기
	addrlen = sizeof(clientaddr);
	getpeername(clientfd, (SOCKADDR*)&clientaddr, &addrlen);

	while (1) {
		//데이터받기(고정길이)
		int retVal = recv(clientfd, buf, BUFSIZE, 0);
		if (retVal == SOCKET_ERROR) {
			err_quit("Recv");
			break;
		}
		else if (retVal == 0) break;

		//받은 데이터 출력
		buf[retVal] = '\0';
		DisplayText("[TCP/%s : %d] %s\r\n", inet_ntoa(clientaddr.sin_addr),
			ntohs(clientaddr.sin_port), buf);

		retVal = send(clientfd, buf, retVal, 0);
		if (retVal == SOCKET_ERROR) {
			err_quit("Send");
			break;
		}
	}
	closesocket(clientfd);
	DisplayText("[TCP 서버] 클라이언트 종료 : IP  주소 = %s, 포트번호 = %d\r\n",
		inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

	return 0;
}
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함