🧼C, C++/네트워크

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

Mawile 2020. 12. 24.
728x90

 

개발환경 >> Visual Studio

언어 >> C++17

운영체제 >> Windows10

 


 

안녕하세요

 

저번에 올렸던 다중클라이언트글은 devcpp 기반이라서 비주얼스튜디오를 사용하시는분들은 작동이 안될겁니다

그래서 비주얼 스튜디오에서도 작동되는 코드로 재수정했습니다.

 

비주얼스튜디오 이용자분들에게 도움이되길 바라요~

 

 

[[[   지난글   ]]]

 

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

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

mawile.tistory.com

 


 

[[[   서버   ]]]

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

#pragma comment(lib, "ws2_32.lib")

#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_s(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();
}

[[[   클라이언트   ]]]

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

#include <iostream> //c++io
#include <thread> //thread
#include <atomic> //atomic
#include <winsock2.h> //WSA



using namespace std;
atomic<bool>broken(false); //서버가 닫히면 클라이언트도 그걸감지하고 자동종료하기위한 변수

#pragma comment(lib, "ws2_32.lib")

//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_s(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();
}

 

 

 

 

그럼 이만..!!

728x90

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

소켓 파일전송 C++ #3  (0) 2021.01.20
다중 스트림서버 C++ TCP #1  (0) 2020.12.31
소켓 파일전송 C++ #2  (0) 2020.11.26
소켓 커스텀 송수신함수 구현 C++  (0) 2020.11.26
다중 클라이언트 C++ TCP #3  (1) 2020.11.09

댓글