프로그래밍의 기초/TCP | IP

Iterative 기반의 서버, 클라이언트 구현

Kim나현 2022. 1. 10. 19:17
반응형
  • 에코 서버: 클라이언트가 전송하는 문자열 데이터를 그대로 재전송

 

  • Iterative 서버의 함수호출 순서

- 계속된 클라이언트의 연결요청을 수락하기 위해 accept함수를 반복

- 한 순간 하나의 클라이언트의 socket을 대상으로 하지만 프로세스, 쓰레드를 통해 둘 이상의 클라이언트의 socket을 대상으로 할 수 있음

 

[Iterative 형태로 동작하는 에코 서버, 에코 클라이언트]

- 서버는 한 순간 하나의 클라이언트와 연결되어 에코 서비스를 제공

- 서버는 총 다섯 개의 클라이언트에게 순차적으로 서비스를 제공하고 종료

- 클라이언트는 프로그램 사용자로부터 문자열 데이터를 입력 받아 서버에 전송

- 서버는 전송받은 문자열 데이터를 클라이언트에게 재전송 (에코)

- 서버와 클라이언트간의 문자열 에코는 클라이언트가 Q를 입력할 때까지 계속

 

// iterative echo server
#define BUF_SIZE	1024
int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET clnt_sock, serc_sock;
	SOCKADDR_IN serv_addr, clnt_addr;
	socklen_t clnt_sz;
	char message[BUF_SIZE];
	int read_len;

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

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		err_handling("start up() error");

	serc_sock = socket(PF_INET, SOCK_STREAM, 0);
	if (serc_sock == INVALID_SOCKET)
		err_handling("socket() error");

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

	if (bind(serc_sock, (SOCKADDR*)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)
		err_handling("bind() error");

	if(listen(serc_sock, 5)== SOCKET_ERROR)
		err_handling("listen() error");

	clnt_sz = sizeof(clnt_addr);

	int n = 0;
	while (n < 5)
	{
		clnt_sock = accept(serc_sock, (SOCKADDR * )&clnt_addr, &clnt_sz);
		if (clnt_sock == INVALID_SOCKET)
			err_handling("accept() error");
		else
			printf("connect: %d\n", ++n);
		
		while ((read_len = recv(clnt_sock, message, BUF_SIZE, 0)) != 0)
			send(clnt_sock, message, read_len, 0);

		closesocket(clnt_sock);
	}
	closesocket(serc_sock);
	WSACleanup();
	return 0;
}

- 클라이언트 socket에서 closesocket이 호출되면 상대 socket에 EOF가 전달되므로  recv 함수에서 0 반환

// iterative echo client 
#define BUF_SIZE	1024
int main(int argc, char* argv[])
{
	char message[BUF_SIZE];
	char recv_m[BUF_SIZE];
	WSADATA wsaData;
	SOCKET clnt_sock, serv_sock;
	SOCKADDR_IN serv_addr;
	int wri_len;

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

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		err_handling("startup() error");

	clnt_sock = socket(PF_INET, SOCK_STREAM, 0);
	if (clnt_sock == INVALID_SOCKET)
		err_handling("socket() error");

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

	if (connect(clnt_sock, &serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)
		err_handling("connect() error");
	else
		printf("connect....\n");

	while (strcmp(recv_m, "q\n") && strcmp(recv_m, "Q\n"))
	{
		fputs("Input messge(Q:exit): ", stdout); 
		fgets(message, BUF_SIZE, stdin);

		send(clnt_sock, message, strlen(message), 0);
		wri_len = recv(clnt_sock, recv_m, BUF_SIZE - 1, 0);
		recv_m[wri_len] = 0;
		printf("receive data: %s", recv_m);
	}
	closesocket(clnt_sock);		// eof 전달: 연결의 끝
	WSACleanup();

	return 0;
}

 // 클라이언트 실행 창

connect....
Input messge(Q:exit): echo
receive data: echo
Input messge(Q:exit): r
receive data: r
Input messge(Q:exit): q
receive data: q​

// 서버 실행 창

connect: 1
connect: 2
connect: 3
connect: 4
connect: 5

- 에코 클라이언트의 문제점

1. TCP 클라이언트는 데이터의 경계가 존재하지 않기 때문에 클라이언트에서 둘 이상의 write 함수호출로 문자열 정보가 묶여 한번에 서버로 전달되면 클라이언트는 한번에 둘 이상의 문자열 정보를 서버로부터 돌려받을 수 있음

2. 서버는 한 번의 send함수로 전송을 명령했지만 전송할 데이터의 크기가 크다면 운영체제에서 내부적으로 여러 개의 조각으로 나눠 클라이언트에게 전송할 수 있음 (데이터의 모든 조각이 클라이언트에게 전송완료되지 않은 상태에서 recv함수를 호출할 수 있음)

반응형