🧼C, C++/네트워크
다중 클라이언트 C++ TCP #3 (수정본)
개발환경 >> Visual Studio
언어 >> C++17
운영체제 >> Windows10
안녕하세요
저번에 올렸던 다중클라이언트글은 devcpp 기반이라서 비주얼스튜디오를 사용하시는분들은 작동이 안될겁니다
그래서 비주얼 스튜디오에서도 작동되는 코드로 재수정했습니다.
비주얼스튜디오 이용자분들에게 도움이되길 바라요~
[[[ 지난글 ]]]
[[[ 서버 ]]]
#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();
}
그럼 이만..!!
'🧼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 |
댓글