티스토리 뷰

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

의 마지막장인 채팅 프로그램을 제작하여 봤습니다. 모델은 Select모델이지만 추후에 IOCP 라던지, Overlapped 모델로도 한번 변화시켜볼려고 합니다. 제작할때 오류도 많았지만.. 굉장이 재밌었네요. IPv6 버전은 아쉽게도 안되서 일단 IPv4만 구현해놨습니다! 클라이언트 같은 경우에는 코드가 굉장히 길고 WINAPI로 만들어져 있어서 길이 긴 점 양해 부탁드리겠습니다.

 

먼저 작동 형태입니다. 아래의 사진과 같이 여러개의 클라이언트가 접속했을때, 서버는 클라들이 입력하는 값을 접속한 클라에게 모두 전송하여 나타내는 형태입니다.


먼저 서버 코드입니다. Select ㅡ 모델로 구현되어있고, Select모델에 올렸던 코드와 굉장히 흡사합니다.

더보기

코드 서버

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

#define PORT	9000
#define BUFSIZE 256

//소켓 정보 저장을 위한 구조체와 변수
struct SOCKETINFO {
    SOCKET sock;
    bool isIPv6;
    char buf[BUFSIZE+1];
    int recvBytes;
};

int nTotalSockets = 0;
SOCKETINFO* SocketInfoArray[FD_SETSIZE];

//소켓 관리 함수
BOOL AddSocketInfo(SOCKET sock, BOOL isIPv6);
void RemoveSocketInfo(int nIndeX);

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

int main()
{
    WSADATA wsaData;
    int retVal;
    //윈속 초기화
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        err_quit("WSAStartup");

    //IPv4 소켓 초기화 시작
    //소켓 생성
    SOCKET listensock4= socket(AF_INET, SOCK_STREAM, 0);
    if (listensock4 == INVALID_SOCKET)
        err_quit("socket");
    
    SOCKADDR_IN sock_addr4;
    ZeroMemory(&sock_addr4, sizeof(SOCKADDR_IN));
    sock_addr4.sin_family = AF_INET;
    sock_addr4.sin_port = htons(PORT);
    sock_addr4.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(listensock4, (SOCKADDR*)&sock_addr4, sizeof(sock_addr4)) == SOCKET_ERROR) err_quit("Bind4");
    if (listen(listensock4, SOMAXCONN) == SOCKET_ERROR) err_quit("listen");

    //비동기 소켓으로 전환 하는 과정
    u_long on = 1;
    retVal = ioctlsocket(listensock4, FIONBIO, &on);
    if (retVal == SOCKET_ERROR) err_quit("ioctlsocket()");

    //IPv6 소켓 초기화 시작
    /*SOCKET listensock6 = socket(AF_INET6, SOCK_STREAM, 0);
    if (listensock6 == INVALID_SOCKET)
        err_quit("socket");

    SOCKADDR_IN6 sockaddr6;
    ZeroMemory(&sockaddr6, sizeof(sockaddr6));
    sockaddr6.sin6_family = AF_INET6;
    sockaddr6.sin6_port = htons(PORT);
    sockaddr6.sin6_addr = in6addr_any;

    if (bind(listensock6, (SOCKADDR*)&sockaddr6, sizeof(sockaddr6))) err_quit("Bind6");
    if (listen(listensock6, SOMAXCONN) == SOCKET_ERROR) err_quit("listen");*/

    //데이터 통신에 사용할 변수(공통)
    FD_SET rset;
    SOCKET clientsock;
    int addrlen, i, j;
    //IPv4에 사용할 변수
    SOCKADDR_IN clientaddr4;

    //IPv6에 사용할 변수
    //SOCKADDR_IN6 clientaddr6

    while (1) {
        //소켓 초기화
        FD_ZERO(&rset);
        FD_SET(listensock4 , &rset);
        //FD_SET(listensock6, &rset);
        for (int i = 0; i < nTotalSockets; i++) {
            FD_SET(SocketInfoArray[i]->sock, &rset);
        }
        //select()
        retVal = select(0, &rset, nullptr, nullptr, nullptr);
        if (retVal == SOCKET_ERROR) {
            err_quit("select()");
            break;
        }
        
        //소켓 셋 검사(1) : 클라이언트 접속 수용
        if (FD_ISSET(listensock4, &rset)) {         
            addrlen = sizeof(clientaddr4);
            clientsock = accept(listensock4, (SOCKADDR*)&clientaddr4, &addrlen);
            if (clientsock == INVALID_SOCKET) {
                err_quit("accept()");
                break;
            }
            else {
                //접속할 클라이언트 정보 출력
                printf("[TCPv4 서버] 클라이언트 접속 IP : %s %d\r\n",
                    inet_ntoa(clientaddr4.sin_addr), ntohs(clientaddr4.sin_port));
                AddSocketInfo(clientsock, false);
            }
        }

        //소켓 셋 검사(2) : 데이터 통신
        for (i = 0; i < nTotalSockets; i++) {
            SOCKETINFO* ptr = SocketInfoArray[i];
            if (FD_ISSET(ptr->sock, &rset)) {
                //데이터 받기
                retVal = recv(ptr->sock, ptr->buf + ptr->recvBytes, BUFSIZE - ptr->recvBytes, 0);
                if (retVal == SOCKET_ERROR || retVal == 0) {
                    err_quit("Recv()");
                    RemoveSocketInfo(i);
                    continue;
                }

                ptr->recvBytes += retVal;

                if (ptr->recvBytes == BUFSIZE) {
                    //받은 바이트수 리셋
                    ptr->recvBytes = 0;

                    //현재 접속한 모든 클라이언트에 데이터 보냄!
                    for (j = 0; j < nTotalSockets; j++) {
                        SOCKETINFO* ptr2 = SocketInfoArray[j];
                        retVal = send(ptr2->sock, ptr->buf, BUFSIZE, 0);
                        if (retVal == SOCKET_ERROR) {
                            err_quit("send()");
                            RemoveSocketInfo(j);
                            --j;
                            continue;
                        }
                    }
                }
            }
        }
    }

    //윈속 종료
    WSACleanup();
    return 0;
}
BOOL AddSocketInfo(SOCKET sock, BOOL isIPv6) {
    if (nTotalSockets >= FD_SETSIZE) {
        printf("[오류] 소켓 정보를 추가할 수 없습니다.\n");
        return FALSE;
    }

    SOCKETINFO* ptr = new SOCKETINFO;
    if (ptr == NULL) {
        printf("[오류] 메모리가 부족합니다.\n");
        return FALSE;
    }

    ptr->sock = sock;
    ptr->isIPv6 = isIPv6;
    ptr->recvBytes = 0;
    SocketInfoArray[nTotalSockets++] = ptr;

    return TRUE;
}

//소켓 정보 삭제
void RemoveSocketInfo(int nIndex) {
    SOCKETINFO* ptr = SocketInfoArray[nIndex];
    //클라 정보 얻기
    if (ptr->isIPv6 == false) {
        SOCKADDR_IN clientaddr;
        int addrlen = sizeof(clientaddr);
        getpeername(ptr->sock, (SOCKADDR*)&clientaddr, &addrlen);
        printf("[TCP 서버] 클라이언트 종료 : IP  주소 = %s, 포트번호 = %d\r\n",
            inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
    }
    else {
        SOCKADDR_IN6 clientaddr;
        int addrlen = sizeof(clientaddr);
        getpeername(ptr->sock, (SOCKADDR*)&clientaddr, &addrlen);
        char ipaddr[50];
        DWORD ipaddrlen = sizeof(ipaddr);
        WSAAddressToString((SOCKADDR*)&clientaddr, sizeof(clientaddr), NULL, ipaddr, &ipaddrlen);
        printf("[TCPv6 서버] 클라이언트 종료 : %s\n", ipaddr);
    }

    closesocket(ptr->sock);
    delete ptr;

    if (nIndex != (nTotalSockets - 1))
        SocketInfoArray[nIndex] = SocketInfoArray[nTotalSockets - 1];

    --nTotalSockets;
}

void err_quit(const char* msg) {
    LPVOID lpMsgBuf;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, WSAGetLastError(),
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf, 0, NULL);
    printf("[%s] %s\n", msg, (char*)lpMsgBuf);
    LocalFree(lpMsgBuf);
    exit(1);
}

아래는 클라이언트 입니다. 코드가 대략 500자 정도 되고, ClientMain이랑 Thread 위주로 보시면 됩니다. 다른 부분들은 WINAPI구현형태라서 그냥 복사하셔서 실행해보시면 됩니다. 단 리소스 형태는 똑같이 해주셔야 합니다.

순서대로 맞춰주시면 됩니다.

클라이언트 코드입니다.

더보기

 

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

#define SERVERIPV4 "127.0.0.1"
#define SERVERIPV6 "::1"
#define SERVERPORT 9000

#define BUFSIZE 256
#define MSGSIZE (BUFSIZE-sizeof(int))

#define CHATTING 1000
#define DRAWLINE 1001

#define WM_DRAWIT (WM_USER+1)

//공통 메시지 형식
//sizeof(COMM_MSG) == 256
struct COMM_MSG {
	int type;
	char dummy[MSGSIZE];
};

//채팅 메시지 형식
//sizeof(CHAT_MSG) == 256
struct CHAT_MSG {
	int type;
	char buf[MSGSIZE];
};

//선그리기 메시지 형식
//sizeof(DRAWLINE_MS) == 256
struct DRAWLINE_MSG {
	int type;
	int color;
	int x0, y0;
	int x1, y1;
	char dummy[BUFSIZE - 6 * sizeof(int)];
};

static HINSTANCE		g_hInst;						//응용프로그램 인스턴스 핸들
static HWND				g_hDrawWnd;						//그림을 그릴 윈도우
static HWND				g_hButtonSendMsg;				//메시지 전송 버튼
static HWND				g_hEditStatus;					//받은 메시지 출력
static char				g_ipaddr[64];					//서버 IP 주소
static u_short			g_port;							//서버 포트 번호
static BOOL				g_isIPv6;						//4, 6 판별
static HANDLE			g_hClientThread;				//스레드 핸들
static volatile BOOL	g_bStart;						//통신 시작 여부
static SOCKET			g_sock;							//클라이언트 소켓
static HANDLE			g_hReadEvent, g_hWriteEvent;	//이벤트 핸들
static CHAT_MSG			g_chatmsg;						//채팅 메시지 저장
static DRAWLINE_MSG		g_drawmsg;						//선 그리기 메시지 저장
static int				g_drawcolor;					//선 그리기 색상

//대화 상자 프로시저
BOOL CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);

//소켓 통신 스레드 함수
DWORD WINAPI ClientMain(LPVOID arg);
DWORD WINAPI ReadThread(LPVOID arg);
DWORD WINAPI WriteThread(LPVOID arg);

//자식 윈도우 프로시저
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
//편집 컨트롤 출력 함수
void DisplayText(const char* fmt, ...);
//사용자 정의 데이터 수신 함수
int recvn(SOCKET s, char* buf, int len, int flags);
//오류 출력 함수
void err_quit(const char* msg);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	WSAData wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
		return 1;

	//이벤트 생성
	g_hReadEvent = CreateEvent(NULL, FALSE, TRUE, NULL);
	if (g_hReadEvent == NULL) return 1;
	g_hWriteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	if (g_hWriteEvent == NULL) return 1;

	//변수 초기화
	g_chatmsg.type = CHATTING;
	g_drawmsg.type = DRAWLINE;
	g_drawmsg.color = RGB(255, 0, 0);

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

	//이벤트 제거
	CloseHandle(g_hReadEvent);
	CloseHandle(g_hWriteEvent);

	WSACleanup();
	return 0;
}

BOOL CALLBACK DlgProc(HWND hDlg, UINT uMsg, WPARAM wParm, LPARAM lParm) {
	static HWND	hButtonIsIPv6;
	static HWND	hEditPadder;
	static HWND	hEditPort;
	static HWND	hButtonConnect;
	static HWND	hEditMsg;
	static HWND	hColorRed;
	static HWND	hColorGreen;
	static HWND	hColorBlue;

	switch (uMsg) {
	case WM_INITDIALOG:
		// 컨트롤 핸들 얻기
		hButtonIsIPv6 = GetDlgItem(hDlg, IDC_ISIPV6);
		hEditPadder = GetDlgItem(hDlg, IDC_IPADDR);
		hEditPort = GetDlgItem(hDlg, IDC_PORT);
		hButtonConnect = GetDlgItem(hDlg, IDC_CONNECT);
		g_hButtonSendMsg = GetDlgItem(hDlg, IDC_SENDMSG);
		hEditMsg = GetDlgItem(hDlg, IDC_MSG);
		g_hEditStatus = GetDlgItem(hDlg, IDC_STATUS);
		hColorRed = GetDlgItem(hDlg, IDC_COLORRED);
		hColorGreen = GetDlgItem(hDlg, IDC_COLORGREE);
		hColorBlue = GetDlgItem(hDlg, IDC_COLORBLUE);

		//컨트롤 초기화
		SendMessage(hEditMsg, EM_SETLIMITTEXT, MSGSIZE, 0);
		EnableWindow(g_hButtonSendMsg, FALSE);
		SetDlgItemText(hDlg, IDC_IPADDR, SERVERIPV4);
		SetDlgItemInt(hDlg, IDC_PORT, SERVERPORT, FALSE);
		SendMessage(hColorRed, BM_SETCHECK, BST_CHECKED, 0);
		SendMessage(hColorGreen, BM_SETCHECK, BST_CHECKED, 0);
		SendMessage(hColorBlue, BM_SETCHECK, BST_CHECKED, 0);

		//윈도우 클래스 등록
		WNDCLASS wndclass;
		wndclass.style = CS_HREDRAW | CS_VREDRAW;
		wndclass.lpfnWndProc = WndProc;
		wndclass.cbClsExtra = 0;
		wndclass.cbWndExtra = 0;
		wndclass.hInstance = g_hInst;
		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;

		//자식 윈도우 생성
		g_hDrawWnd = CreateWindow("MyWndClass", "그림 그릴 윈도우", WS_CHILD, 450, 38, 425, 415, hDlg, (HMENU)NULL, g_hInst, NULL);
		if (g_hDrawWnd == NULL) return 1;
		ShowWindow(g_hDrawWnd, SW_SHOW);
		UpdateWindow(g_hDrawWnd);
		return TRUE;

	case WM_COMMAND:
		switch (LOWORD(wParm)) {
		case IDC_ISIPV6:
			g_isIPv6 = SendMessage(hButtonIsIPv6, BM_GETCHECK, 0, 0);
			if (g_isIPv6 == false)
				SetDlgItemText(hDlg, IDC_IPADDR, SERVERIPV4);
			else
				SetDlgItemText(hDlg, IDC_IPADDR, SERVERIPV6);
			return TRUE;
		case IDC_CONNECT:
			GetDlgItemText(hDlg, IDC_IPADDR, g_ipaddr, sizeof(g_ipaddr));
			g_port = GetDlgItemText(hDlg, IDC_PORT, NULL, FALSE);
			g_isIPv6 = SendMessage(hButtonIsIPv6, BM_GETCHECK, 0, 0);

			//소켓 통신 스레드 시작
			g_hClientThread = CreateThread(NULL, 0, ClientMain, NULL, 0, NULL);
			if (g_hClientThread == NULL) {
				MessageBox(hDlg, "클라이언트를 시작할 수 없습니다. \r\n프로그램을 종료합니다.", "실패!", MB_ICONERROR);
				EndDialog(hDlg, 0);
			}
			else {
				EnableWindow(hButtonConnect, FALSE);
				while (g_bStart == FALSE);
				EnableWindow(hButtonConnect, FALSE);
				EnableWindow(hEditPadder, FALSE);
				EnableWindow(hEditPort, FALSE);
				EnableWindow(g_hButtonSendMsg, TRUE);
				SetFocus(hEditMsg);
			}
			return TRUE;

		case IDC_SENDMSG:
			//읽기 완료를 기다림
			WaitForSingleObject(g_hReadEvent, INFINITE);
			GetDlgItemText(hDlg, IDC_MSG, g_chatmsg.buf, MSGSIZE);
			//쓰기 완료를 알림
			SetEvent(g_hWriteEvent);
			//입력된 텍스트 전체를 선택 표시
			SendMessage(hEditMsg, EM_SETSEL, 0, -1);
			return TRUE;

		case IDC_COLORRED:
			g_drawmsg.color = RGB(255, 0, 0);
			return TRUE;

		case IDC_COLORGREE:
			g_drawmsg.color = RGB(0, 255, 0);
			return TRUE;

		case IDC_COLORBLUE:
			g_drawmsg.color = RGB(0, 0, 255);
			return TRUE;

		case IDCANCEL:
			if (MessageBox(hDlg, "정말로 종료하시겠습니까?", "질문", MB_YESNO | MB_ICONQUESTION) == IDYES) {
				closesocket(g_sock);
				EndDialog(hDlg, IDCANCEL);
			}
			return TRUE;

		}
		return FALSE;
	}
	return FALSE;
}

//소켓 통신 함수
DWORD WINAPI ClientMain(LPVOID arg) {
	int retVal;

	if (g_isIPv6 == false) {
		g_sock = socket(AF_INET, SOCK_STREAM, 0);
		if (g_sock == INVALID_SOCKET) err_quit("socket()");

		//connect
		SOCKADDR_IN serveraddr;
		ZeroMemory(&serveraddr, sizeof(serveraddr));
		serveraddr.sin_family = AF_INET;
		serveraddr.sin_port = htons(SERVERPORT);
		serveraddr.sin_addr.s_addr = inet_addr(g_ipaddr);
		retVal = connect(g_sock, (SOCKADDR*)&serveraddr, sizeof(serveraddr));
		if (retVal == SOCKET_ERROR) err_quit("connect4()");
	}
	else {
		g_sock = socket(AF_INET6, SOCK_STREAM, 0);
		if (g_sock == INVALID_SOCKET) err_quit("socket()");

		//connect
		SOCKADDR_IN6 serveraddr;
		ZeroMemory(&serveraddr, sizeof(serveraddr));
		serveraddr.sin6_family = AF_INET6;
		int addrlen = sizeof(serveraddr);
		WSAStringToAddress(g_ipaddr, AF_INET6, NULL, (SOCKADDR*)&serveraddr, &addrlen);
		serveraddr.sin6_port = htons(g_port);
		retVal = connect(g_sock, (SOCKADDR*)&serveraddr, sizeof(serveraddr));
		if (retVal == SOCKET_ERROR) err_quit("connect6()");
	}
	MessageBox(NULL, "서버에 접속했습니다.", "성공!", MB_ICONINFORMATION);

	//읽기 & 쓰기 스레드 생성
	HANDLE hThread[2];
	hThread[0] = CreateThread(NULL, 0, ReadThread, NULL, 0, NULL);
	hThread[1] = CreateThread(NULL, 0, WriteThread, NULL, 0, NULL);
	if (hThread[0] == NULL || hThread[1] == NULL) {
		MessageBox(NULL, "스레드를 시작할 수 없습니다." "\r\n프로그램을 종료합니다.", "실패!", MB_ICONERROR);
		exit(1);
	}

	g_bStart = TRUE;

	//스레드 종료 대기
	retVal = WaitForMultipleObjects(2, hThread, FALSE, INFINITE);
	retVal -= WAIT_OBJECT_0;
	if (retVal == 0)
		TerminateThread(hThread[1], 1);
	else
		TerminateThread(hThread[0], 1);
	CloseHandle(hThread[0]);
	CloseHandle(hThread[1]);

	g_bStart = FALSE;

	MessageBox(NULL, "서버가 접속을 끊었습니다.", "알림", MB_ICONINFORMATION);
	EnableWindow(g_hButtonSendMsg, FALSE);

	closesocket(g_sock);
	return 0;
}

//데이터 받기
DWORD WINAPI ReadThread(LPVOID arg) {
	int retVal;
	COMM_MSG comm_msg;
	CHAT_MSG* chat_msg;
	DRAWLINE_MSG* draw_msg;

	while (1) {
		retVal = recvn(g_sock, (char*)&comm_msg, BUFSIZE, 0);
		if (retVal == 0 || retVal == SOCKET_ERROR) {
			break;
		}
		if (comm_msg.type == CHATTING) {
			chat_msg = (CHAT_MSG*)&comm_msg;
			DisplayText("[받은 메시지] %s\r\n", chat_msg->buf);
		}
		else if (comm_msg.type == DRAWLINE) {
			draw_msg = (DRAWLINE_MSG*)&comm_msg;
			g_drawcolor = draw_msg->color;
			SendMessage(g_hDrawWnd, WM_DRAWIT, MAKEWPARAM(draw_msg->x0, draw_msg->y0), MAKELPARAM(draw_msg->x1, draw_msg->y1));
		}
	}

	return 0;
}

//데이터 보내기
DWORD WINAPI WriteThread(LPVOID arg) {
	int retVal;

	//서버와 데이터 통신
	while (1) {
		//쓰기 완료 기다리기
		WaitForSingleObject(g_hWriteEvent, INFINITE);

		
		//문자열 길이가 0이면 보내지 않음.
		if (strlen(g_chatmsg.buf) == 0) {
			EnableWindow(g_hButtonSendMsg, TRUE);
			SetEvent(g_hReadEvent);
			continue;
		}
		//데이터 보내기
		retVal = send(g_sock, (char*)&g_chatmsg, BUFSIZE, 0);
		if (retVal == SOCKET_ERROR)
			break;
		//메시지 전송 버튼 활성화
		EnableWindow(g_hButtonSendMsg, TRUE);
		SetEvent(g_hReadEvent);
	}

	return 0;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParm, LPARAM lParm) {
	HDC hDC;
	int cx, cy;
	PAINTSTRUCT ps;
	RECT rect;
	HPEN hPen, hOldPen;
	static HBITMAP hBitmap;
	static HDC hDCMem;
	static int x0, y0;
	static int x1, y1;
	static BOOL bDrawing = FALSE;

	switch (uMsg) {
	case WM_CREATE:
		hDC = GetDC(hWnd);

		//화면 저장할 비트맵 생성
		cx = GetDeviceCaps(hDC, HORZRES);
		cy = GetDeviceCaps(hDC, VERTRES);
		hBitmap = CreateCompatibleBitmap(hDC, cx, cy);

		//메모리 DC 생성
		hDCMem = CreateCompatibleDC(hDC);

		//비트맵 선택 후 메모리 DC 화면을 흰색으로 칠함
		SelectObject(hDCMem, hBitmap);
		SelectObject(hDCMem, GetStockObject(WHITE_BRUSH));
		SelectObject(hDCMem, GetStockObject(WHITE_PEN));
		Rectangle(hDCMem, 0, 0, cx, cy);

		ReleaseDC(hWnd, hDC);
		return 0;
	case WM_LBUTTONDOWN:
		x0 = LOWORD(lParm);
		y0 = HIWORD(lParm);
		bDrawing = true;
		return 0;
	case WM_MOUSEMOVE:
		if (bDrawing && g_bStart) {
			x1 = LOWORD(lParm);
			y1 = HIWORD(lParm);

			//선 그리기 메시지 보내기
			g_drawmsg.x0 = x0;
			g_drawmsg.x1 = x1;
			g_drawmsg.y0 = y0;
			g_drawmsg.y1 = y1;
			send(g_sock, (char*)&g_drawmsg, BUFSIZE, 0);

			x0 = x1;
			y0 = y1;
		}
		return 0;
	case WM_LBUTTONUP:
		bDrawing = FALSE;
		return 0;
	case WM_DRAWIT:
		hDC = GetDC(hWnd);
		hPen = CreatePen(PS_SOLID, 3, g_drawcolor);

		//화면에 그리기
		hOldPen = (HPEN)SelectObject(hDC, hPen);
		MoveToEx(hDC, LOWORD(wParm), HIWORD(wParm), NULL);
		LineTo(hDC, LOWORD(lParm), HIWORD(lParm));
		SelectObject(hDC, hOldPen);

		//메모리 비트맵에 그리기
		hOldPen = (HPEN)SelectObject(hDCMem, hPen);
		MoveToEx(hDCMem, LOWORD(wParm), HIWORD(wParm), NULL);
		LineTo(hDCMem, LOWORD(lParm), HIWORD(lParm));
		SelectObject(hDC, hOldPen);

		DeleteObject(hPen);
		ReleaseDC(hWnd, hDC);
		return 0;
	case WM_PAINT:
		hDC = BeginPaint(hWnd, &ps);

		//메모리 비트맵에 저장된 그림을 화면에 전송
		GetClientRect(hWnd, &rect);
		BitBlt(hDC, 0, 0, rect.right - rect.left, rect.bottom - rect.top, hDCMem, 0, 0, SRCCOPY);

		EndPaint(hWnd, &ps);
		return 0;
	case WM_DESTROY:
		DeleteObject(hBitmap);
		DeleteDC(hDCMem);
		PostQuitMessage(0);
		return 0;
	}

	return DefWindowProc(hWnd, uMsg, wParm, lParm);
}
//편집 컨트롤 출력 함수
void DisplayText(const char* fmt, ...) {
	va_list arg;
	va_start(arg, fmt);

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

	va_end(arg);
}

int recvn(SOCKET s, char* buf, int len, int flag) {
	int received;
	char* ptr = buf;
	int left = len;

	while (left > 0) {
		received = recv(s, ptr, left, flag);
		if (received == SOCKET_ERROR)
			return SOCKET_ERROR;
		else if (received == 0)
			break;
		left -= received;
		ptr += received;
	}
	return (len - left);
}

void err_quit(const char* msg) {
	LPVOID lpMsgBuf;
	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
		NULL, WSAGetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR)&lpMsgBuf, 0, NULL);
	MessageBox(NULL, (LPCSTR)lpMsgBuf, msg, MB_ICONERROR);
	LocalFree(lpMsgBuf);
	exit(1);
}
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
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
글 보관함