에코서버란 클라이언트가 전송해주는 데이터를 그대로 되돌려 전송해 주는 기능을 가진 서버를 말한다. 에코 서버 클라이언트 모델의 특징은 몇 바이트를 송수신할 것인지 예상할 수 있다는 것이 큰 특징이다. 그 이유는 전송한만큼 바이트를 되돌려 받기 때문이다.
서버 프로그램의 구현 과정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
#include <iostream>
#include <Winsock2.h>
#pragma comment(lib, "ws2_32.lib")
using namespace std;
#define BUFFER_SIZE 1024
int main()
{
// 윈속 초기화
WSADATA wsaData = {};
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
cout << "WSAStartup() Error" << endl;
return -1;
}
// 서버 소켓 생성
SOCKET servSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (servSocket == INVALID_SOCKET)
{
cout << "socket() Error" << endl;
return -1;
}
// 소켓에 주소 할당
SOCKADDR_IN sockAddr = {};
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(4000);
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(servSocket, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR_IN)) == -1)
{
cout << "bind() Error" << endl;
return -1;
}
// 연결 요청 대기 상태
listen(servSocket, SOMAXCONN);
char message[BUFFER_SIZE] = {};
int length = 0;
// 연결 요청 수락
SOCKADDR_IN tClntAddr = {};
int size = sizeof(SOCKADDR_IN);
SOCKET clntSocket = accept(servSocket, (SOCKADDR*)&tClntAddr, &size);
if (clntSocket == SOCKET_ERROR)
{
closesocket(servSocket);
WSACleanup();
}
// 데이터의 송수신
while ((length = recv(clntSocket, message, BUFFER_SIZE, 0)) != 0)
{
send(clntSocket, message, length, 0);
}
// 클라이언트 연결 종료
closesocket(clntSocket);
// 서버 연결 종료
closesocket(servSocket);
// 윈도우 소켓 해제
WSACleanup();
}
|
cs |
데이터의 송수신 과정을 눈여겨보자. recv() 함수에서 클라이언트로부터 데이터 패킷이 날라오면 그 크기만큼 값을 그대로 반환을 해준다. 이것이 에코 서버의 핵심이다.
클라이언트 프로그램의 구현 과정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
#include <iostream>
#include <Winsock2.h>
#include <WS2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
using namespace std;
#define BUFFER_SIZE 1024
int main()
{
// 윈속 초기화
WSADATA wsaData = {};
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
cout << "WSAStartup() Error" << endl;
return -1;
}
// 서버로의 접속을 위한 소켓 생성
SOCKET clntSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (clntSocket == INVALID_SOCKET)
{
cout << "socket() Error" << endl;
return -1;
}
// 서버로의 연결 요청
SOCKADDR_IN sockAddr = {};
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(4000);
inet_pton(AF_INET, "127.0.0.1", &sockAddr.sin_addr);
if (connect(clntSocket, (SOCKADDR*)&sockAddr, sizeof(sockAddr)) == SOCKET_ERROR)
{
cout << "connect() Error" << endl;
return -1;
}
char message[BUFFER_SIZE] = {};
int length = 0;
while (true)
{
// 메시지 입력
fputs("전송할 메시지를 입력하세요 (q to quit) : ", stdout);
fgets(message, BUFFER_SIZE, stdin);
// q를 입력했을 경우 루프문 종료
if (strcmp(message, "q\n") == 0)
break;
// 메시지 송신
send(clntSocket, message, strlen(message), 0);
// 메시지 수신
length = recv(clntSocket, message, BUFFER_SIZE - 1, 0);
message[length] = 0;
cout << "서버로부터 전송된 메시지 : " << message << endl;
}
// 클라이언트 연결 종료
closesocket(clntSocket);
// 윈도우 소켓 해제
WSACleanup();
}
|
cs |
클라이언트는 서버와 연결을 한 후 루프문 속에서 보낸 메시지를 서버에게 그대로 받아와서 출력해주도록 구현을 해보았다. 직접 따라쳐보며 디버깅을 통해 어떠한 흐름으로 서버와 클라이언트가 통신하는지 확실하게 기억하자.
'소켓 프로그래밍 > 서버와 클라이언트' 카테고리의 다른 글
* Iterative 서버 (0) | 2020.07.14 |
---|---|
* TCP / IP 4계층 (0) | 2020.07.13 |
* 바이트 순서 (0) | 2020.07.09 |
* 주소 체계와 데이터 설정 (0) | 2020.07.08 |
* 소켓의 생성과 프로토콜 (0) | 2020.07.08 |