에코 서버란 클라이언트에서 수신 된 데이터를 그대로 다시 클라이언트에게 되돌려 보내는 서버를 의미한다.

Iterative란 반복적이라는 의미로 각 클라이언트의 연결 요청을 순차적으로 처리하는 서버를 의미한다.

이 서버는 한 번에 하나의 클라이언트 요청만 처리하며, 하나의 연결이 완료된 후 다음 연결을 처리한다.

 

// Server

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#define BUF_SIZE 1024
void ErrorHandling(char* message);

int main(int argc, char* argv[]) {
	WSADATA wsaData;
	SOCKET hServSock, hClntSock;
	char message[BUF_SIZE];
	int strLen, i;

	SOCKADDR_IN servAddr, clntAddr;
	int clntAddrSize;
	

	if (argc != 2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
		ErrorHandling("WSAStartup() error!");
	}

	hServSock = socket(PF_INET, SOCK_STREAM, 0);
	if (hServSock == INVALID_SOCKET) {
		ErrorHandling("socket() error!");
	}

	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAddr.sin_port = htons(atoi(argv[1]));

	if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) {
		ErrorHandling("bind() error!");
	}

	if (listen(hServSock, 5) == SOCKET_ERROR) {
		ErrorHandling("listen() error!");
	}

	clntAddrSize = sizeof(clntAddr);

	// 연결 요청 대기 큐의 크기만큼 반복
	for (i = 0; i < 5; i++) {
		
		hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &clntAddrSize);
		if (hClntSock == -1) {
			ErrorHandling("accept() error!");
		}
        // 연결 되었다면 몇 번째 클라이언트인지 출력
		else {
			printf("Connected client %d \n", i + 1);
		}
		
        // 클라이언트한테 데이터를 받으면
		while ((strLen = recv(hClntSock, message, BUF_SIZE, 0)) != 0) {
        	// 그대로 돌려주기
			send(hClntSock, message, strLen, 0);
		}

		closesocket(hClntSock);
	}
	
	closesocket(hServSock);
	WSACleanup();

	return 0;
}

void ErrorHandling(char* message) {
	fputs(message, stderr);
	fputs("\n", stderr);
	exit(1);
}

 

// Client

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#define BUF_SIZE 1024
#define _WINSOCK_DEPRECATED_NO_WARNINGS
void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hSocket;
	char message[BUF_SIZE];
	int strLen;
	SOCKADDR_IN servAddr;

	if (argc != 3) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
		ErrorHandling("WSAStartup() error!");
	}

	hSocket = socket(PF_INET, SOCK_STREAM, 0);
	if (hSocket == INVALID_SOCKET) {
		ErrorHandling("socket() error!");
	}

	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = inet_addr(argv[1]);
	servAddr.sin_port = htons(atoi(argv[2]));

	if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) {
		ErrorHandling("connect() error!");
	}
	else {
		printf("Connected.........");
	}

	while (1) {
		fputs("Input message(Q to quit): ", stdout);
		fgets(message, BUF_SIZE, stdin);
		
        // 입력 값이 q or Q이면 종료
		if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) {
			break;
		}
		
        // 입력 값 보내기
		send(hSocket, message, strlen(message), 0);
		strLen = recv(hSocket, message, BUF_SIZE - 1, 0);
		message[strLen] = 0;
        // 서버로부터 받은 값 출력
		printf("Message from server: %s", message);
	}

	closesocket(hSocket);
	WSACleanup();

	return 0;
}

void ErrorHandling(char* message) {
	fputs(message, stderr);
	fputs("\n", stderr);
	exit(1);
}

 

 

2개의 클라이언트를 실행하였다.

2개의 클라이언트를 실행 했으나 서버에는 Connected client1만 뜬 상태에서 client1과 1대1 통신이 진행되었다.

client1에서 입력한 값을 서버에서 그대로 돌려주었고 q를 눌러 연결을 끊었을 때 서버에서 Connected clinet2가 뜨며 client2와 연결이 되었다.

멈춰있던 client2에서도 입력이 가능해졌고 입력값을 그대로 돌려받았다.

 

 

위의 Iterative 에코 클라이언트에서  문제가 생길 수 있는 부분이 있다.

while (1) {
    fputs("Input message(Q to quit): ", stdout);
    fgets(message, BUF_SIZE, stdin);

    if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) {
        break;
    }

    send(hSocket, message, strlen(message), 0);
    strLen = recv(hSocket, message, BUF_SIZE - 1, 0);
    message[strLen] = 0;
    printf("Message from server: %s", message);
}

해당 부분에서 입력 받은 데이터를 send를 통해 통째로 서버에 보내고 있다.

다음줄에서는 recv를 통해 서버에서 데이터를 통째로 받고 있다.

 

서버에서 데이터를 다 전달하지 못하였는데 recv를 해버리는 경우 문제가 발생할 수 있다.

 

while (1) {
    fputs("Input message(Q to quit): ", stdout);
    fgets(message, BUF_SIZE, stdin);

    if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) {
        break;
    }

    strLen = send(hSocket, message, strlen(message), 0);
    recvLen = 0;

    while (recvLen < strLen) {
        recvCnt = recv(hSocket, message, BUF_SIZE - 1, 0);
        if (recvCnt == -1) {
            ErrorHandling("recv() error!");
        }
        recvLen += recvCnt;
    }

    message[strLen] = 0;
    printf("Message from server: %s", message);
}

위와 같이 바꾸어 주면 해당 문제를 해결할 수 있다.

strLen에 send 해주는 message의 길이를 저장해 둔 후 recv한 데이터의 길이가 strLen보다 커질 때 while을 빠져나와 출력을 해주면 된다.

'네트워크' 카테고리의 다른 글

소켓 프로그래밍 - TCP 소켓  (0) 2023.09.10
ReaderWriterLock 구현  (0) 2023.08.05
ReaderWriterLock  (0) 2023.08.04
AutoResetEvent  (0) 2023.08.03
SpinLock  (0) 2023.08.02

+ Recent posts