Echo Server는 클라이언트가 보낸 데이터를 그대로 다시 반환하는 서버를 말한다. 즉, 클라가 서버에 메세지를 전송하면, 서버는 그 메세지를 그대로 클라이언트에게 다시 보낸다.
🔹 Echo Server, Echo Client 의 주요 특징
구현이 간단하기 때문에 네트워크 프로그래밍을 학습하는 초보자에게 적합하다.
클라이언트와 서버 간 통신이 정상적으로 이루어지는지 확인할 때 유용하다.
TCP 기반 Echo Server
#include<winsock2.h>
#pragma comment(lib, "ws2_32")
int main()
{
// 윈속 초기화
WSADATA wsa = { 0 };
if(::WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
puts("ERROR: 윈속을 초기화 할 수 없습니다.");
return 0;
}
// 1. 접속대기 소켓을 생성한다.
SOCKET hSocket = ::socket(AF_INET, SOCK_STREAM, 0);
if(hSocket == INVALID_SOCKET)
{
puts("ERROR: 접속 대기 소켓을 생성할 수 없습니다.");
return 0;
}
// 2. 포트 바인딩
SOCKADDR_IN svraddr = { 0 };
svraddr.sin_family = AF_INET;
svraddr.sin_port = htons(25000);
svraddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
if(::bind(hSocket, (SOCKADDR*)&svraddr, sizeof(svraddr)) == SOCKET_ERROR)
{
puts("ERROR: 소켓에 IP주소와 포트를 바인드 할 수 없습니다.");
return 0;
}
// 3. 접속대기 상태로 전환
if(::listen(hSocket, SOMAXCONN) == SOCKET_ERROR)
{
puts("ERROR: 리슨 상태로 전환할 수 없습니다.");
return 0;
}
// 4. 클라이언트 접속 처리 및 대응
SOCKADDR_IN clientaddr = { 0 };
int nAddrLen = sizeof(clientaddr);
SOCKET hClient = 0;
char szBuffer[128] = { 0 };
int nReceive = 0;
// 4.1 클라이언트 연결을 받아들이고 새로운 소켓을 생성한다. (개방)
while((hClient = ::accept(hSocket,
(SOCKADDR*)&clientaddr,
&nAddrLen)) != INVALID_SOCKET)
{
puts("새 클라이언트가 연결되었습니다."); fflush(stdout);
// 4.2 클라이언트로부터 문자열을 수신한다.
while((nReceive = ::recv(hClient, szBuffer, sizeof(szBuffer, 0)) > 0)
{
// 4.3 수신한 문자열을 그대로 전송한다.
::send(hClient, szBuffer, sizeof(szBuffer), 0);
puts(szBuffer); fflush(stdout);
memset(szBuffer, 0, sizeof(szBuffer));
}
// 4.4 클라이언트가 연결을 종료한다.
::shutdown(hClient, SD_BOTH);
::closesocket(hClient);
puts("클라이언트 연결에 끊겼습니다."); fflush(stdout);
}
// 5. 리슨 소켓 닫기
closesocket(hSocket);
// 윈속을 해제 한다.
::WSACleanup();
return 0;
}
TCP 기반 Echo Client
#include <winsock2.h>
#pragma comment(lib, "ws2_32")
int main()
{
// 윈속 초기화
WSADATA wsa = { 0 };
if (::WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
puts("ERROR: 윈속을 초기화 할 수 없습니다.");
return 0;
}
//1. 접속대기 소켓 생성
SOCKET hSocket = ::socket(AF_INET, SOCK_STREAM, 0);
if (hSocket == INVALID_SOCKET)
{
puts("ERROR: 소켓을 생성할 수 없습니다.");
return 0;
}
//2. 포트 바인딩 및 연결
SOCKADDR_IN svraddr = { 0 };
svraddr.sin_family = AF_INET;
svraddr.sin_port = htons(25000);
svraddr.sin_addr.S_un.S_addr = inet_addr("192.168.77.2");
if (::connect(hSocket,
(SOCKADDR*)&svraddr, sizeof(svraddr)) == SOCKET_ERROR)
{
puts("ERROR: 서버에 연결할 수 없습니다.");
return 0;
}
//3. 채팅 메시지 송/수신
char szBuffer[128] = { 0 };
while (1)
{
//사용자로부터 문자열을 입력 받는다.
gets_s(szBuffer);
if (strcmp(szBuffer, "EXIT") == 0)
{
break;
}
//사용자가 입력한 문자열을 서버에 전송한다.
::send(hSocket, szBuffer, strlen(szBuffer) + 1, 0);
//서버로부터 방금 보낸 문자열에 대한 에코 메시지를 수신한다.
memset(szBuffer, 0, sizeof(szBuffer));
::recv(hSocket, szBuffer, sizeof(szBuffer), 0);
printf("From server: %s\n", szBuffer);
}
//4. 소켓을 닫고 종료.
//::shutdown(hSocket, SD_BOTH);
::closesocket(hSocket);
//※윈속 해제
::WSACleanup();
return 0;
}
🔹 Wire shark로 연결 상태 확인
앞서 배운 내용 처럼 서버를 실행하고 클라를 실행한후 port를 25000으로 지정하면
3 Way Handshake를 수행한 결과를 위 그림처럼 와이어 샤크로 확인할 수 있다. [SYN], [SYN, ACK], [ACK]
일반적인 데이터 주고 받을때
일반적인 종료 상황에서의 4 Way Handshake