공부

Select모델 에코서버 프로그래밍

글로벌디노 2020. 10. 17. 22:41

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