Select모델 에코서버 프로그래밍
Select 모델
소켓모드(블로킹, 넌블로킹)와 관계없이 여러 소켓을 한 스레드로 처리할 수 있다
서버 코드
더보기
#pragma comment(lib, "ws2_32")
#include <stdio.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#define SERVERPORT 9000
#define BUFSIZE 512
// 소켓 정보 저장을 위한 구조체와 변수
struct SOCKETINFO
{
SOCKET sock;
char buf[BUFSIZE + 1]; // 맨 끝에 '\0'
int recvBytes;
int sendBytes;
};
int nTotalSockets = 0;
SOCKETINFO* socketInfoArray[FD_SETSIZE];
// 소켓 관리 함수
BOOL AddSocketInfo(SOCKET sock);
void RemoveSocketInfo(int nIndex);
int main()
{
int retVal;
// 윈속 초기화
WSADATA wsa;
retVal = WSAStartup(MAKEWORD(2, 2), &wsa);
if (retVal != 0)
{
printf("error WSAStartup() %d\n", retVal);
return 1;
}
// listen socket
SOCKET listenSock = socket(AF_INET, SOCK_STREAM, 0);
if (listenSock == INVALID_SOCKET)
{
printf("error listen socket()\n");
return 1;
}
// bind
SOCKADDR_IN serverAddr;
ZeroMemory(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(SERVERPORT);
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
retVal = bind(listenSock, (SOCKADDR*)&serverAddr, sizeof(serverAddr));
if (retVal == SOCKET_ERROR)
{
printf("error bind()\n");
return 1;
}
// listen
retVal = listen(listenSock, SOMAXCONN);
if (retVal == SOCKET_ERROR)
{
printf("error listen()\n");
return 1;
}
// 넌블로킹 소켓으로 전환
u_long on = 1;
retVal = ioctlsocket(listenSock, FIONBIO, &on);
if (retVal == SOCKET_ERROR)
{
printf("error ioctlsocket()\n");
return 1;
}
// 데이터 통신에 사용할 변수
FD_SET rset, wset;
SOCKET clientSock;
SOCKADDR_IN clientAddr;
int addrLen, i;
while (1)
{
// 소켓 셋 초기화
FD_ZERO(&rset);
FD_ZERO(&wset);
FD_SET(listenSock, &rset);
for (i = 0; i < nTotalSockets; i++)
{
if (socketInfoArray[i]->recvBytes > socketInfoArray[i]->sendBytes)
FD_SET(socketInfoArray[i]->sock, &wset);
else
FD_SET(socketInfoArray[i]->sock, &rset);
}
// select()
retVal = select(0, &rset, &wset, NULL, NULL);
if (retVal == SOCKET_ERROR)
{
printf("error select()\n");
return 1;
}
// 소켓 셋 검사(1) : 클라이언트 접속 수용
if (FD_ISSET(listenSock, &rset))
{
addrLen = sizeof(clientAddr);
clientSock = accept(listenSock, (SOCKADDR*)&clientAddr, &addrLen);
if (clientSock == INVALID_SOCKET)
{
printf("error accept()\n");
}
else
{
char buf[16];
inet_ntop(AF_INET, &clientAddr.sin_addr, buf, sizeof(buf));
printf("[TCP 서버] 클라이언트 접속 : IP 주소 = %s, 포트 번호 = %d\n", buf, ntohs(clientAddr.sin_port));
// 소켓 정보 추가
AddSocketInfo(clientSock);
}
}
// 소켓 셋 검사(2) : 데이터 통신
for (i = 0; i < nTotalSockets; i++)
{
SOCKETINFO* ptr = socketInfoArray[i];
if (FD_ISSET(ptr->sock, &rset))
{
// 데이터 받기
retVal = recv(ptr->sock, ptr->buf, BUFSIZE, 0);
if (retVal == SOCKET_ERROR)
{
printf("recv SOCKET_ERROR\n");
RemoveSocketInfo(i);
continue;
}
else if (retVal == 0)
{
printf("recv 0\n");
RemoveSocketInfo(i);
continue;
}
ptr->recvBytes = retVal;
// 받은 데이터 출력
addrLen = sizeof(clientAddr);
getpeername(ptr->sock, (SOCKADDR*)&clientAddr, &addrLen);
ptr->buf[retVal] = '\0';
char buf[16];
inet_ntop(AF_INET, &clientAddr.sin_addr, buf, sizeof(buf));
printf("[TCP %s:%d] %s\n", buf, ntohs(clientAddr.sin_port), ptr->buf);
}
if (FD_ISSET(ptr->sock, &wset))
{
// 데이터 보내기
retVal = send(ptr->sock, ptr->buf + ptr->sendBytes, ptr->recvBytes - ptr->sendBytes, 0);
if (retVal == SOCKET_ERROR)
{
printf("send() SOCKET_ERROR\n");
RemoveSocketInfo(i);
continue;
}
ptr->sendBytes += retVal;
if (ptr->recvBytes == ptr->sendBytes)
{
ptr->recvBytes = 0;
ptr->sendBytes = 0;
}
}
}
}
// 윈속 종료
WSACleanup();
return 0;
}
// 소켓 정보 추가
BOOL AddSocketInfo(SOCKET sock)
{
if (nTotalSockets >= FD_SETSIZE)
{
printf("[오류] 소켓 정보를 추가할 수 없습니다\n");
return FALSE;
}
SOCKETINFO* ptr = new SOCKETINFO;
if (ptr == NULL)
{
printf("[오류] 메모리가 부족합니다\n");
return FALSE;
}
ptr->sock = sock;
ptr->recvBytes = 0;
ptr->sendBytes = 0;
socketInfoArray[nTotalSockets++] = ptr;
return TRUE;
}
// 소켓 정보 삭제
void RemoveSocketInfo(int nIndex)
{
SOCKETINFO* ptr = socketInfoArray[nIndex];
// 클라이언트 정보 얻기
SOCKADDR_IN clientAddr;
int addrLen = sizeof(clientAddr);
getpeername(ptr->sock, (SOCKADDR*)&clientAddr, &addrLen);
char buf[16];
inet_ntop(AF_INET, &clientAddr.sin_addr, buf, sizeof(buf));
printf("[TCP 서버] 클라이언트 종료: IP 주소 = %s, 포트 번호 = %d\n", buf, ntohs(clientAddr.sin_port));
closesocket(ptr->sock);
delete ptr;
if (nIndex != (nTotalSockets - 1))
socketInfoArray[nIndex] = socketInfoArray[nTotalSockets - 1];
--nTotalSockets;
}
클라이언트 코드
더보기
#pragma comment(lib, "ws2_32")
#include <stdio.h>
#include <WinSock2.h>
#include <ws2tcpip.h>
#define SERVERIP "127.0.0.1"
#define SERVERPORT 9000
#define BUFSIZE 512
// 사용자 정의 데이터 수신 함수
int recvn(SOCKET socket, char* buf, int len, int flag);
int main()
{
int retVal;
// 윈속 초기화
WSADATA wsaData;
retVal = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (retVal != 0)
{
printf("error WSAStartup() %d\n", retVal);
return 1;
}
// socket()
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
{
printf("error socket() %d\n", WSAGetLastError());
return 1;
}
// connect()
SOCKADDR_IN sockAddr;
ZeroMemory(&sockAddr, sizeof(sockAddr));
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(SERVERPORT);
inet_pton(sockAddr.sin_family, SERVERIP, &sockAddr.sin_addr);
retVal = connect(sock, (SOCKADDR*)&sockAddr, sizeof(sockAddr));
if (retVal == SOCKET_ERROR)
{
printf("error connect() [%d]\n", WSAGetLastError());
return 1;
}
// 데이터 통신에 사용할 변수
char buf[BUFSIZE + 1];
int len;
while (1)
{
// 데이터 입력
printf("\n[보낼 데이터] ");
if (fgets(buf, BUFSIZE, stdin) == NULL)
break;
// '\n' 문자 제거
len = strlen(buf);
if (buf[len - 1] == '\n')
buf[len - 1] = '\0';
if (strlen(buf) == 0)
break;
// "bye" 이면 종료
if (strcmp(buf, "bye") == 0)
break;
// 데이터 보내기
retVal = send(sock, buf, strlen(buf), 0);
if (retVal == SOCKET_ERROR)
{
printf("error send()\n");
break;
}
printf("[TCP 클라이언트] %d바이트를 보냈습니다.\n", retVal);
// 데이터 받기
retVal = recvn(sock, buf, retVal, 0);
if (retVal == 0 || retVal == SOCKET_ERROR)
{
printf("error recv %d\n", retVal);
break;
}
// 받은 데이터 출력
buf[retVal] = '\0';
printf("[TCP 클라이언트] %d바이트를 받았습니다.\n", retVal);
printf("[받은 데이터] %s\n", buf);
}
// closesocket()
closesocket(sock);
// 윈속 종료
WSACleanup();
return 0;
}
int recvn(SOCKET socket, char* buf, int len, int flag)
{
int received;
int left = len;
while (left > 0)
{
received = recv(socket, buf, left, flag);
if (received == 0 || received == SOCKET_ERROR)
return received;
left -= received;
buf += received;
}
return len - left;
}
실행 결과
반응형
'공부' 카테고리의 다른 글
복습) 문제 만들기 20201020 (0) | 2020.10.20 |
---|---|
WSAAsyncSelect 모델 서버 프로그래밍 (0) | 2020.10.19 |
TCP 파일전송 프로그램 (0) | 2020.10.13 |
TCP 에코서버 프로그래밍 (0) | 2020.10.10 |
내 PC의 backlog 큐 개수 구하기 (2) | 2020.10.08 |