🧼C, C++/네트워크

다중 클라이언트 C++ TCP #3

Mawile 2020. 11. 9.
728x90

 

개발환경 >> DevCpp

언어 >> C++11

운영체제 >> Windows10

 

 


 

[[[   지난 글   ]]]

 

다중 클라이언트 C++ TCP #2

개발환경 >> DevCpp 언어 >> C++11 운영체제 >> Windows10 [[[  지난 글  ]]] 다중 클라이언트 C++ TCP #1 시작하기 앞서서 사용한 운영체제는 Windows10이며 IDE는 DevCpp이고 사용한 언어는 C++11입니다! 참..

mawile.tistory.com

 

 


 

[[[   비주얼스튜디오 이신분들은 이곳으로!!   ]]]

 

다중 클라이언트 C++ TCP #3 (수정본)

개발환경 >> Visual Studio 언어 >> C++17 운영체제 >> Windows10 안녕하세요 저번에 올렸던 다중클라이언트글은 devcpp 기반이라서 비주얼스튜디오를 사용하시는분들은 작동이 안될겁니다 그래서 비주얼

mawile.tistory.com

 


[[[   참고자료   ]]]

 

소켓 구조체 정보 전송 C++

시작하기 앞서서 사용한 운영체제는 Windows10이며 IDE는 DevCpp이고 사용한 언어는 C++11입니다! 안녕하세요!! 이번에는 간단명료하게 원리 설명과 소스코드 뿌리고 빠지겠습니다~!!(ㅋㅋㅋㅋ) 궁금

mawile.tistory.com

 

 

자료구조 만들기 C++ #1

개발환경 >> DevCpp 언어 >> C++11 운영체제 >> Windows10 안녕하세요~!~!~!! 이번에는 자료구조를 만들어볼 건데 생각 없이 만들다가 그냥 완성한 거긴 하지만,,.. 일단 방식은 LIFO (Last In First Out)입니다~..

mawile.tistory.com

 

 

멀티쓰레딩 <c++ thread=""></c++>

시작하기 앞서서 사용한 운영체제는 Windows10이며 IDE는 DevCpp이고 사용한언어는 C++11입니다! 안녕하세요! 멀티쓰레딩관련 첫 번째 글이네요! 먼저 멀 티쓰 레딩 시리즈는 thread->atomic->mutex->chrono->p_t

mawile.tistory.com

 


 

 

안녕하세요.....!!!

이번 다중 클라이언트 강좌는 지난 글에 비해서 난이도가 많이 높아졌으니까

집중해주세요!!!

 

그리고 저가 위에 올려놓은 지난 글과 참고자료를 모두 보고 오시는 것을 추천드립니다!!

이번 강좌에서 핵심적으로 쓰인 것들입니다~~

 

이번 글을 요약하자면 기존의 통신 형식(서버 <-> 클라이언트)이 아닌

클라이언트 간의 통신 형식(클라이언트 <-> 클라이언트)이 되겠네요~

한마디로 클라이언트 간의 데이터를 주고받는 방법입니다!!!

 

이번 강좌는 저번 강좌에 비해서 난이도가 상승해서 많이 어려울 수도 있으니 집중 부탁드립니다!!

 

또, 이번 강좌는 처음 보시는 분들이 많을 것 같아서

저가 그린 그림으로 메커니즘 및 원리설명부터 하고

코드 설명을 시작하겠습니다~!

 

그리고 시작하기 전에 저가 강좌를 안 올린 atomic이라는 라이브러리가 쓰였는데 이것도 곧 올릴 겁니다!

 

그럼 시작합니다....!

 

 

 


 

[[[   원리 설명   ]]]

서버
클라이언트

 

 

 

그림 ㅋㅋㅋ 죄송합니다

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

 

 


 

[[[   소스코드(서버)   ]]]

#include <iostream> //c++io
#include <thread> //thread
#include <winsock2.h>  //WSA
using namespace std;

#define PACKET_SIZE 1024 //패킷크기 통일

//벡터대용으로 최적화목적의 직접만든 커스텀벡터(LIFO)
template <typename T> class dpVec{
private:
	int size,maxsize;
	T *data=NULL;
public:
	dpVec() : size(0) , maxsize(1) , data(0) { }
	~dpVec(){ size=0,maxsize=1; delete[] data; }
	void push(T t){
		if(size+1>=maxsize){
			maxsize *=2;
			T *tmp = new T[maxsize];
			for(int i=0;i<size;i++) tmp[i]=data[i];
			tmp[size++]=t;
			delete[] data;
			data=tmp;
		}
		else data[size++]=t;
	}
	int length()const{ return this->size; }
	T& operator[](const int index){ return data[index]; }
};

//클라이언트의 메세지정보를 담을 구조체
typedef struct{
	int mNum; //보낸 클라이언트의 번호
	int hNum; //받을 클라이언트의 번호
	char hMessage[PACKET_SIZE]; //메세지내용
}PacketInfo;

//클라이언트하나를 캡슐화한 클래스
class dpSock{
	public:
		SOCKET client;
		SOCKADDR_IN client_info={0};
		int client_size = sizeof(client_info);
		int number;
		dpSock(){ //생성자
			client={0};
			client_info={0};
			client_size = sizeof(client_info);
		}
		~dpSock(){ //소멸자
			client={0};
			client_info={0};
			client_size=-1;
			number=-1;
		}
}; dpVec<dpSock>s; dpVec<PacketInfo>pkInfo; //클라이언트소켓리스트,클라이언트패킷리스트

void recvData(SOCKET ls,int num){ //클라이언트와 서버의 송수신함수
	char buf[PACKET_SIZE];
	cout << num << ".[입장]" << endl;
	while(1){
		recv(ls,buf,PACKET_SIZE,0);
		memcpy((char*)&pkInfo[num],buf,PACKET_SIZE); //struct<-char*값복사
		if(WSAGetLastError()){
			cout << num << ".[퇴장]" << endl;
			return;
		}
		if(pkInfo[num].hNum!=-1&&pkInfo[num].hNum<=s.length()-2){ //대상존재o일반채팅
			cout << "[" << num << "] -> [" << pkInfo[num].hNum << "] : "<< pkInfo[num].hMessage << endl;
			send(s[pkInfo[num].hNum].client,buf,PACKET_SIZE,0);
		}
		else if(pkInfo[num].hNum!=-1&&pkInfo[num].hNum>s.length()-2){ //대상존재x일반채팅
			cout << "[" << num << "] -> [X] : "<< pkInfo[num].hMessage << endl;
			send(s[pkInfo[num].hNum].client,buf,PACKET_SIZE,0);
		}
		else { //전체채팅
			cout << "[" << num << "] -> [ALL] : "<< pkInfo[num].hMessage << endl;
			for(int i=0;i<s.length();i++) send(s[i].client,buf,PACKET_SIZE,0);
		}
	}
}

void acceptClients(SOCKET &server){ //클라이언트들의 접속을 수용하는 함수
	int number=0; //몇번째클라이언트인지 체크할 변수
	char dsf[100]={0}; //클라이언트에게 해당클라이언트의 번호를 전달할 변수
	while(1){
		s.push(dpSock()); //클라이언트소켓 생성
		pkInfo.push(PacketInfo()); //클라이언트패킷정보 생성
		s[number].client = accept(server,(SOCKADDR*)&s[number].client_info,&s[number].client_size);
        s[number].number = number;
        itoa(number,dsf,10); //int->char*
        send(s[number].client,dsf,strlen(dsf),0); //클라이언트에게 해당클라이언트의 번호전달
        thread (recvData,s[number].client,number).detach();
		number++;
	}
}

int main(){
	WSADATA wsa;
	WSAStartup(MAKEWORD(2,2),&wsa);
	
	SOCKET server = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
	
	SOCKADDR_IN addr={0};
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	addr.sin_port = htons(8080);
	addr.sin_family = AF_INET;
	
	bind(server,(SOCKADDR*)&addr,sizeof(addr));
	listen(server,SOMAXCONN);
	
	thread (acceptClients,ref(server)).detach();
	
	while(1); //메인함수가 닫히는걸 방지
	
	for(int i=0;i<s.length();i++) closesocket(s[i].client);
	closesocket(server);
	WSACleanup();
}

 

 

 

[[[   소스코드(클라이언트)   ]]]

#include <iostream> //c++io
#include <thread> //thread
#include <atomic> //atomic
#include <winsock2.h> //WSA
#define PACKET_SIZE 1024 //패킷크기 통일
using namespace std;
atomic<bool>broken(false); //서버가 닫히면 클라이언트도 그걸감지하고 자동종료하기위한 변수

//sendInfo,recvInfo나눠눈 이유 : 만약 같을경우 받은정보와
//보낸정보가 동시에일어날경우 데이터가 혼합된다
typedef struct{ 
	int mNum;
	int hNum;
	char hMessage[PACKET_SIZE];
}PacketInfo; PacketInfo sendInfo,recvInfo;

void rec(SOCKET &server){ //데이터받는함수
	char buf[PACKET_SIZE]={0};
	while(1){
		recv(server,buf,PACKET_SIZE,0);
		memcpy((char*)&recvInfo,buf,PACKET_SIZE); //char*->struct값대입
		if(WSAGetLastError()) { //서버종료감지
			broken.store(true,memory_order_release);
            //종료될경우 broken의내용을 memory_order_release방식으로 true전환
            //memory_order_relaxed를 사용해도되지만 데이터반환이 이것보다 불안정함
			return;
		}
		if(recvInfo.hNum!=-1) cout << "[" << recvInfo.mNum << "]: " << recvInfo.hMessage << endl;
        //일반채팅
		else cout << "[All]: " << recvInfo.hMessage << endl; //전체채팅
	}
}

void sendMSG(SOCKET &server){ //메세지전송 함수
	//hNum == -1 => 모든클라이언트들에게 데이터전송 (-1입력시 전체클라이언트에게 발송)
	char hMessage[PACKET_SIZE]={0};
	char hNum[100]={0};
	while(1){
		cin >> hMessage >> hNum;
        //입력방법 ex) helloworld 3  (3번클라이언트에게 helloworld발송)
		if(atoi(hNum)>=-1){
			strcpy(sendInfo.hMessage,hMessage);
            //'='연산자 왼쪽에있는 char[]자료형은 대입이안됨
            //따라서 strcpy를 이용해 복사함
			sendInfo.hNum=atoi(hNum);
            //굳이 hNum을 int가아닌 char[]로하는이유는
            //만약 d1 d2 da같은 문자가 나갈경우 서버와 클라이언트모두 에러발생
            //안정성을 위해 d1일시 1,d2일시 2가나가고 da일시 0이나가도록함
			send(server,(char*)&sendInfo,PACKET_SIZE,0);
		}
	}
}

int main(){
	WSADATA wsa;
	if(WSAStartup(MAKEWORD(2,2),&wsa)!=0) return -1;
	
	SOCKET server = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(server == INVALID_SOCKET) return -1;
	
	SOCKADDR_IN addr={0};
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	addr.sin_family = AF_INET;
	addr.sin_port = htons(8080);
	
	while(connect(server,(SOCKADDR*)&addr,sizeof(addr)));
	
    //서버에서 자신의 클라이언트번호를 받는부분입니다
    //sendInfo에만 넣는이유는 어차피 쓰다보면 recvInfo에도 알아서 따라붙습니다
	char mNumc[100]={0};
	recv(server,mNumc,100,0);
	sendInfo.mNum=atoi(mNumc);
	
	thread (rec,ref(server)).detach();
	thread (sendMSG,ref(server)).detach();
	
	while(!broken.load(memory_order_acquire));
    //이것또한 위처럼 memory_order_relaxed를 사용해도되지만
    //메모리상의 데이터에 혼동이 생길수있음
	
	closesocket(server);
	WSACleanup();
}

 

 

 


 

[[[   시연 영상   ]]]

 

 

 

 

 

 

동영상 중간이 뭔가 이상하게 잘렸네요...ㅠㅠ

마지막 부분은 뭘 보여 주려고 했었냐면

2번이 없을 때는 X, 들어오면 정상적으로 서버에서 내용을

확인할 수 있다는 것을 보여주려고 했어요...ㅠㅜ

또, 서버를 닫으면 클라이언트들이 자동으로 다 닫히는 걸 보여주려고 했는데....ㅠㅠ

 

 

 


 

 

 

모르는 부분이나 이해가 가지 않는 부분이 있다면 댓글로 질문을 주세요!!

다음에는 더욱 흥미진진한 글로 찾아뵙겠습니다~~

 

 

 

감사합니다!!

 

 

728x90

'🧼C, C++ > 네트워크' 카테고리의 다른 글

소켓 파일전송 C++ #2  (0) 2020.11.26
소켓 커스텀 송수신함수 구현 C++  (0) 2020.11.26
다중 클라이언트 C++ TCP #2  (2) 2020.11.05
소켓 버퍼 비우기 C++  (0) 2020.10.17
소켓 구조체 정보 전송 C++ TCP  (4) 2020.10.16

댓글