-
[IOCP] IOCP 서버 및 클라이언트 에코 예제C_C++ 프로그래밍/IOCP 2019. 7. 6. 14:43
개발 환경
OS
Windows 10
컴파일러
VC++ 2017
IOCP Echo
예제 및 git에서 얻은 정보를 토대로 짜집기 하여직접 구현한 서버입니다.현재 서버가 클라이언트에게 데이터를 받으면, 다른 클라이언트들에게도
데이터를 전송을 하는 부분은 구현은 되었지만... (vector 사용)
클라이언트가 봉쇄 통신을 하고 있기 때문에, 이 부분은 아직 미구현입니다. (대충 복붙하였습니다)
(구현 하면 채팅이 가능하게 될 것으로 예상은 합니다.)
( 학습 및 테스트 용으로 구현하였으니, 테스트 혹은 학습 용도로 사용하시는 것을 추천드립니다.)
퀄리티가 떨어져서..
수정해야 하는 부분난잡한 논리 흐름 (비동기화를 적절히 사용한 흐름), 클라이언트 비동기화 구현
추후에는 상속 및 캡슐화를 해보려고 합니다.
클라이언트에게 데이터를 뿌리는 기능도 추가 예정
IOCP_Server.cpp
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202#include <iostream>#include <stdlib.h>#include <process.h>#include <winsock2.h>#include <windows.h>#include <vector>#include <Ws2tcpip.h> //inet_pton// vs warning and winsock error#pragma comment(lib, "ws2_32.lib")#pragma warning (disable : 4996)constexpr int PORT_NUM = 3800;constexpr int BUF_SIZE = 1024;constexpr int READ = 3;constexpr int WRITE = 5;typedef struct // socket info{SOCKET hClntSock;SOCKADDR_IN clntAdr;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, 0, 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, 5);while (1){SOCKET hClntSock;SOCKADDR_IN clntAdr;int addrLen = sizeof(clntAdr);hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &addrLen);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; // 클라이언트의 정보를 구조체에 담아 놓는다.printf("New Client Access : %s\n", handleInfo->ip);memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen);// 소켓 입출력 포트에 accept을 통해서 return 된 클라이언트 정보를 묶는다.CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (DWORD)handleInfo, 0);// 클라이언트 user data 초기화UserList.push_back(handleInfo);// 클라이언트가 가지게 될 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;// 비동기 입출력 시작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("message received!");// critical section// EOF 전송, client exitif (bytesTrans == 0){std::vector<LPPER_HANDLE_DATA>::iterator iter;for (iter = UserList.begin(); iter != UserList.end(); ++iter) {if ((*iter)->hClntSock == sock) {UserList.erase(iter);break;}}closesocket(sock);free(handleInfo);free(ioInfo);continue;}// critical section end// 클라이언트가 가진 데이터 구조체의 정보를 바꾼다.// 이젠 서버가 쓰기를 행함/* (추가 내용)handling을 사용하여다중 클라이언트에게 전달 */std::vector<LPPER_HANDLE_DATA>::iterator iter;memcpy(message, ioInfo->wsaBuf.buf, BUF_SIZE);message[bytesTrans] = '\0'; // 문자열의 끝에 \0을 추가한다 (쓰레기 버퍼 방지)printf("Sock[%d] : %s\n", sock, message); //testfree(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(ioInfo->buffer, message);ioInfo->wsaBuf.buf = ioInfo->buffer;ioInfo->rwMode = WRITE;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 <WinSock2.h>#include <iostream>#include <stdio.h>#include <tchar.h>using namespace std;#define MAX_BUFFER 1024#define SERVER_PORT 3800#define SERVER_IP "127.0.0.1"#pragma warning (disable : 4996)#pragma comment(lib, "ws2_32.lib")struct stSOCKETINFO{WSAOVERLAPPED overlapped;WSABUF dataBuf;SOCKET socket;char messageBuffer[MAX_BUFFER];int recvBytes;int sendBytes;};int main(){WSADATA wsaData;// 윈속 버전을 2.2로 초기화int nRet = WSAStartup(MAKEWORD(2, 2), &wsaData);if (nRet != 0) {std::cout << "Error : " << WSAGetLastError() << std::endl;return false;}// TCP 소켓 생성SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (clientSocket == INVALID_SOCKET) {std::cout << "Error : " << WSAGetLastError() << std::endl;return false;}std::cout << "socket initialize success." << std::endl;// 접속할 서버 정보를 저장할 구조체SOCKADDR_IN stServerAddr;char szOutMsg[MAX_BUFFER];char sz_socketbuf_[MAX_BUFFER];stServerAddr.sin_family = AF_INET;// 접속할 서버 포트 및 IPstServerAddr.sin_port = htons(SERVER_PORT);stServerAddr.sin_addr.s_addr = inet_addr(SERVER_IP);nRet = connect(clientSocket, (sockaddr*)&stServerAddr, sizeof(sockaddr));if (nRet == SOCKET_ERROR) {std::cout << "Error : " << WSAGetLastError() << std::endl;return false;}std::cout << "Connection success..." << std::endl;while (true) {memset(szOutMsg, 0x00, MAX_BUFFER);std::cout << ">>";std::cin >> szOutMsg;if (_strcmpi(szOutMsg, "quit") == 0) break;int nSendLen = send(clientSocket, szOutMsg, strlen(szOutMsg), 0);if (nSendLen == -1) {std::cout << "Error : " << WSAGetLastError() << std::endl;return false;}std::cout << "Message sended : bytes[" << nSendLen << "], message : [" <<szOutMsg << "]" << std::endl;memset(sz_socketbuf_, 0x00, MAX_BUFFER);int nRecvLen = recv(clientSocket, sz_socketbuf_, 1024, 0);if (nRecvLen == 0) {std::cout << "Client connection has been closed" << std::endl;closesocket(clientSocket);return false;}else if (nRecvLen == -1) {std::cout << "Error : " << WSAGetLastError() << std::endl;closesocket(clientSocket);return false;}std::cout << "Message received : bytes[" << nRecvLen << "], message : [" <<sz_socketbuf_ << "]" << std::endl;}closesocket(clientSocket);std::cout << "Client has been terminated..." << std::endl;return 0;}cs 실행결과
클라이언트에게 보내는 기능을 현재 Server가 하고 있지만Client는 현재 비봉쇄 통신을 하고 있지 않기 때문에 받지 못하고 있습니다.마무리
winsock MSDN 삽질하다 보니까 슬슬 알게 될 것 같은 느낌적인 느낌이다.그리고 iocp는 학습자료 예제가 부족한 것 같습니다. (죄다 에코..)※ 본 글은 개인 포트폴리오 혹은 공부용으로 사용하기 때문에, 무단 복사 유포는 금지하지만, 개인 공부 용도로는 얼마든지 사용하셔도 좋습니다
반응형'C_C++ 프로그래밍 > IOCP' 카테고리의 다른 글
[IOCP] 채팅 서버 및 테스트 클라이언트 예제 (0) 2019.07.10