🧼C, C++/네트워크

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

Mawile 2020. 10. 14.
728x90

시작하기 앞서서 사용한 운영체제는 Windows10이며

IDE는 DevCpp이고 사용한 언어는 C++11입니다!


 

참고자료

 

소켓 기본 틀 #1

[[[  서버 기본 틀  ]]] #include #include using namespace std; #define PACKET_SIZE 1024 SOCKET skt,client_sock; int main(){ WSADATA wsa; WSAStartup(MAKEWORD(2,2), &wsa); skt = socket(PF_INET,SOCK_S..

mawile.tistory.com

 

 

 

 

소켓 기본 틀 #2

[[[  서버  ]]] #include #include using namespace std; #define PACKET_SIZE 1024 int main(){ WSADATA wsa; if(WSAStartup(MAKEWORD(2,2), &wsa)){ cout << "WSA error"; return 0; } SOCKET skt; skt = sock..

mawile.tistory.com

 

 

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

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

mawile.tistory.com


안녕하세요~!

오늘은 TCP멀티 클라이언트에 관한 소켓 강좌를 준비했습니다.

 

TCP멀티 클라이언트란?

원래 TCP는 서버 하나에 클라이언트 하나잖아요?

이거는 서버 하나에 여러 개의 클라이언트를 접속할 수 있게 하는 것입니다.

 

멀티 클라이언트는 쓸게 많기 때문에 계속 미루고 미루다가 이제 쓰네요...ㅋㅋㅋ

UDP강좌도 곧 시작하겠습니다~!

저도 공부할 겸 자세하게 써보도록 하고 할게요

 

이번 강좌 끝나고 소켓과 멀티스레드를 하다 보면 중간에 멀티스레드를 특정 조건 때 종료시키고 싶을 때

스레드를 죽일 수 있는 방법에 대해서 써보려고요!

 

시작합니다......!


일단 저는 "소켓 기본 틀 #2"를 사용했습니다

그냥 개인적으로 연습할 때는 #1로 하고 블로그 소개에서는 #2를 사용하려고요!

[[  서버#1  ]]

#include <iostream>
#include <winsock2.h>
#include <thread> //멀티쓰레드사용
using namespace std;

#define PACKET_SIZE 1024
#define MAX 10 //최대수용가능한 클라이언트수지정

WSADATA wsa; //변수들은 제일위로올려준다
SOCKET skt,client_sock[MAX];
SOCKADDR_IN client[MAX] = {0};
int client_size[MAX];

void accpetclients(){ //클라이언트 연결관리함수(멀티쓰레드)
	for(int i=0;i<MAX;i++){ //for문으로 클라이언트 연결관리!
		client_size[i] = sizeof(client[i]);
		client_sock[i] = accept(skt,(SOCKADDR*)&client[i],&client_size[i]);
		if(client_sock[i]==INVALID_SOCKET){
			cout << "accept error";
			closesocket(client_sock[i]);
			closesocket(skt);
			WSACleanup();
			return;
		}
	}
}

int main(){
	if(WSAStartup(MAKEWORD(2,2), &wsa)){
		cout << "WSA error";
		return 0;
	}

	skt = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(skt==INVALID_SOCKET){
		cout << "socket error";
		closesocket(skt);
		WSACleanup();
		return 0;
	}

	SOCKADDR_IN addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(4444);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);

	if(bind(skt, (SOCKADDR*)&addr,sizeof(addr))){
		cout << "bind error";
		closesocket(skt);
		WSACleanup();
		return 0;
	}
	if(listen(skt,SOMAXCONN)){
		cout << "listen error";
		closesocket(skt);
		WSACleanup();
		return 0;
	}
	
	thread (accpetclients).detach();
    //멀티쓰레드를 사용하여 'accecptclients함수를 호출'
    //.detach(); 메인함수는 더이상 해당쓰레드가 종료될때까지
    //기다려주지않음(완전히 별개의 쓰레드로 분리)
	
	while(1){ //종료되지않도록 반복문
		
	}

	for(int i=0;i<MAX;i++) closesocket(client_sock[i]); //for문으로 종료소켓늘리기
	closesocket(skt);
	WSACleanup();
}

[[  클라이언트#1 ]]

#include <iostream>
#include <winsock2.h>
using namespace std;

#define PACKET_SIZE 1024

int main(){
	WSADATA wsa;
	if(WSAStartup(MAKEWORD(2,2),&wsa)){
		cout << "WSA error";
		WSACleanup();
		return 0;
	}

	SOCKET skt;
	skt = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(skt==INVALID_SOCKET){
		cout << "socket error";
		closesocket(skt);
		WSACleanup();
		return 0;
	}

	SOCKADDR_IN addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(4444);
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	
	while(connect(skt, (SOCKADDR*)&addr,sizeof(addr)));
    
    while(1){ //반복문추가
    	
	}
    
	closesocket(skt);
	WSACleanup();
}

 

 

 

자 이제 기본적으로 멀티 클라이언트 기본 틀이 완성되었습니다

이것도 소켓 기본 틀 시리즈에다가 넣어놓겠습니다~!

이제 본격적으로 색을 입혀줄 차례입니다!

 

 

 

 

[[  서버#2 ]]

#include <iostream>
#include <winsock2.h>
#include <thread>
using namespace std;

#define PACKET_SIZE 1024
#define MAX 10

WSADATA wsa;
SOCKET skt,client_sock[MAX];
SOCKADDR_IN client[MAX] = {0};
int client_size[MAX];

void recvclient(SOCKET &s,int client_num){ //클라이언트 recv함수전용 멀티쓰레드
	
}

void accpetclients(){
	char client_num[10]; //클라이언트정수값을 문자열로 저장하기위한 저장용 변수
	for(int i=0;i<MAX;i++){
		client_size[i] = sizeof(client[i]);
		client_sock[i] = accept(skt,(SOCKADDR*)&client[i],&client_size[i]);
		
		if(client_sock[i]==INVALID_SOCKET){
			cout << "accept error";
			closesocket(client_sock[i]);
			closesocket(skt);
			WSACleanup();
			return;
		}
        
		cout << "Client #" << i << " Joined!"; // 클라이언트 연결감지
		ZeroMemory(client_num,sizeof(client_num)); // 저장용변수 내용초기화
		itoa(i,client_num,10); // i의 정수값을 client_num에다가 10진수로 저장
		send(client_sock[i],client_num,strlen(client_num),0); // 클라이언트번호 전송
		thread (recvclient,ref(client_sock[i]),i).detach(); // 해당클라이언트 쓰레드생성
	}
}

int main(){
	if(WSAStartup(MAKEWORD(2,2), &wsa)){
		cout << "WSA error";
		return 0;
	}

	skt = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(skt==INVALID_SOCKET){
		cout << "socket error";
		closesocket(skt);
		WSACleanup();
		return 0;
	}

	SOCKADDR_IN addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(4444);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);

	if(bind(skt, (SOCKADDR*)&addr,sizeof(addr))){
		cout << "bind error";
		closesocket(skt);
		WSACleanup();
		return 0;
	}
	if(listen(skt,SOMAXCONN)){
		cout << "listen error";
		closesocket(skt);
		WSACleanup();
		return 0;
	}
	
	thread (accpetclients).detach();
	
	while(1){
		
	}

	for(int i=0;i<MAX;i++) closesocket(client_sock[i]);
	closesocket(skt);
	WSACleanup();
}

[[  클라이언트#2 ]]

#include <iostream>
#include <winsock2.h>
using namespace std;

#define PACKET_SIZE 1024

int main(){
	WSADATA wsa;
	if(WSAStartup(MAKEWORD(2,2),&wsa)){
		cout << "WSA error";
		WSACleanup();
		return 0;
	}

	SOCKET skt;
	skt = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(skt==INVALID_SOCKET){
		cout << "socket error";
		closesocket(skt);
		WSACleanup();
		return 0;
	}

	SOCKADDR_IN addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(4444);
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	
	while(connect(skt, (SOCKADDR*)&addr,sizeof(addr)));
	
	char buf[PACKET_SIZE];
	
	recv(skt,buf,PACKET_SIZE,0); //자신이 접속한 클라이언트번호 수신
	int mynum = atoi(buf); //char형에서 int형으로 형변환
	sprintf(buf,"[%d] %s::%d", mynum,inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
    // 콘솔제목지정 >> [내가 접속한 클라이언트번호] [서버아이피]::[서버포트]
    SetConsoleTitle(buf);
    
    while(!WSAGetLastError()){ //서버가 나갈시 자동으로 종료
    	
	}
    
	closesocket(skt);
	WSACleanup();
}

 

 

 

 

저가 define으로 MAX를 10이라고 했는데

만약에 저는 10으로 또는 사용자 정의로 그때그때 다른 사이즈로 정하려면

어떻게 해야 할까요?

다음은 사용자 정의 클라이언트 최대수 용수 정하는 법입니다!

 

 

 

 

[[  서버#3 ]]

#include <iostream>
#include <winsock2.h>
#include <thread>
using namespace std;

#define PACKET_SIZE 1024

WSADATA wsa;
SOCKET skt,*client_sock;
SOCKADDR_IN *client;
int *client_size,MAX; //MAX -> 클라이언트 최대 수용 수에 대한 변수 추가

void recvclient(SOCKET &s,int client_num){
	
	return;
}

void accpetclients(){
	char client_num[10];
	for(int i=0;i<MAX;i++){
		client_size[i] = sizeof(client[i]);
		client_sock[i] = accept(skt,(SOCKADDR*)&client[i],&client_size[i]);
		
		if(client_sock[i]==INVALID_SOCKET){
			cout << "accept error";
			closesocket(client_sock[i]);
			closesocket(skt);
			WSACleanup();
			return;
		}
		
		cout << "Client #" << i << " Joined!";
		ZeroMemory(client_num,sizeof(client_num));
		itoa(i,client_num,10);
		send(client_sock[i],client_num,strlen(client_num),0);
		thread (recvclient,ref(client_sock[i]),i).detach();
	}
	return;
}

void openSocket(int PORT){ //소켓오픈전용 함수!
	if(WSAStartup(MAKEWORD(2,2), &wsa)){
		cout << "WSA error";
		return;
	}

	skt = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(skt==INVALID_SOCKET){
		cout << "socket error";
		closesocket(skt);
		WSACleanup();
		return;
	}

	SOCKADDR_IN addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(PORT);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);

	if(bind(skt, (SOCKADDR*)&addr,sizeof(addr))){
		cout << "bind error";
		closesocket(skt);
		WSACleanup();
		return;
	}
	if(listen(skt,SOMAXCONN)){
		cout << "listen error";
		closesocket(skt);
		WSACleanup();
		return;
	}
	
	thread (accpetclients).detach();
	
	while(1){
		
	}

	for(int i=0;i<MAX;i++) closesocket(client_sock[i]);
	closesocket(skt);
	WSACleanup();
	return;
}

int main(){
	int PORT;
	cout << "포트설정 >> ";
	cin >> PORT; //포트 설정
	cout << "클라이언트 최대수용 수 설정 >> ";
	cin >> MAX; //최대 수용 수 설정
	
	client_sock = new SOCKET[MAX]; //동적할당
	client = new SOCKADDR_IN[MAX];
	client_size  = new int[MAX];
	
	for(int i=0;i<MAX;i++){
		ZeroMemory(&client_sock,sizeof(client_sock)); //메모리비우기
		ZeroMemory(&client,sizeof(client));
		ZeroMemory(client_size,sizeof(client_size));
	}
	
	openSocket(PORT); //소켓열기
	
	delete[] client_sock,client,client_size; //동정할당 해제
	return 0;
}

[[  클라이언트#3 ]]

#include <iostream>
#include <winsock2.h>
using namespace std;

#define PACKET_SIZE 1024

void openSocket(char IP[],int PORT){ //IP주소,포트번호 전달!
	WSADATA wsa;
	if(WSAStartup(MAKEWORD(2,2),&wsa)){
		cout << "WSA error";
		WSACleanup();
		return;
	}

	SOCKET skt;
	skt = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(skt==INVALID_SOCKET){
		cout << "socket error";
		closesocket(skt);
		WSACleanup();
		return;
	}

	SOCKADDR_IN addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(PORT); //포트번호
	addr.sin_addr.s_addr = inet_addr(IP); // IP주소
	
	while(connect(skt, (SOCKADDR*)&addr,sizeof(addr)));
	
	char buf[PACKET_SIZE];
	
	recv(skt,buf,PACKET_SIZE,0);
	int mynum = atoi(buf);
	sprintf(buf,"[%d] %s::%d", mynum,inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
    SetConsoleTitle(buf);
    
    while(!WSAGetLastError()){
    	
	}
    
	closesocket(skt);
	WSACleanup();
}

int main(){
	char IP[100];
	int PORT;
	cout << "아이피주소 설정 >> ";
	cin >> IP; // 아이피 주소
	cout << "포트 설정 >> ";
	cin >> PORT; // 포트 번호
	
	openSocket(IP,PORT); //소켓 오픈!
	return 0;
}

 

 

 

 

그러면 어느 정도 완성되었습니다

그러면.. 마지막으로 제일 중요한 것..!

데이터 송수신이겠죠??

recv와 send를 이용해서 서버와 MAX개의 클라이언트가

서로 데이터를 전달받을 수 있도록 하겠습니다!

이 부분은 좀 어려워하실 수 있으니까 잘 봐주세요!

 

 

 

 

 

[[  서버#END ]]

#include <iostream>
#include <winsock2.h>
#include <thread>
using namespace std;

#define PACKET_SIZE 1024

WSADATA wsa;
SOCKET skt,*client_sock;
SOCKADDR_IN *client;
int *client_size,MAX;

void recv_data(SOCKET &s,int client_num){
	char buf[PACKET_SIZE];
	
	while(1){
		ZeroMemory(buf,PACKET_SIZE);
		if(recv(s,buf,PACKET_SIZE,0)==-1) break; //클라이언트 종료 감지
		
		cout << "\nClient #" << client_num << " << " << buf << "\n보낼 데이터를 입력 >> ";
	}
	
	return;
}

void accpetclients(){
	char client_num[10];
	for(int i=0;i<MAX;i++){
		client_size[i] = sizeof(client[i]);
		client_sock[i] = accept(skt,(SOCKADDR*)&client[i],&client_size[i]);
		
		if(client_sock[i]==INVALID_SOCKET){
			cout << "accept error";
			closesocket(client_sock[i]);
			closesocket(skt);
			WSACleanup();
			return;
		}
		
		cout << "Client #" << i << " Joined!" << "\n보낼 데이터를 입력 >> ";
		ZeroMemory(client_num,sizeof(client_num));
		itoa(i,client_num,10);
		send(client_sock[i],client_num,strlen(client_num),0);
		thread (recv_data,ref(client_sock[i]),i).detach();
	}
	return;
}

void openSocket(int PORT){
	if(WSAStartup(MAKEWORD(2,2), &wsa)){
		cout << "WSA error";
		return;
	}

	skt = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(skt==INVALID_SOCKET){
		cout << "socket error";
		closesocket(skt);
		WSACleanup();
		return;
	}

	SOCKADDR_IN addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(PORT);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);

	if(bind(skt, (SOCKADDR*)&addr,sizeof(addr))){
		cout << "bind error";
		closesocket(skt);
		WSACleanup();
		return;
	}
	if(listen(skt,SOMAXCONN)){
		cout << "listen error";
		closesocket(skt);
		WSACleanup();
		return;
	}
	
	thread (accpetclients).detach();
	
	char msg[PACKET_SIZE],sendnum[PACKET_SIZE]; 
	
	while(1){
		cout << "보낼 데이터를 입력 >> ";
		cin >> msg; //데이터내용
		
		if(!strcmp(msg,"exit")) break; // 데이터의내용이 "exit"일시 소켓종료
		
		cout << "대상 클라이언트를 입력(all:모두) >> ";
		cin >> sendnum; //대상클라이언트번호지정 "all"일시 모든 클라이언트에게전송
		
		if(!strcmp(sendnum,"all")) // 변수sendnum의 내용이 "all" 이라면 모두에게 메세지전송
			for (int i=0;i<MAX;i++)
				send(client_sock[i],msg,strlen(msg),0);
		else send(client_sock[atoi(sendnum)],msg,strlen(msg),0); //아니라면 한명에게만 전송
	}

	for(int i=0;i<MAX;i++) closesocket(client_sock[i]);
	closesocket(skt);
	WSACleanup();
	return;
}

int main(){
	int PORT;
	cout << "포트설정 >> ";
	cin >> PORT;
	cout << "클라이언트 최대수용 수 설정 >> ";
	cin >> MAX;
	
	client_sock = new SOCKET[MAX];
	client = new SOCKADDR_IN[MAX];
	client_size  = new int[MAX];
	
	ZeroMemory(client_sock,sizeof(client_sock));
	ZeroMemory(client,sizeof(client));
	ZeroMemory(client_size,sizeof(client_size));
	
	openSocket(PORT);
	
	delete[] client_sock,client,client_size;
	return 0;
}

[[  클라이언트#END ]]

#include <iostream>
#include <winsock2.h>
#include <thread>
using namespace std;

#define PACKET_SIZE 1024

void recv_data(SOCKET &s){
	char buf[PACKET_SIZE];
	
	while(1){
		ZeroMemory(buf,PACKET_SIZE);
		recv(s,buf,PACKET_SIZE,0);
		
		if(WSAGetLastError()) break; //서버종료 감지
		
		cout << "\n[Server] >> " << buf << "\n보낼 데이터를 입력 >> "; 
	}
	
	return;
}

void openSocket(char IP[],int PORT){
	WSADATA wsa;
	if(WSAStartup(MAKEWORD(2,2),&wsa)){
		cout << "WSA error";
		WSACleanup();
		return;
	}

	SOCKET skt;
	skt = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(skt==INVALID_SOCKET){
		cout << "socket error";
		closesocket(skt);
		WSACleanup();
		return;
	}

	SOCKADDR_IN addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(PORT);
	addr.sin_addr.s_addr = inet_addr(IP);
	
	while(connect(skt, (SOCKADDR*)&addr,sizeof(addr)));
	
	char buf[PACKET_SIZE];
	
	recv(skt,buf,PACKET_SIZE,0);
	int mynum = atoi(buf);
	sprintf(buf,"[%d] %s::%d", mynum,inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
    SetConsoleTitle(buf);
    
    thread (recv_data,ref(skt)).detach();
    
    while(!WSAGetLastError()){
    	cout << "보낼 데이터를 입력 >> ";
		cin >> buf;
		
    	send(skt,buf,strlen(buf),0); // 데이터전송
	}
    
	closesocket(skt);
	WSACleanup();
}

int main(){
	char IP[100];
	int PORT;
	cout << "아이피주소 설정 >> ";
	cin >> IP;
	cout << "포트 설정 >> ";
	cin >> PORT;
	
	openSocket(IP,PORT);
	return 0;
}

[[  시연 영상  ]]

 


끝났다~~~~~!!!!!!!!!!!!!!!!!!!!!!

완성본 파일은 밑에 깃허브링크로 올려놓겠습니다

 

궁금한 부분이 있다면 댓글 달아주세요!

그럼 긴 글 봐주셔서 감사합니다!


[[  다운로드 ]]

 

DRAGONPROCESS/Multiclients

Contribute to DRAGONPROCESS/Multiclients development by creating an account on GitHub.

github.com


[[  다음글  ]]

 

 

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

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

mawile.tistory.com

 

 

728x90

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

소켓 버퍼 비우기 C++  (0) 2020.10.17
소켓 구조체 정보 전송 C++ TCP  (4) 2020.10.16
소켓 기본 틀 #2  (1) 2020.10.13
소켓 파일전송 C++ #1  (0) 2020.10.06
소켓 기본 틀 #1  (0) 2020.10.05

댓글