본문 바로가기

소켓 프로그래밍/서버와 클라이언트

2) 에코 서버

에코서버란 클라이언트가 전송해주는 데이터를 그대로 되돌려 전송해 주는 기능을 가진 서버를 말한다. 에코 서버 클라이언트 모델의 특징은 몇 바이트를 송수신할 것인지 예상할 수 있다는 것이 큰 특징이다. 그 이유는 전송한만큼 바이트를 되돌려 받기 때문이다. 

서버 프로그램의 구현 과정

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(22), &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(22), &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 - 10);
        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