프로세스(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();
}
'STUDY > Network Programming' 카테고리의 다른 글
Network Programming - 윈도우 메세지 통해 소켓 입출력, WSAAsyncSelect 모델 (11) | 2022.01.10 |
---|---|
Network Programming - 다중 입출력 함수 Select() (0) | 2022.01.07 |
Network Programming - 소켓모드의 Blocking & NonBlocking (0) | 2022.01.06 |
Network Programming - 1:N 채팅/통신 채팅 프로그램 구현 :: TCP 서버 구현(소스코드) (0) | 2022.01.03 |
Network Programming - TCP/UDP 프로토콜 장단점, WinSock 윈속 소켓 (0) | 2021.12.31 |