-
[IOCP] 채팅 서버 및 테스트 클라이언트 예제C_C++ 프로그래밍/IOCP 2019. 7. 10. 16:04
IOCP 채팅 예제
IOCP에 대해서 의도치 않은 삽질을 하다보니
어느 정도 소스가 읽히고
(영어 실력도 늘어버린 것 같다..)그러면서 클라이언트도 IOCP 방식으로 구현을 하려고 했으나실패하였다.
결국에 스레드 2개를 호출하는 방식으로 바꾸게 되었고
구글링을 하여 적당한 소스를 긁어 수정하여 구현하였다.
소스를 확인하면 알 수 있겠지만
- mutex sock;
- mutex push_lock;
- mutex erase_lock;
총 3개의 뮤텍스를 설정하여
클라이언트가 무작위로 방문하더라도 생기는 데이터 공유의 문제를 해결하였다.
아직 부족한 부분이 많기 때문에 좀 더 수정해야 하는 소스이지만
정상적으로 작동은 하기 때문에 올립니다.
IOCP_Server.cpp
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234#include <iostream>#include <stdlib.h>#include <process.h>#include <winsock2.h>#include <windows.h>#include <vector>#include <Ws2tcpip.h> //inet_pton#include <mutex>// vs warning and winsock error#pragma comment(lib, "ws2_32.lib")#pragma warning (disable : 4996)std::mutex push_lock;std::mutex erase_lock;std::mutex sock_lock;constexpr int PORT_NUM = 3800;constexpr int BUF_SIZE = 1024;constexpr int READ = 3;constexpr int WRITE = 5;constexpr int CLIENT_SIZE = 3000;int login_count = 0;int logout_count = 0;typedef struct // socket info{SOCKET hClntSock;SOCKADDR_IN clntAdr;CHAR name[20];CHAR ip[22];} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;typedef struct // buffer info{OVERLAPPED overlapped;WSABUF wsaBuf;char buffer[BUF_SIZE];INT rwMode; // READ or WRITE} PER_IO_DATA, *LPPER_IO_DATA;// 클라이언트 통신을 위한 vector containerstd::vector<LPPER_HANDLE_DATA> UserList;void ErrorHandling(const char *message);unsigned __stdcall ThreadMain(void * CompletionPortIO);int main(int argc, char* argv[]){WSADATA wsaData;HANDLE hComPort;SYSTEM_INFO sysInfo;LPPER_IO_DATA ioInfo;LPPER_HANDLE_DATA handleInfo;SOCKET hServSock;SOCKADDR_IN servAdr;DWORD recvBytes, flags = 0;INT i;// winsock startif (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)ErrorHandling("WSAStartup Error");hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);GetSystemInfo(&sysInfo);// main thread와 연결된 thread 생성for (i = 0; i < sysInfo.dwNumberOfProcessors; i++)_beginthreadex(NULL, 0, ThreadMain, (LPVOID)hComPort, 0, NULL);// socket 설정hServSock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);memset(&servAdr, 0, sizeof(servAdr));servAdr.sin_family = AF_INET;servAdr.sin_addr.s_addr = htonl(INADDR_ANY);servAdr.sin_port = htons(PORT_NUM);// bind and listen qbind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr));listen(hServSock, CLIENT_SIZE);while (1){sock_lock.lock();SOCKET hClntSock;SOCKADDR_IN clntAdr;int addrLen = sizeof(clntAdr);hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &addrLen);sock_lock.unlock();handleInfo = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA)); // LPPER_HANDLE_DATA 초기화inet_ntop(AF_INET, &clntAdr.sin_addr, handleInfo->ip, INET_ADDRSTRLEN); // get new client iphandleInfo->hClntSock = hClntSock; // 클라이언트의 정보를 구조체에 담아 놓는다.std::cout << ++login_count << '\n';printf("New Client Access : %s\n", handleInfo->ip);memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen);// 소켓 입출력 포트에 accept을 통해서 return 된 클라이언트 정보를 묶는다.CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (DWORD)handleInfo, 0);push_lock.lock();// 클라이언트 user data 초기화UserList.push_back(handleInfo);push_lock.unlock();// 클라이언트가 가지게 될 data 초기화ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));memset(ioInfo->buffer, 0x00, BUF_SIZE);ioInfo->wsaBuf.len = BUF_SIZE;ioInfo->wsaBuf.buf = ioInfo->buffer;ioInfo->rwMode = READ;// name 받기recv(handleInfo->hClntSock, handleInfo->name, 20, 0);// 비동기 입출력 시작WSARecv(handleInfo->hClntSock, &(ioInfo->wsaBuf), 1, &recvBytes, &flags, &(ioInfo->overlapped), NULL);}return 0;}unsigned __stdcall ThreadMain(void * pComPort){HANDLE hComPort = (HANDLE)pComPort;SOCKET sock;DWORD bytesTrans;LPPER_HANDLE_DATA handleInfo;LPPER_IO_DATA ioInfo;int flags = 0;CHAR message[BUF_SIZE];while (1){// 비동기 입출력 완료를 기다린다.GetQueuedCompletionStatus(hComPort, &bytesTrans, (LPDWORD)&handleInfo, (LPOVERLAPPED*)&ioInfo, INFINITE);// 클라이언트의 socket을 가져온다.sock = handleInfo->hClntSock;// 첫 시작은 읽기 상태if (ioInfo->rwMode == READ){puts("\nmessage received!");if (bytesTrans == 0){erase_lock.lock();std::vector<LPPER_HANDLE_DATA>::iterator iter;for (iter = UserList.begin(); iter != UserList.end(); ++iter) {if ((*iter)->hClntSock == sock) {std::cout << "Logout >> "<< "IP :"<<(*iter)->ip << ", "<< "Sock :" <<(*iter)->hClntSock << ", "<< "name :" <<(*iter)->name << '\n';UserList.erase(iter);std::cout << ++logout_count << '\n';break;}}closesocket(sock);free(handleInfo);free(ioInfo);erase_lock.unlock();continue;}memcpy(message, ioInfo->wsaBuf.buf, BUF_SIZE);message[bytesTrans] = '\0'; // 문자열의 끝에 \0을 추가한다 (쓰레기 버퍼 방지)/* name 나누기char *ptr = strtok(message, "]"); // [] => ']'기준으로 나눈다.strcpy_s(handleInfo->name, ptr + 1); // ]으로 나눈 nameptr = strtok(NULL, "]"); // ]으로 다시 나눈 messagestrcpy_s(message, ptr); // message와 name이 나누어진다.*/printf("name : %s\n", handleInfo->name);printf("Sock[%d] : %s\n", sock, message);// 클라이언트가 가진 데이터 구조체의 정보를 바꾼다.// 이젠 서버가 쓰기를 행함std::vector<LPPER_HANDLE_DATA>::iterator iter;free(ioInfo);for (iter = UserList.begin(); iter != UserList.end(); ++iter) {ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));memset(&(ioInfo->overlapped), 0x00, sizeof(OVERLAPPED));int len = strlen(message);ioInfo->wsaBuf.len = len;strcpy_s(ioInfo->buffer, message);ioInfo->wsaBuf.buf = ioInfo->buffer;ioInfo->rwMode = WRITE;if (bytesTrans == 0){closesocket(sock);free(handleInfo);free(ioInfo);continue;}if (WSASend((*iter)->hClntSock, &(ioInfo->wsaBuf), 1, &bytesTrans, 0, &(ioInfo->overlapped), NULL) == SOCKET_ERROR){if (WSAGetLastError() != WSA_IO_PENDING)ErrorHandling("WSASend() error");}}// 데이터 구조체 초기화, 쓰기 -> 읽기 모드로 변경ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));memset(&(ioInfo->overlapped), 0x00, sizeof(OVERLAPPED));ioInfo->wsaBuf.len = BUF_SIZE;ioInfo->wsaBuf.buf = ioInfo->buffer;ioInfo->rwMode = READ;// 읽기 시작if (WSARecv(sock, &(ioInfo->wsaBuf), 1, &bytesTrans, (LPDWORD)&flags, &(ioInfo->overlapped), NULL) == SOCKET_ERROR){if (WSAGetLastError() != WSA_IO_PENDING)ErrorHandling("WSASend() error");}}// 쓰기 상태else {printf("Message Sent!\n");free(ioInfo);}}return 0;}void ErrorHandling(const char *message){perror(message);exit(1);}cs IOCP_Client.cpp
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798#include <iostream>#include <stdlib.h>#include <string.h>#include <Windows.h>#include <process.h>// vs warning and winsock error#pragma comment(lib, "ws2_32.lib")#pragma warning (disable : 4996)#define BUF_SIZE 1024#define NAME_SIZE 20unsigned WINAPI SendMsg(void* arg);//쓰레드 전송함수unsigned WINAPI RecvMsg(void* arg);//쓰레드 수신함수void ErrorHandling(const char* msg);char name[NAME_SIZE] = "[DEFAULT]";char msg[BUF_SIZE];int main(int argc, char* argv[]) {WSADATA wsaData;SOCKET sock;SOCKADDR_IN serverAddr;HANDLE sendThread, recvThread;if (argc != 4) {printf("Usage : %s <IP> <port> <name>\n", argv[0]);exit(1);}std::cout << "connect........\n";if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)// 윈도우 소켓을 사용한다고 운영체제에 알림ErrorHandling("WSAStartup() error!");sprintf(name, "%s", argv[3]);sock = socket(PF_INET, SOCK_STREAM, 0);//소켓을 하나 생성한다.memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = inet_addr(argv[1]);serverAddr.sin_port = htons(atoi(argv[2]));if (connect(sock, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)//서버에 접속한다.ErrorHandling("connect() error");// 접속에 성공하면 이 줄 아래가 실행된다.std::cout << "Connect Success!\n";std::cout << "Sending client's name\n";send(sock, name, sizeof(name), 0);std::cout << "Success!\n";sendThread = (HANDLE)_beginthreadex(NULL, 0, SendMsg, (void*)&sock, 0, NULL);//메시지 전송용 쓰레드가 실행된다.recvThread = (HANDLE)_beginthreadex(NULL, 0, RecvMsg, (void*)&sock, 0, NULL);//메시지 수신용 쓰레드가 실행된다.WaitForSingleObject(sendThread, INFINITE);//전송용 쓰레드가 중지될때까지 기다린다./WaitForSingleObject(recvThread, INFINITE);//수신용 쓰레드가 중지될때까지 기다린다.//클라이언트가 종료를 시도한다면 이줄 아래가 실행된다.closesocket(sock);//소켓을 종료한다.WSACleanup();//윈도우 소켓 사용중지를 운영체제에 알린다.std::cout << "Client exit\n";return 0;}unsigned WINAPI SendMsg(void* arg) {//전송용 쓰레드함수SOCKET sock = *((SOCKET*)arg);//서버용 소켓을 전달한다.char msg[BUF_SIZE];while (1) {//반복fgets(msg, BUF_SIZE, stdin);//입력을 받는다.if (!strcmp(msg, "q\n") || !strcmp(msg, "Q\n")) {//q를 입력하면 종료한다.closesocket(sock);send(sock, "", 0, 0);exit(0);}send(sock, msg, strlen(msg), 0);//nameMsg를 서버에게 전송한다.}return 0;}unsigned WINAPI RecvMsg(void* arg) {SOCKET sock = *((SOCKET*)arg);//서버용 소켓을 전달한다.char msg[NAME_SIZE + BUF_SIZE];int strLen;while (1) {//반복strLen = recv(sock, msg, NAME_SIZE + BUF_SIZE - 1, 0);//서버로부터 메시지를 수신한다.if (strLen == -1)return -1;msg[strLen] = 0;//문자열의 끝을 알리기 위해 설정std::cout << ">>" << msg << '\n';}return 0;}void ErrorHandling(const char* msg) {fputs(msg, stderr);fputc('\n', stderr);exit(1);}cs 테스트 결과
마무리
삽질도 나름 공부인 것 같다.
※ 본 글은 개인 포트폴리오 혹은 공부용으로 사용하기 때문에, 무단 복사 유포는 금지하지만, 개인 공부 용도로는 얼마든지 사용하셔도 좋습니다
반응형'C_C++ 프로그래밍 > IOCP' 카테고리의 다른 글
[IOCP] IOCP 서버 및 클라이언트 에코 예제 (0) 2019.07.06