01
06

프로세스(Process)

 

컴퓨터에서 연속적으로 실행되고 있는 컴퓨터 프로그램이다.

Code, Data, Stack, Heap의 구조로 되어 있는 독립된 메모리 영역이다.

 

Code 텍스트, 코드영역
Data 전역변수, 정적변수, 배열, 구조체
Heap 동적 데이터 (new delete)
Stack 지역변수, 매개변수, 임시메모리

 

쓰레드(Thread)

 

CPU 코어에서 돌아가는 프로그램 단위를 쓰레드(Thread)라고 한다.

한 개의 프로세스에는 최소 한개의 쓰레드로 돌아가며, 프로세스에서

여러개의 쓰레드로 구성하는걸 멀티 쓰레드라고 한다.

 

 

하나의 프로세스에서 여러 쓰레드에서 나누면

자원을 효율적으로 관리 할 수 있고, 응답시간이 단축된다는 장점이 있지만

(쓰레드를 사용하면 일의 분업화가 된다. )

단점으로는 동기화 문제가 있다.

 

동기화 문제

쓰레드A와 쓰레드B로 왔다 갔다(스위칭)하면서 경쟁하는데

둘의 조건이 안맞아 쓰레드끼리 서로 대기하는 교착상태(DeadLock)이 발생한다.

실행 순서를 제어해야하는데, 이런 기술들을 동기화(Synchronization)라고 한다. 

 

쓰레드 스케줄링 -> 쓰레드를 얼마 만큼 간격으로 실행할걱인가 결정하는 정책

스위칭 -> 스레드 A는 일정시간(0.02초) 퀀텀시간동안 실행하고 잠시 중지하고 스레드 B로 스위칭한다.

스레드 컨텍스트 -> 쓰레드 스위칭간에 작업상태 정보를 저장해 놓는 것


클라이언트/서버 멀티쓰레드 C++ 채팅 프로그램

c++에서 쓰레드를 생성하는 방법은 두가지가 있다.

1. Window API 쓰레드

2. C++11부터 지원하는 쓰레드. 

 

C++11 부터 지원하는 쓰레드 방식으로 생성한다.

//C++
HANDLE CreateThread(
  [in, optional]  LPSECURITY_ATTRIBUTES   lpThreadAttributes,
  [in]            SIZE_T                  dwStackSize,
  [in]            LPTHREAD_START_ROUTINE  lpStartAddress,
  [in, optional]  __drv_aliasesMem LPVOID lpParameter,
  [in]            DWORD                   dwCreationFlags,
  [out, optional] LPDWORD                 lpThreadId
);

 

 lpThreadAttributes, // 보안 속성 지정
 dwStackSize,         // 스택 크기를 지정
 lpStartAddress,      // 스레드의 시작함수를 지정
 lpParameter,         //시작 함수의 인자값
 dwCreationFlags,   // 스레드의 특성 지정 (바로 시작 OR 대기상태)
 lpThreadId           // 스레드의 ID를 반환함

반환값으로는 스레드 핸들값을 반환한다.

예시

패킷 전송하는 스레드와 패킷을 받아오는 스레드

 

스레드를 종료하는 방법은 함수안에서, 밖에서 하는 함수가 있지만

제일 좋은 방법은 시작함수에서 반환하는 것이라고 한다.

 

스래드 중지 및 재실행

// 해당 스레드의 동작을 일시 중지 시킨다.
DWORD SuspendThread(HANDLE hThread);
// 일시 정지된 스레드의 수행을 재개한다.
DWORD ResumeThread(HANDLE hThread);

 

 

더보기

클라이언트

#define ServerIP "192.xxx.0.xx"
#define ServerIP2 "192.xxx.0.xx"
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <winsock2.h>
#include <conio.h>
#include "KPacket.h"
#pragma comment	(lib, "ws2_32.lib")
int SendMsg(SOCKET sock, char* msg, WORD type);
int SendPacket(SOCKET sock, char* msg, WORD type);
int RecvPacketHeader(SOCKET sock, UPACKET& recvPacket);
int RecvPacketData(SOCKET sock, UPACKET& recvPacket);

DWORD WINAPI SendThread(LPVOID IpThreadParameter);
DWORD WINAPI RecvThread(LPVOID IpThreadParameter);
//SendThread가 리턴이 되면 쓰레드가 종료가 된다.
//클라이언트
void main()
{
	/*WSACleanup 함수와 쌍을 이뤄 소켓 프로그램의 시작과 끝을 나타냄*/
	WSADATA wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
	{
		return;
	}
	//TCP - SOCK_STREAM , UDP - SOCK_DGRAM

	SOCKET sock = socket(AF_INET,
		SOCK_STREAM, 0);

	SOCKADDR_IN sa;
	ZeroMemory(&sa, sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_port = htons(10000);
	sa.sin_addr.s_addr = inet_addr(ServerIP);
	int iRet = connect(
		sock,
		(sockaddr*)&sa,
		sizeof(sa));
	if (iRet == SOCKET_ERROR)
	{
		return;
	}
	std::cout << "접속성공!" << std::endl;

	//논블로킹 소켓 0이 아니라면 논블로킹 모드로 설정
	u_long on = 1;
	ioctlsocket(sock, FIONBIO, &on);

	//쓰레드 생성에는 두가지 방법이 있음
	// Window API 방법, C++ 11 방법, :: 범위해결자
	//c++
	DWORD ThreadIndex;
	HANDLE hThread_Send = ::CreateThread(0, 0, SendThread,
		(LPVOID)sock, 0, &ThreadIndex);

	HANDLE hThread_Recv = ::CreateThread(0, 0, RecvThread,
		(LPVOID)sock, 0, &ThreadIndex);

	//메인 쓰레드 작업
	while (1)
	{
		Sleep(100);
	}
	std::cout << "접속종료" << std::endl;

	CloseHandle(hThread_Send); //쓰레드 종료가 아닌 제어를 운영체제에게 넘겨주는 것
	CloseHandle(hThread_Recv); //쓰레드 종료가 아닌 제어를 운영체제에게 넘겨주는 것
	closesocket(sock);
	WSACleanup();
}

DWORD __stdcall RecvThread(LPVOID IpThreadParameter)
{
	SOCKET sock = (SOCKET)IpThreadParameter;
	while (1)
	{
		UPACKET packet;
		int iRet = RecvPacketHeader(sock, packet);
		if (iRet < 0) break;
		if (iRet == 1)
		{
			int iRet = RecvPacketData(sock, packet);
			if (iRet < 0) break;
			if (iRet == 0) continue;
			// 메세지 처리
			KPacket data;
			data.m_uPacket = packet;
			KChatting recvdata;
			ZeroMemory(&recvdata, sizeof(recvdata));
			data >> recvdata.index >> recvdata.name
				>> recvdata.damage >> recvdata.message;
			std::cout << "\n" <<
				"[" << recvdata.name << "]"
				<< recvdata.message;
		}
	}
	return 0;
}

DWORD __stdcall SendThread(LPVOID IpThreadParameter)
{
	SOCKET sock = (SOCKET)IpThreadParameter;
	char szBuffer[256] = { 0, };
	while (1)
	{
		ZeroMemory(szBuffer, sizeof(char) * 256);
		fgets(szBuffer, 256, stdin);
		if (strlen(szBuffer) == 0)
		{
			std::cout << "정상 종료됨.." << std::endl;
			break;
		}

		// 방법 1
		//int iSendByte = SendMsg(sock, szBuffer, PACKET_CHAT_MSG);
		// 방법 2
		int iSendByte = SendPacket(sock, szBuffer, PACKET_CHAT_MSG);
		if (iSendByte < 0)
		{
			std::cout << "비정상 종료됨.." << std::endl;
			break;
		}
	}
	return 0;
}



int SendMsg(SOCKET sock, char* msg, WORD type)
{
	// 1번 패킷 생성
	UPACKET packet;
	ZeroMemory(&packet, sizeof(packet));
	//메세지 내용에서 헤더 사이즈 추가
	packet.ph.len = strlen(msg) + PACKET_HEADER_SIZE;
	packet.ph.type = type;
	memcpy(packet.msg, msg, strlen(msg));
	// 2번 패킷 전송 : 운영체제 sendbuffer(short바이트), recvbuffer
	char* pMsg = (char*)&packet;
	int iSendSize = 0;
	do {
		int iSendByte = send(sock, &pMsg[iSendSize],
			packet.ph.len - iSendSize, 0);
		if (iSendByte == SOCKET_ERROR)
		{
			if (WSAGetLastError() != WSAEWOULDBLOCK)
			{
				return -1;
			}
		}
		iSendSize += iSendByte;
	} while (iSendSize < packet.ph.len);
	return iSendSize;
}
//패킷 전송
int SendPacket(SOCKET sock, char* msg, WORD type)
{

	// 1번 패킷 생성
	KPacket tPacket(type);

	//index, name[20], damage, message[256]
	tPacket << 999 << "최지원" << (short)50 << msg;
	
	//생성자로 패킷 생성자로 패킷을 받아 패킷을 만듬
	KPacket tPacketTest(tPacket);

	KChatting recvdata;
	ZeroMemory(&recvdata, sizeof(recvdata));
	
	//패킷에서 정보 파싱
	tPacketTest >> recvdata.index >> recvdata.name
		>> recvdata.damage >> recvdata.message;

	char* pData = (char*)&tPacket.m_uPacket;

	int iSendSize = 0;
	do {
		int iSendByte = send(sock, &pData[iSendSize],
			tPacket.m_uPacket.ph.len - iSendSize, 0);
		if (iSendByte == SOCKET_ERROR)
		{
			if (WSAGetLastError() != WSAEWOULDBLOCK)
			{
				return -1;
			}
		}
		iSendSize += iSendByte;
	} while (iSendSize < tPacket.m_uPacket.ph.len);
	return iSendSize;
}
//패킷 헤더만 받아오기
int RecvPacketHeader(SOCKET sock, UPACKET& recvPacket)
{
	char szRecvBuffer[256] = { 0, };
	//패킷헤더 받기
	ZeroMemory(&recvPacket, sizeof(recvPacket));
	bool bRun = true;
	int iRecvSize = 0;
	//페킷 헤더 (4바이트)가 될때까지 do while문 
	do {
		int iRecvByte = recv(sock, szRecvBuffer,
			PACKET_HEADER_SIZE, 0);
		iRecvSize += iRecvByte;
		if (iRecvByte == 0)
		{
			closesocket(sock);
			std::cout << " 접속종료됨." << std::endl;
			return -1;
		}
		if (iRecvByte == SOCKET_ERROR)
		{
			int iError = WSAGetLastError();
			if (iError != WSAEWOULDBLOCK)
			{
				std::cout << " 비정상 접속종료됨." << std::endl;
				return -1;
			}
			else
			{
				return 0;
			}
		}
	} while (iRecvSize < PACKET_HEADER_SIZE);
	memcpy(&recvPacket.ph, szRecvBuffer, PACKET_HEADER_SIZE);
	return 1;
}
//패킷 헤더 제외한 패킷 받기
int RecvPacketData(SOCKET sock, UPACKET& recvPacket)
{
	// 데이터 받기
	int iRecvSize = 0;
	do {
		int iRecvByte = recv(sock, recvPacket.msg,
			recvPacket.ph.len - PACKET_HEADER_SIZE - iRecvSize, 0);
		iRecvSize += iRecvByte;
		if (iRecvByte == 0)
		{
			closesocket(sock);
			std::cout << " 접속종료됨." << std::endl;
			return -1;
		}
		if (iRecvByte == SOCKET_ERROR)
		{
			int iError = WSAGetLastError();
			if (iError != WSAEWOULDBLOCK)
			{
				std::cout << " 비정상 접속종료됨." << std::endl;
				return -1;
			}
			else
			{
				return 0;
			}
		}
	} while (iRecvSize < recvPacket.ph.len - PACKET_HEADER_SIZE);
	return 1;
}

서버

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <winsock2.h>
#include <list>
#include <string>
#include "KPacket.h"
#pragma comment	(lib, "ws2_32.lib")

//서버
struct User
{
	SOCKET		m_Sock;
	SOCKADDR_IN m_Addr;
	std::string m_csName;
	short       m_iPort;
	void set(SOCKET sock, SOCKADDR_IN addr)
	{
		m_Sock = sock;
		m_Addr = addr;
		//네트워크 주소 변환 함수 빅엔디안(32bit) ->  
		m_csName = inet_ntoa(addr.sin_addr);
		//엔디안은 메모리 연속된 대상을 배열하는 방법으로 
		// Network Byte 순서를 To Host의 Byte 순서로 바꿈
		m_iPort = ntohs(addr.sin_port);
	}
};

int SendMsg(SOCKET sock, char* msg, WORD type)
{
	//1. 패킷을 객체 생성
	UPACKET packet;
	ZeroMemory(&packet, sizeof(packet));
	//패킷의 길이는 헤더 사이즈 + 메세지 사이즈
	packet.ph.len = strlen(msg) + PACKET_HEADER_SIZE;
	packet.ph.type = type;
	//메세지 받은걸로 memcpy로 패킷 만듬
	memcpy(packet.msg, msg, strlen(msg));

	// 2번 패킷 전송 : 운영체제 sendbuffer(short바이트)
	char* pMsg = (char*)&packet;
	int iSize = 0;

	do {
		//TCP는 연속적이지만 바이트 단위로 나눠져 보내질도 있음
		int iSendByte = send(sock, &pMsg[iSize],
			packet.ph.len - iSize, 0);

		if (iSendByte == SOCKET_ERROR)
		{
			if (WSAGetLastError() != WSAEWOULDBLOCK)
			{
				return -1;
			}
		}

		iSize += iSendByte;
	} while (iSize < packet.ph.len);
	return iSize;
}
//패킷을 받았을때.
int SendMsg(SOCKET sock, UPACKET& packet)
{
	char* pMsg = (char*)&packet;
	int iSize = 0;
	do {
		int iSendByte = send(sock, &pMsg[iSize],
			packet.ph.len - iSize, 0);

		if (iSendByte == SOCKET_ERROR)
		{
			if (WSAGetLastError() != WSAEWOULDBLOCK)
			{
				return -1;
			}
		}

		iSize += iSendByte;
	} while (iSize < packet.ph.len);
	return iSize;
}

void main()
{
	WSADATA wsa;
	//2.2 버젼 WSA 
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
	{
		return;
	}
	SOCKET ListenSock = socket(AF_INET, SOCK_STREAM, 0);
	SOCKADDR_IN sa;
	ZeroMemory(&sa, sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_port = htons(10000);
	sa.sin_addr.s_addr = htonl(INADDR_ANY);
	//소켓에 주소 할당
	int iRet = bind(ListenSock, (sockaddr*)&sa, sizeof(sa));
	if (iRet == SOCKET_ERROR)  return;
	//클라이언트로 부터 연결 요청을 처리할수있는 상태를 만듬 
	iRet = listen(ListenSock, SOMAXCONN);
	if (iRet == SOCKET_ERROR)  return;

	SOCKADDR_IN clientAddr;
	int iLen = sizeof(clientAddr);

	std::cout<< "Server Start." << std::endl;

	//non blocking socket 0이면 블락킹 소켓
	u_long on = 1;
	ioctlsocket(ListenSock, FIONBIO, &on);

	std::list<User> userlist;

	while (1)
	{
		SOCKET clientSock = accept(ListenSock,
			(sockaddr*)&clientAddr, &iLen);

		if (clientSock == SOCKET_ERROR)
		{
			int iError = WSAGetLastError();
			if (iError != WSAEWOULDBLOCK)
			{
				std::cout << "ErrorCode=" << iError << std::endl;
				break;
			}
		}
		//클라이언트가 접속 시 시작함
		else
		{
			User user;
			user.set(clientSock, clientAddr);
			userlist.push_back(user);
			std::cout
				<< "ip =" << inet_ntoa(clientAddr.sin_addr)
				<< "port =" << ntohs(clientAddr.sin_port)
				<< "  " << std::endl;
			u_long on = 1;
			ioctlsocket(clientSock, FIONBIO, &on);
			std::cout << userlist.size() << " 명 접속중." << std::endl;
		}

		if (userlist.size() > 0)
		{
			std::list<User>::iterator iter;
			for (iter = userlist.begin(); iter != userlist.end(); )
			{
				User user = *iter;
				char szRecvBuffer[256] = { 0, };
				//패킷헤더 받기
				UPACKET recvPacket;
				ZeroMemory(&recvPacket, sizeof(recvPacket));
				int iRecvSize = 0;
				do {
					int iRecvByte = recv(user.m_Sock, szRecvBuffer,
						PACKET_HEADER_SIZE, 0);
					iRecvSize += iRecvByte;
					if (iRecvByte == 0)
					{
						closesocket(user.m_Sock);
						iter = userlist.erase(iter);
						std::cout << user.m_csName << " 접속종료됨." << std::endl;
						break;
					}
					if (iRecvByte == SOCKET_ERROR)
					{
						int iError = WSAGetLastError();
						if (iError != WSAEWOULDBLOCK)
						{
							iter = userlist.erase(iter);
							std::cout << user.m_csName << " 비정상 접속종료됨." << std::endl;
							break;
						}
						else
						{
							break;
						}
					}
				} while (iRecvSize < PACKET_HEADER_SIZE);

				if (iRecvSize == SOCKET_ERROR)
				{
					if (iter != userlist.end())
					{
						iter++;
					}
					continue;
				}

				memcpy(&recvPacket.ph, szRecvBuffer, PACKET_HEADER_SIZE);
				// 데이터 받기
				iRecvSize = 0;
				do {
					int iRecvByte = recv(user.m_Sock, recvPacket.msg,
						recvPacket.ph.len - PACKET_HEADER_SIZE - iRecvSize, 0);
					iRecvSize += iRecvByte;
					if (iRecvByte == 0)
					{
						closesocket(user.m_Sock);
						iter = userlist.erase(iter);
						std::cout << user.m_csName << " 접속종료됨." << std::endl;
						continue;
					}
					if (iRecvByte == SOCKET_ERROR)
					{
						int iError = WSAGetLastError();
						if (iError != WSAEWOULDBLOCK)
						{
							iter = userlist.erase(iter);
							std::cout << user.m_csName << " 비정상 접속종료됨." << std::endl;
						}
						else
						{
							iter++;
						}
					}
				} while (iRecvSize < recvPacket.ph.len - PACKET_HEADER_SIZE);

				KPacket data;
				data.m_uPacket = recvPacket;
				KChatting recvdata;
				ZeroMemory(&recvdata, sizeof(recvdata));
				data >> recvdata.index >> recvdata.name
					>> recvdata.damage >> recvdata.message;

				std::cout << "\n" <<
					"[" << recvdata.name << "]"
					<< recvdata.message;

				// 패킷 완성		
				std::list<User>::iterator iterSend;
				for (iterSend = userlist.begin();
					iterSend != userlist.end(); )
				{
					User user = *iterSend;
					int iSendMsgSize = SendMsg(user.m_Sock, recvPacket);
					if (iSendMsgSize < 0)
					{
						closesocket(user.m_Sock);
						iterSend = userlist.erase(iterSend);
						std::cout << user.m_csName << " 비정상 접속종료됨." << std::endl;
					}
					else
					{
						iterSend++;
					}
				}
				if (iter != userlist.end())
				{
					iter++;
				}
			}
		}
	}
	closesocket(ListenSock);
	WSACleanup();
}
COMMENT