🧼C, C++/네트워크
다중 클라이언트 C++ TCP #1
시작하기 앞서서 사용한 운영체제는 Windows10이며
IDE는 DevCpp이고 사용한 언어는 C++11입니다!
참고자료
안녕하세요~!
오늘은 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;
}
[[ 시연 영상 ]]
끝났다~~~~~!!!!!!!!!!!!!!!!!!!!!!
완성본 파일은 밑에 깃허브링크로 올려놓겠습니다
궁금한 부분이 있다면 댓글 달아주세요!
그럼 긴 글 봐주셔서 감사합니다!
[[ 다운로드 ]]
[[ 다음글 ]]
'🧼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 |
댓글