c++ 소켓은 winsock2로 구현할 수 있는데
자바의 소켓이랑은 많이 달랐음
클라이언트에서 데이터 보내기 및 받기 - Win32 apps | Microsoft Learn
예시 코드로는 ms 설명서에 제대로 나와있기는 한데
일단 winsock.h 과 winsock2.h 두 가지 버전으로 나뉨
전자는 옛날버전이고 후자는 기능 추가 한걸로 알고 있음
winsock.h는 windows.h 헤더에 이미 포함되어 있어
winsock2.h와 windows.h 헤더를 같이 쓰면 재정의 오류가 뜸
이를 해결하려면 # WIN32_LEAN_AND_MEAN 를 추가하여
winsock.h 및 여러 불필요한 헤더들을 windows.h로 부터 제외 가능
이렇게 같이 사용할 수 있음
하지만 간접 헤더 포함될 때도 재정의 오류가 뜸...
그래서 모든 헤더에 #define 하기 그래서 함수 포인터를 씀
거기에 소켓 관련 함수들을 싹다 넣고 호출
서버 코드
#include <winsock2.h> #include <ws2tcpip.h> #include <string> #include <thread> #include <vector> #include <wchar.h> #include <iostream> #pragma comment(lib, "Ws2_32.lib") #define SERVER_PORT 8001 #define BUFFER_SIZE 256 using namespace std; struct ClientData { SOCKET clientSocket = {}; SOCKADDR_IN clientAddr = {}; wchar_t clientName[10] = { L"player" }; bool online; }; vector<ClientData*> clients; void SendToAll(char* typeMsg, wchar_t* chatMsg) { int result = 0; int clsSize = clients.size(); for (int i = 0; i < clsSize; i++) { if (!clients[i]->online) { char type3 = 0x03; wchar_t exitMsg[BUFFER_SIZE] = {}; wcscat_s(exitMsg, clients[i]->clientName); wcscat_s(exitMsg, L" - "); wcscat_s(exitMsg, L"님이 퇴장했습니다.\n"); SendToAll(&type3, exitMsg); delete clients[i]; clients.erase(clients.begin() + i); } } for (int i = 0; i < clsSize; i++) { result = send(clients[i]->clientSocket, typeMsg, sizeof(char), 0); if (result == SOCKET_ERROR) { closesocket(clients[i]->clientSocket); continue; } result = send(clients[i]->clientSocket, (char*)chatMsg, BUFFER_SIZE * sizeof(wchar_t), 0); if (result == SOCKET_ERROR) { closesocket(clients[i]->clientSocket); continue; } } } int main() { int result; // Winsock 초기화 WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { std::cerr << "WSAStartup failed!" << std::endl; return 0; } // 소켓 생성 SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (serverSocket == INVALID_SOCKET) { std::cerr << "Socket creation failed! Error: " << WSAGetLastError() << std::endl; WSACleanup(); return 0; } // 서버 주소 설정 SOCKADDR_IN serverAddr = {}; serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = INADDR_ANY; serverAddr.sin_port = htons(SERVER_PORT); bind(serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)); listen(serverSocket, SOMAXCONN); wcout.imbue(locale("kor")); ////논 블로킹으로 설정 //u_long argp = 1; //ioctlsocket(clientSocket, FIONBIO, &argp); int repeatCount = 0; //새로운 클라이언트를 계속해서 받는 반복문 while (true) { ClientData* newClientData = new ClientData; int clientAddrSize = sizeof(SOCKADDR_IN); newClientData->clientSocket = accept(serverSocket, (SOCKADDR*)&newClientData->clientAddr, &clientAddrSize); if (newClientData->clientSocket == INVALID_SOCKET) { std::cerr << "Accept failed! Error: " << WSAGetLastError() << std::endl; closesocket(serverSocket); WSACleanup(); break; } wchar_t convNum[4] = {}; _itow_s(repeatCount, convNum, _countof(convNum), 10);//카운트를 문자로 wcscat_s(newClientData->clientName, convNum);//클라이언트 이름에 숫자 추가 newClientData->online = true;//연결중임을 의미 clients.push_back(newClientData);//클라이언트 배열에 추가 char type3 = 0x03; wchar_t joinMsg[BUFFER_SIZE] = {}; wcscat_s(joinMsg, newClientData->clientName); wcscat_s(joinMsg, L" - "); wcscat_s(joinMsg, L"님이 접속했습니다.\n"); SendToAll(&type3, joinMsg); wcout << L"New Player Entered : " << newClientData->clientName << endl; //지속적으로 클라이언트로부터 메시지를 받아 다른 클라로 전달해주는 반복문 thread recvThread ( [](ClientData* cls) { while (true) { int tresult; char typeBuffer; wchar_t nameAndBar[BUFFER_SIZE] = {}; wchar_t strBuffer[BUFFER_SIZE] = {}; tresult = recv(cls->clientSocket, &typeBuffer, sizeof(char), 0); if (tresult == SOCKET_ERROR) { closesocket(cls->clientSocket); cerr << WSAGetLastError() << endl; cls->online = false; for (int i = 0; i < clients.size(); i++) { if (!clients[i]->online) { char type3 = 0x03; wchar_t exitMsg[BUFFER_SIZE] = {}; wcscat_s(exitMsg, clients[i]->clientName); wcscat_s(exitMsg, L" - "); wcscat_s(exitMsg, L"님이 퇴장했습니다.\n"); delete clients[i]; clients.erase(clients.begin() + i); SendToAll(&type3, exitMsg); } } break; } tresult = recv(cls->clientSocket, (char*)strBuffer, BUFFER_SIZE * sizeof(wchar_t), 0); if (tresult == SOCKET_ERROR) { closesocket(cls->clientSocket); cerr << WSAGetLastError() << endl; cls->online = false; break; } wcscat_s(nameAndBar, cls->clientName); wcscat_s(nameAndBar, L" - "); wcscat_s(strBuffer, L"\n"); SendToAll(&typeBuffer, nameAndBar); SendToAll(&typeBuffer, strBuffer); } }, newClientData ); recvThread.detach(); repeatCount++;//카운트 증가 } std::cout << "Server ShutDown" << std::endl; // 소켓 닫기 closesocket(serverSocket); WSACleanup(); return 0; }
메시지 받은걸 모든 접속자에게 돌려주는 프로그램
을.. 실행하고, 채팅 주고 받을 수 있는 클라로 접속하면 된다.
소켓 통신에서 데이터를 주고 받을 땐 send와 recv 함수가 있는데
기본적으로 blocking 이라는 데이터가 제대로 전달될 때 까지
기다리는 기능이 있는데 이것 때문에 메인 스레드는 못써먹게 됨
따라서 스레드 새로 생성해서 그쪽에서 돌려야 함
blocking을 없애는 것도 가능하다 //주석처리로 해둠
ioctlsocket 함수(winsock.h) - Win32 apps | Microsoft Learn
시연 이미지
아니라면 포트포워드
클라이언트 소스코드
GitHub - h117562/Tutorial_Socket
서버 프로그램은 data 폴더에 exe로 뒀고 포트는 8001 고정이다
댓글 없음:
댓글 쓰기