C++/C++ 적용 예제

C++ TCP/IP 가장 단순한 소켓 통신 네트워크 구현 - Server / Client (feat. 식당 주문 받기에 비유해보자)

쉽코딩 2023. 2. 19.

 

 

 

소켓 통신 구현

 

 

 

소켓통신 구현

소켓 통신은 네트워크에서 실행되는 둘 이상의 프로세스 간에 데이터 교환을 허용하는 IPC(프로세스 간 통신) 방법입니다. 소켓은 네트워크의 두 프로세스 간의 통신을 위한 끝점이며 IP 주소와 포트 번호의 고유한 조합으로 표시됩니다.

 

소켓 통신의 대표 그림


소켓 프로그래밍에서 프로세스는 소켓 API를 사용하여 네트워크를 통해 데이터를 송수신합니다. 데이터를 보내려는 프로세스는 소켓을 생성하고 수신 프로세스의 IP 주소와 포트 번호를 지정하고, 데이터를 받으려는 프로세스는 소켓을 생성하고 자신의 IP 주소와 포트 번호를 지정합니다.

소켓에는 두 가지 주요 유형이 있습니다.

▶ 스트림 소켓 (연결 지향형): TCP(전송 제어 프로토콜) 소켓이라고도 하며 안정적인 스트림 지향 연결을 제공합니다.

▶ 데이터그램 소켓 (비연결 지향형): UDP(사용자 데이터그램 프로토콜) 소켓이라고도 하며 신뢰할 수 없는 메시지 지향 연결을 제공합니다.

소켓 통신은 네트워크 프로그래밍, 특히 네트워크 프로토콜, 네트워크 서비스 및 분산 시스템 구현에서 널리 사용됩니다.

 

네트워크 소켓 이란 → https://ko.wikipedia.org/wiki/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC_%EC%86%8C%EC%BC%93

 

TCP/IP(전송 제어 프로토콜/인터넷 프로토콜) 프로토콜 제품군에서 소켓 통신은 네트워크를 통해 서로 다른 시스템의 프로세스 간에 데이터를 교환하는 것을 의미합니다. TCP/IP 프로토콜 제품군에는 두 가지 주요 유형의 소켓 통신이 있습니다.

  • TCP(전송 제어 프로토콜) 소켓 통신: 연결 지향형
    TCP는 신뢰할 수 있고 순서가 지정된 데이터 전달을 제공하는 신뢰할 수 있는 스트림 지향 프로토콜입니다. TCP 소켓 통신에서 클라이언트와 서버 모두에 소켓이 생성되고 둘 사이에 연결이 설정됩니다. 클라이언트는 서버에 데이터를 보내고 서버는 데이터를 수신하고 응답을 다시 클라이언트로 보냅니다. 데이터는 연속 스트림으로 전송되며 전송된 순서대로 도착하도록 보장됩니다.

  • UDP(사용자 데이터그램 프로토콜) 소켓 통신: 비연결 지향형
    UDP는 빠르고 효율적인 데이터 교환 방법을 제공하는 신뢰할 수 없는 메시지 지향 프로토콜입니다. UDP 소켓 통신에서는 클라이언트와 서버 양쪽에 소켓이 생성되지만 연결을 설정할 필요는 없습니다. 클라이언트는 별도의 패킷으로 서버에 데이터를 보내고 서버는 데이터를 수신하고 응답을 다시 클라이언트로 보냅니다. 데이터가 전송된 순서대로 도착하지 않거나 전혀 도착하지 않을 수 있습니다.

TCP와 UDP 소켓 통신의 주요 차이점은 이들이 제공하는 안정성 수준입니다. TCP는 순서가 있고 오류 없는 데이터 전달을 제공하는 신뢰할 수 있는 프로토콜인 반면 UDP는 데이터를 교환하는 빠르고 효율적인 방법을 제공하는 신뢰할 수 없는 프로토콜입니다. TCP는 일반적으로 이메일, 파일 전송 및 원격 로그인과 같이 안정적인 데이터 전송이 필요한 애플리케이션에 사용되는 반면 UDP는 일반적으로 실시간 비디오 및 오디오 스트리밍, 온라인 게임, VoIP에 사용됩니다.

 

 

TCP/IP 네트워크 구성 서버(server) 기초 지식 → It's like taking a restaurant order

TCP/IP 서버는 식당의 웨이터와 같은 서비스 제공자로 생각할 수 있습니다. 서버는 수신 요청을 수신하고(예: 웨이터가 고객이 도착하기를 기다리는 경우) 해당 요청을 수락한 다음(예: 웨이터가 고객을 앉히는 경우) 처리합니다(예: 웨이터가 주문을 받고 음식을 서빙하는 경우).

Socket creation : 이 단계는 웨이터가 테이블을 설정하고 고객을 위해 준비하는 것과 같습니다. 웨이터는 고객의 요청을 받기 위해 "테이블"(소켓)을 만들어야 합니다.

Binding the socket : 이 단계는 웨이터가 고객에게 특정 테이블을 할당하는 것과 같습니다. 웨이터는 고객의 요청을 받기 위해 테이블(소켓)을 특정 위치(IP 주소 및 포트)에 "바인딩"해야 합니다.

Listening for connections : 이 단계는 테이블 옆에 서서 고객의 요청을 받을 준비가 된 웨이터와 같습니다. 웨이터는 들어오는 요청(연결)을 "듣고" 고객을 앉힐 준비를 해야 합니다(연결 수락).

Accepting connections : 이 단계는 웨이터가 고객을 테이블에 앉히는 것과 같습니다. 웨이터는 들어오는 요청(연결)을 "수락"하고 고객에게 주문할 메뉴(소켓 설명자)를 제공해야 합니다.

Processing client requests: 이 단계는 웨이터가 고객의 주문을 받고 음식을 준비하는 것과 같습니다. 웨이터는 고객의 요청을 "처리"(클라이언트로부터 데이터 수신)하고 음식을 준비(요청된 데이터 또는 서비스 제공)해야 합니다.

Sending data : 이 단계는 웨이터가 고객에게 음식을 제공하는 것과 같습니다. 웨이터는 준비된 음식(데이터)을 고객에게 "전송"해야 합니다.

Closing the connection : 이 단계는 웨이터가 테이블을 치우고 다음 고객을 위해 준비하는 것과 같습니다. 웨이터는 연결(테이블)을 "닫고" 다음 고객을 위해 준비해야 합니다(다음 요청 처리).

TCP/IP 서버를 식당의 웨이터와 비교하면 서버 생성 및 클라이언트 요청 처리와 관련된 단계를 더 쉽게 이해할 수 있습니다.

 

TCP/IP 소켓 통신 서버(server) 설명

 

1. 소켓을 사용하기위한 헤더 include 및 기본설정 지정

소켓을 사용하기위한 헤더 include 및 기본설정 지정

 

#include <WinSock2.h>는 C/C++ 프로그램에서 Windows 소켓 2 API(WinSock2)에 대한 헤더 파일을 포함하는 전처리기 지시문입니다. Windows 소켓 2 API는 Windows에서 소켓을 만들고 사용하기 위한 함수 및 데이터 구조 집합을 제공합니다. WinSock2.h 헤더 파일은 API에서 사용하는 상수, 데이터 유형 및 함수 프로토타입을 정의합니다.

#pragma comment(lib, "ws2_32")는 ws2_32.lib 라이브러리 파일을 프로그램에 링크하도록 컴파일러에 알리는 pragma 지시어입니다. ws2_32.lib 라이브러리 파일에는 Windows 소켓 2 API의 구현이 포함되어 있으며 API를 사용하는 프로그램에 필요합니다.

Windows 소켓 2 API를 사용하는 C/C++ 프로그램에서 이러한 두 지시문은 일반적으로 API가 제대로 초기화되고 사용되도록 프로그램 시작 부분에 포함됩니다. Windows 기반 시스템에서 소켓 통신을 사용하려면 필요합니다.

 

#define PORT 4578은 값이 4578인 PORT라는 상수 기호를 정의하는 전처리기 지시어입니다. PORT의 값은 프로그램에서 소켓 통신에 사용될 포트 번호를 나타냅니다. 포트 번호는 네트워크로 연결된 호스트에서 특정 프로세스(서비스)를 식별하는 데 사용됩니다.

#define PACKET_SIZE 1024는 값 1024를 갖는 PACKET_SIZE라는 상수 심볼을 정의하는 전처리기 지시문입니다. PACKET_SIZE의 값은 프로그램에서 데이터 전송에 사용될 패킷의 크기를 나타냅니다. 패킷 크기는 단일 전송으로 보낼 수 있는 데이터의 양을 결정하며 통신의 효율성과 속도에 영향을 줄 수 있습니다.

이 두 상수는 소켓 통신 프로그램에서 통신에 사용할 포트 번호와 패킷 크기를 지정하는 데 사용됩니다. 이러한 상수를 기호 이름으로 정의하면 프로그램 전체에서 쉽게 변경하고 참조할 수 있으므로 코드를 더 읽기 쉽고 유지 관리할 수 있습니다.

 

WSADATA wsaData는 Windows 소켓 구현에 대한 정보를 저장하는 데 사용되는 WSADATA 유형의 변수입니다. WSADATA 구조는 WinSock2.h 헤더 파일에 정의되어 있으며 구현이 지원하는 Windows 소켓 사양의 버전, 구현이 지원할 수 있는 사양의 최고 버전 및 기타 기타 정보와 같은 정보를 포함합니다.

WSAStartup(MAKEWORD(2, 2), &wsaData)는 Windows 소켓 구현을 초기화하는 함수 호출입니다. WSAStartup 함수는 WinSock2.h 헤더 파일에 정의되어 있으며 Windows 소켓 API를 사용하는 프로그램에서 호출해야 하는 첫 번째 함수입니다. MAKEWORD(2, 2) 매크로는 Windows 소켓 구현에 대한 버전 번호를 만들고 &wsaData 인수는 구현에 대한 정보를 받을 WSADATA 구조에 대한 포인터입니다.

WSACleanup()은 Windows 소켓 구현의 사용을 종료하는 함수 호출입니다. WSACleanup 함수는 WinSock2.h 헤더 파일에 정의되어 있으며 Windows 소켓 API를 사용하는 프로그램에서 호출해야 하는 마지막 함수입니다. 이 함수는 Windows 소켓 구현에 의해 할당된 모든 리소스를 해제하며 리소스가 제대로 해제되도록 프로그램이 종료되기 전에 호출되어야 합니다.

 

일반적으로 Windows 소켓 구현을 초기화하고 정리하기 위해 Windows 기반 소켓 통신 프로그램에서 사용됩니다. WSAStartup 함수는 다른 Windows 소켓 함수를 사용하기 전에 호출해야 하며 WSACleanup 함수는 리소스가 제대로 해제되도록 프로그램이 종료되기 전에 호출해야 합니다.

 

 

2. 소켓생성

소켓생성

SOCKET hListen은 소켓을 나타내기 위해 WinSock2.h 헤더 파일에 정의된 유형인 SOCKET 유형의 변수입니다. hListen은 들어오는 연결을 수신하는 데 사용될 소켓에 대한 핸들입니다.

socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)는 소켓을 생성하는 함수 호출입니다. 소켓 기능은 WinSock2.h 헤더 파일에 정의되어 있으며 통신에 사용할 수 있는 소켓을 생성하는 데 사용됩니다. PF_INET 인수는 인터넷 주소 계열(IPv4)인 주소 계열을 지정합니다. SOCK_STREAM 인수는 스트림 소켓인 소켓 유형을 지정합니다. IPPROTO_TCP 인수는 TCP인 프로토콜을 지정합니다.

socket 함수는 hListen 변수에 저장된 생성된 소켓에 대한 핸들을 반환합니다. 그런 다음 핸들을 사용하여 소켓을 특정 주소 및 포트에 바인딩, 수신 연결 수신, 수신 연결 수락과 같은 다양한 소켓 작업을 수행할 수 있습니다.

주어진 코드에서 hListen 변수는 TCP 프로토콜을 사용하여 들어오는 연결을 수신하는 데 사용될 소켓에 대한 핸들을 저장하는 데 사용됩니다. 소켓은 적절한 인수로 소켓 함수를 호출하여 생성되며 소켓에 대한 핸들은 hListen 변수에 저장됩니다.

 

 

3. 소켓의 구성요소를 담을 구조체 생성 및 값 할당

소켓의 구성요소를 담을 구조체 생성 및 값 할당

SOCKADDR_IN은 Windows 소켓 API에서 인터넷 주소를 나타내기 위해 WinSock2.h 헤더 파일에 정의된 구조입니다. 이 구조는 통신을 위해 소켓에서 사용할 수 있는 IP 주소 및 포트 번호에 대한 정보를 저장하는 데 사용됩니다.

SOCKADDR_IN 구조는 다음과 같이 정의됩니다.

struct sockaddr_in {
    short sin_family;
    u_short sin_port;
    struct in_addr sin_addr;
    char sin_zero[8];
};

▷ sin_family 필드는 인터넷 주소 계열(IPv4)에 대해 AF_INET으로 설정되어야 하는 주소 계열을 지정합니다.

▷ sin_port 필드는 네트워크 바이트 순서로 포트 번호를 지정합니다.

▷ sin_addr 필드는 네트워크 바이트 순서로 IP 주소를 지정하는 구조체입니다.

▷ sin_zero 필드는 예약되어 있으며 0으로 설정되어야 합니다.

SOCKADDR_IN tListenAddr = {}; SOCKADDR_IN 구조를 생성하고 모두 0으로 초기화하는 변수 정의입니다. 이 구조는 소켓이 바인드될 주소 및 포트에 대한 정보를 저장하는 데 사용되며 소켓을 특정 주소 및 포트와 연결하기 위해 바인드 함수에 인수로 전달됩니다.

요약하면, SOCKADDR_IN 구조는 Windows 소켓 API에서 인터넷 주소를 나타내는 데 사용되며 SOCKADDR_IN tListenAddr = {}; 변수 정의는 소켓이 바인딩될 주소 및 포트에 대한 정보를 저장하는 데 사용되는 SOCKADDR_IN 구조를 만듭니다.

 

 

4. 소켓에 위에 설정한 주소정보를 묶어주고, 소켓을 접속대기상태로 만들어줌

소켓에 위에 설정한 주소정보를 묶어주고, 소켓을 접속대기상태로 만들어줌

bind(hListen, (SOCKADDR*)&tListenAddr, sizeof(tListenAddr))는 소켓을 특정 주소 및 포트와 연결하는 함수 호출입니다. 바인딩 기능은 WinSock2.h 헤더 파일에 정의되어 있으며 소켓을 특정 주소 및 포트에 바인딩하는 데 사용됩니다.

▷첫 번째 인수인 hListen은 주소와 포트에 바인딩될 소켓에 대한 핸들입니다.

▷두 번째 인수인 (SOCKADDR*)&tListenAddr은 주소 및 포트 정보를 저장하는 SOCKADDR_IN 구조에 대한 포인터입니다. SOCKADDR_IN 구조는 WinSock2.h 헤더 파일에 정의된 일반 소켓 주소 구조인 SOCKADDR 포인터로 캐스트됩니다.

▷세 번째 인수 sizeof(tListenAddr)는 SOCKADDR_IN 구조의 크기(바이트)입니다.

바인딩 함수는 성공 시 0을 반환하고 실패 시 0이 아닌 값을 반환합니다.

주어진 코드에서 바인드 함수는 hListen 핸들이 나타내는 소켓을 tListenAddr 구조에 지정된 주소 및 포트와 연결하는 데 사용됩니다. 바인드 함수는 소켓을 주소와 포트에 바인딩하기 위해 적절한 인수와 함께 호출되며 함수의 반환 값은 이 코드 스니펫에서 확인되지 않습니다.

 

listen(hListen, SOMAXCONN)은 소켓에서 들어오는 연결을 수신 대기하는 함수 호출입니다. 수신 기능은 WinSock2.h 헤더 파일에 정의되어 있으며 소켓이 들어오는 연결을 수신할 준비가 되었음을 지정하는 데 사용됩니다.

▷첫 번째 인수 hListen은 들어오는 연결을 수신할 소켓에 대한 핸들입니다.

▷두 번째 인수인 SOMAXCONN은 소켓이 동시에 처리할 수 있는 최대 연결 수입니다. SOMAXCONN 기호는 WinSock2.h 헤더 파일에 정의되어 있으며 최대 연결 수에 대한 최대 합당한 값을 나타냅니다.

listen 함수는 성공 시 0을 반환하고 실패 시 0이 아닌 값을 반환합니다.

주어진 코드에서 hListen 핸들이 나타내는 소켓이 들어오는 연결을 수신할 준비가 되었음을 지정하기 위해 listen 함수가 호출됩니다. listen 함수는 hListen 핸들과 SOMAXCONN 상수를 인수로 사용하여 호출되며 함수의 반환 값은 이 코드 스니펫에서 확인되지 않습니다.

 

5. 클라이언트 측 소켓 생성 및 정보를 담을 구조체 생성 및 값 할당, 클라이언트가 접속 요청하면 승인해주는 역할

클라이언트 측 소켓 생성 및 정보를 담을 구조체 생성 및 값 할당, 클라이언트가 접속 요청하면 승인해주는 역할

SOCKET hClient = accept(hListen, (SOCKADDR*)&tClntAddr, &iClntSize)는 소켓에서 들어오는 연결을 수락하는 함수 호출입니다. 수락 기능은 WinSock2.h 헤더 파일에 정의되어 있으며 청취 소켓에서 들어오는 연결을 수락하는 데 사용됩니다.

▷첫 번째 인수 hListen은 청취 소켓에 대한 핸들입니다.

▷두 번째 인수인 (SOCKADDR*)&tClntAddr은 연결 클라이언트의 주소에 대한 정보를 수신할 SOCKADDR_IN 구조에 대한 포인터입니다. SOCKADDR_IN 구조는 WinSock2.h 헤더 파일에 정의된 일반 소켓 주소 구조인 SOCKADDR 포인터로 캐스트됩니다.

▷세 번째 인수 &iClntSize는 SOCKADDR_IN 구조의 크기를 지정하는 정수에 대한 포인터입니다. 입력 시 두 번째 인수가 가리키는 버퍼의 크기를 지정합니다. 출력에는 반환된 주소의 실제 크기가 포함됩니다.

수락 함수는 들어오는 연결을 처리하기 위해 생성된 새 소켓에 대한 핸들을 반환합니다. 핸들은 hClient 변수에 저장됩니다.

주어진 코드에서 수락 함수는 hListen 핸들이 나타내는 소켓에서 들어오는 연결을 수락하기 위해 호출됩니다. 수락 함수는 들어오는 연결을 처리하기 위해 생성된 새 소켓에 대한 핸들을 반환하며 핸들은 hClient 변수에 저장됩니다. accept 함수는 또한 tClntAddr 구조의 연결 클라이언트 주소와 iClntSize 변수의 주소 크기에 대한 정보를 반환합니다.

 

 

6. 클라이언트 측으로부터 정보를 받아오고 출력, 클라이언트에 정보 전송 

클라이언트 측으로부터 정보를 받아오고 출력, 클라이언트에 정보 전송

char cBuffer[PACKET_SIZE] = {}는 PACKET_SIZE 상수로 지정된 크기의 문자 배열을 생성하는 변수 정의입니다. cBuffer 배열은 클라이언트로부터 받은 데이터를 저장하는 버퍼로 사용됩니다.

recv(hClient, cBuffer, PACKET_SIZE, 0)은 소켓에서 데이터를 받는 함수 호출입니다. recv 함수는 WinSock2.h 헤더 파일에 정의되어 있으며 소켓에서 데이터를 받는 데 사용됩니다.

▷첫 번째 인수 hClient는 데이터를 수신할 소켓에 대한 핸들입니다.

▷두 번째 인수 cBuffer는 데이터를 수신할 버퍼에 대한 포인터입니다.

▷세 번째 인수인 PACKET_SIZE는 버퍼의 크기(바이트)입니다.

▷네 번째 인수인 0은 수행할 수신 작업의 유형을 지정하는 플래그입니다. 값 0은 기본 수신 동작을 지정하는 데 사용됩니다.

recv 함수는 성공 시 받은 바이트 수를 반환하고 실패 시 0이 아닌 값을 반환합니다.

주어진 코드에서 recv 함수는 hClient 핸들이 나타내는 소켓에서 데이터를 수신하기 위해 호출됩니다. 수신된 데이터는 cBuffer 배열에 저장되며 버퍼의 크기는 PACKET_SIZE 상수로 지정됩니다. recv 함수는 소켓에서 데이터를 수신하기 위해 적절한 인수와 함께 호출되며 함수의 반환 값은 이 코드 스니펫에서 확인되지 않습니다.

 

send(hClient, cMsg, strlen(cMsg), 0)은 데이터를 소켓으로 보내는 함수 호출입니다. send 함수는 WinSock2.h 헤더 파일에 정의되어 있으며 데이터를 소켓으로 보내는 데 사용됩니다.

▷첫 번째 인수 hClient는 데이터를 수신할 소켓에 대한 핸들입니다.

▷두 번째 인수 cMsg는 전송할 데이터가 포함된 버퍼에 대한 포인터입니다.

▷세 번째 인수 strlen(cMsg)는 바이트 단위의 데이터 길이입니다. strlen 함수는 null로 끝나는 문자열의 길이를 반환하는 표준 C 라이브러리 함수입니다.

▷네 번째 인수인 0은 수행할 전송 작업의 유형을 지정하는 플래그입니다. 값 0은 기본 전송 동작을 지정하는 데 사용됩니다.

send 함수는 성공 시 전송된 바이트 수를 반환하고 실패 시 0이 아닌 값을 반환합니다.

주어진 코드에서 send 함수는 hClient 핸들이 나타내는 소켓으로 데이터를 보내기 위해 호출됩니다. 보낼 데이터는 cMsg 버퍼에 저장되며 데이터의 길이는 strlen 함수를 호출하여 결정됩니다. send 함수는 데이터를 소켓으로 보내기 위해 적절한 인수와 함께 호출되며 함수의 반환 값은 이 코드 스니펫에서 확인되지 않습니다.

 

closesocket(hClient) 및 closesocket(hListen)은 소켓을 닫는 함수 호출입니다. closesocket 함수는 WinSock2.h 헤더 파일에 정의되어 있으며 소켓을 닫는 데 사용됩니다.

closesocket 함수의 인수는 닫을 소켓에 대한 핸들입니다.

주어진 코드에서 closesocket 함수는 두 개의 소켓을 닫기 위해 두 번 호출됩니다. 첫 번째 호출인 closesocket(hClient)는 hClient 핸들이 나타내는 소켓을 닫습니다. 두 번째 호출인 closesocket(hListen)은 hListen 핸들이 나타내는 소켓을 닫습니다.

소켓을 닫으면 소켓과 관련된 모든 리소스가 해제되고 소켓과 관련된 모든 연결이 종료됩니다. 소켓이 닫히면 더 이상 통신에 사용할 수 없습니다.

 

Server - Full 코드

#include <stdio.h>
#include <WinSock2.h>

#pragma comment(lib, "ws2_32")

#define PORT 4578
#define PACKET_SIZE 1024
#define SERVER_IP "xxx.xxx.xxx.xxx"

int main()
{
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	SOCKET hSocket;
	hSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

	SOCKADDR_IN tAddr = {};
	tAddr.sin_family = AF_INET;
	tAddr.sin_port = htons(PORT);
	tAddr.sin_addr.s_addr = inet_addr(SERVER_IP);

	connect(hSocket, (SOCKADDR*)&tAddr, sizeof(tAddr));
	
	char cMsg[] = "Clinet Send";
	send(hSocket, cMsg, strlen(cMsg), 0);

	char cBuffer[PACKET_SIZE] = {};
	recv(hSocket, cBuffer, PACKET_SIZE, 0);
	printf("Recv Msg : %s\n", cBuffer);

	closesocket(hSocket);

	WSACleanup();
	return 0;
}

 

 

 

TCP/IP 네트워크 구성 클라이언트(client) 기초 지식 → It's like taking a restaurant order

 

TCP/IP 클라이언트는 다음과 같은 방식으로 레스토랑의 고객과 비교할 수 있습니다.

Socket creation : 이것은 고객이 식당에 들어와 테이블에 앉는 것과 같습니다. 고객(클라이언트)은 주문을 하기 위해 "테이블"(소켓)을 생성해야 합니다.

Connecting to the server : 이것은 고객이 웨이터에게 주문하는 것과 같습니다. 고객(클라이언트)은 필요한 서비스를 요청하기 위해 서버(웨이터)에 "연결"해야 합니다.

Sending requests : 고객이 웨이터에게 주문하는 것과 같습니다. 고객(클라이언트)은 요청(주문)을 서버(웨이터)에게 "전송"해야 합니다.

Receiving data : 고객이 웨이터로부터 음식을 받는 것과 같습니다. 고객(클라이언트)은 서버(웨이터)로부터 데이터(음식)를 "받아야" 합니다.

Closing the connection : 고객이 식사를 마치고 식당을 떠나는 것과 같습니다. 고객(클라이언트)은 서버(웨이터)로부터 데이터(식사)를 받은 후 연결(테이블)을 "닫아야" 합니다.

TCP/IP 클라이언트를 레스토랑의 고객과 비교하면 서버에서 데이터를 요청하고 수신하는 것과 관련된 단계를 더 쉽게 이해할 수 있습니다.

 

 

1. 클라이언트측 코드도 대부분 동일하다. 하지만 서버 IP를 지정해줘야한다.

클라이언트측 코드도 대부분 동일하다. 하지만 서버 IP를 지정해줘야한다.

우리는 이 컴퓨터 내에서 서버와 클라이언트 둘다 돌리기때문에, 해당 컴퓨터의 IP주소를 입력해준다.

 

실행 순서 : 명령 프롬프트(cmd) 실행 → ipconfig → IPv4 주소 확인

cmd 창을 통해 개인 ip를 check 후 visual studio에 입력

 

2. 클라이언트측 코드, 소켓 구성요소 구조체에 접속할 서버의 ip를 적어준다.

클라이언트측 코드, 소켓 구성요소 구조체에 접속할 서버의 ip를 적어준다.

 

클라이언트에서는 bind함수 대신 connect함수를 사용한다.

 

connect(hSocket, (SOCKADDR*)&tAddr, sizeof(tAddr))는 소켓을 원격 주소에 연결하는 함수 호출입니다. 연결 기능은 WinSock2.h 헤더 파일에 정의되어 있으며 소켓과 원격 주소 간의 연결을 설정하는 데 사용됩니다.

▷첫 번째 인수 hSocket은 원격 주소에 연결될 소켓에 대한 핸들입니다.

▷두 번째 인수인 (SOCKADDR*)&tAddr은 원격 주소 정보를 저장하는 SOCKADDR_IN 구조에 대한 포인터입니다. SOCKADDR_IN 구조는 WinSock2.h 헤더 파일에 정의된 일반 소켓 주소 구조인 SOCKADDR 포인터로 캐스트됩니다.

▷세 번째 인수인 sizeof(tAddr)는 SOCKADDR_IN 구조의 크기(바이트)입니다.

연결 함수는 성공 시 0을 반환하고 실패 시 0이 아닌 값을 반환합니다.

주어진 코드에서 connect 함수는 hSocket 핸들이 나타내는 소켓을 tAddr 구조에 지정된 원격 주소에 연결하기 위해 호출됩니다. connect 함수는 소켓과 원격 주소 사이의 연결을 설정하기 위해 적절한 인수와 함께 호출되며 함수의 반환 값은 이 코드 스니펫에서 확인되지 않습니다.

 

3. 성공적으로 서버측과 클라이언트측이 정보를 주고받았다. → 서버 측을 먼저 실행한 다음 클라이언트 측을 실행하는 것이 자연스럽다.

성공적으로 서버측과 클라이언트측이 정보를 주고받았다.

"서버 측을 먼저 실행한 다음 클라이언트 측을 실행하는 것이 자연스럽다"는 말은 소켓 응용 프로그램의 서버 측이 클라이언트 측보다 먼저 시작되어야 함을 의미합니다. 이는 클라이언트 측이 연결하기 전에 서버 측이 클라이언트로부터 들어오는 연결을 수신 대기해야 하기 때문입니다.

Visual Studio 2017과 관련하여 이 버전을 사용하는 경우 진행하기 전에 프로젝트 속성 창에서 SDL 검사를 해제해야 합니다. 이전 버전의 Visual Studio에서는 프로젝트를 만들 때 SDL 검사를 해제하는 것이 좋습니다.

SDL(Security Development Lifecycle) 검사는 보안 소프트웨어 개발을 위해 Microsoft에서 권장하는 보안 평가 프로세스입니다. SDL 검사를 해제하면 개발 프로세스에서 특정 보안 검사가 비활성화됩니다. 그러나 SDL 확인을 비활성화하는 것은 신중하게 고려한 후에 개발 프로세스에 필요한 경우에만 수행해야 한다는 점에 유의해야 합니다.

 

Client - Full 코드

#include <stdio.h>
#include <WinSock2.h>

#pragma comment(lib, "ws2_32")

#define PORT 4578
#define PACKET_SIZE 1024
#define SERVER_IP "xxx.xxx.xxx.xxx"

int main()
{
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	SOCKET hSocket;
	hSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

	SOCKADDR_IN tAddr = {};
	tAddr.sin_family = AF_INET;
	tAddr.sin_port = htons(PORT);
	tAddr.sin_addr.s_addr = inet_addr(SERVER_IP);

	connect(hSocket, (SOCKADDR*)&tAddr, sizeof(tAddr));
	
	char cMsg[] = "Clinet Send";
	send(hSocket, cMsg, strlen(cMsg), 0);

	char cBuffer[PACKET_SIZE] = {};
	recv(hSocket, cBuffer, PACKET_SIZE, 0);
	printf("Recv Msg : %s\n", cBuffer);

	closesocket(hSocket);

	WSACleanup();
	return 0;
}

 

댓글