#include <stdio.h>
#include <winsock2.h>
#include <windows.h>

#pragma comment(lib, "ws2_32")

//#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define BUFSIZE 1024

int main()
{
    /* WSAStartup 함수의 인자로 들어가는 변수입니다. */
    WSADATA wsaData;
    /* socket 함수의 반환값을 저장할 UINT_PTR 변수입니다. */
    SOCKET client;
    /* SOCKADDR_IN https://docs.microsoft.com/ko-kr/windows/win32/api/winsock/ns-winsock-sockaddr_in
     * 소켓의 주소를 담는 기본 구조체 역할을 합니다.
     * 선택한 프로토콜에 따라 구조가 달라지며, 네트워크 바이트 순서대로 표현됩니다.
     * 2바이트의 family 부분과 14바이트의 data부분으로 나눠집니다.
     */
    SOCKADDR_IN server_addr;
    /* 보낼 문자열과 받은 문자열을 저장할 변수입니다. */
    char buffer[BUFSIZE];
    int size;

    /* WSAStartup https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup
     * WSACleanup 함수와 같이 사용되며 소켓 프로그램의 시작을 나타냅니다.
     * 
     * 첫번째 인자에는 winsock 버전을 넣습니다. 
     * 상위 2바이트에는 주 버전 번호, 하위 2바이트에는 부 버전 번호를 넣습니다.
     * 2.2버전을 사용할려면 0x0202 또는 MAKEWORD(2,2)를 사용하면 됩니다.
     * 
     * 두번째 인자에는 WSADATA 구조체의 포인터를 넣습니다.
     * 이 포인터는 윈도우 소켓 구현에 대한 세부정보를 받는 WSADATA 타입 구조체의 포인터입니다.
     * 
     * 성공하면 0을 반환하고, 실패하면 나열된 오류 코드중 하나를 반환합니다.
     * 오류코드를 반환하기 때문에 WSAGetLastError 함수의 호출은 필요하지 않으며, 사용하면 안됩니다.
     */
    WSAStartup(0x0202, &wsaData);

    /* socket https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket
     * 소켓을 만듭니다.
     * 
     * 첫번째 인자에서는 주소 계열을 지정해줍니다.
     * AF_* 상수와 PF_* 상수의 값이 동일하기 때문에 원하는 상수를 쓰면 됩니다.
     * 
     * 두번째 인자에서는 소켓의 유형을 지정해줍니다.
     * 
     * 세번째 인자에서는 소켓의 프로토콜을 지정해줍니다.
     * 값을 0으로 지정하면 프로토콜을 지정하지 않고 서버가 사용할 프로토콜을 선택합니다.
     * 
     * 오류가 발생하지 않으면 새 소켓을 참조하는 descriptor 를 반환합니다.
     * 그렇지 않으면 INVALID_SOCKET 값이 반환되고 WSAGetLastError를 호출하여 오류코드를 반환받을 수 있습니다.
     */
    client = socket(AF_INET, SOCK_STREAM, 0);
    if (client == INVALID_SOCKET) { //에러 핸들링
        printf("socket error, error code : %d", WSAGetLastError());
        system("pause");
        return 1;
    }
    printf("socket descriptor : %d\n", client);

    // server_addr 변수를 0으로 초기화 해줍니다.
    memset(&server_addr, 0, sizeof(server_addr));
    // 주소 체계를 AF_INET(IPv4)로 지정합니다.
    server_addr.sin_family = AF_INET;
    // 주소를 152.70.238.188로 지정합니다.
    server_addr.sin_addr.S_un.S_addr = inet_addr("152.70.238.188");
    /* 포트를 3000으로 지정해주는데,
     * htons 함수는 2바이트 데이터를 네트워크 바이트 정렬 방식으로 변경합니다.
     */
    server_addr.sin_port = htons(3000);

    /* connect https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect
     * SOCKET 변수와 주소 변수를 받아서 소켓을 연결합니다.
     * 
     * 첫번째 인자는 연결되지 않은 소켓의 descriptor을 넣습니다.
     * 두번째 인자는 SOCKADDR_IN 변수를 sockaddr 구조체의 포인터로 변경하여 넣어줍니다.
     * 세번째 인자는 sockaddr 구조체의 크기를 넣어줍니다.
     * 
     * 오류가 발생하지 않으면 0을 반환하고, 아니면 SOCKET_ERROR를 반환합니다.
     * socket 함수와 같이 WSAGetLastError 를 통해서 오류코드를 반환받을 수 있습니다.
     * 비 블로킹 소켓(비동기)을 사용하면 SOCKET_ERROR를 반환하고,
     * WSAGetLastError에서 WSAEWOULDBLOCK를 반환합니다.
     */
    int con = connect(client, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if (con == SOCKET_ERROR) {
        printf("connect error, error code : %d\n", WSAGetLastError());
        system("pause");
        return 1;
    }

    // 연결이 성공됨을 출력합니다.
    printf("----------------------connect success----------------------\n\n");
    
    // buffer를 비웁니다.
    memset(buffer, 0, BUFSIZE);
    // buffer에 "10215" 문자열을 복사합니다.
    strcpy(buffer, "10215");
    // 소켓에 전송합니다.
    /* send https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-send
     * 연결된 소켓에 데이터를 전송합니다.
     * 
     * 첫번째 인자엔 연결된 소켓의 descriptor가 들어갑니다.
     * 두번째 인자엔 전송할 데이터를 포함하는 버퍼의 포인터가 들어갑니다.
     * 세번째 인자엔 버퍼의 크기가 들어갑니다.
     * 네번째 인자엔 플래그가 들어갑니다.
     * 
     * MSG_DONTROUTE, MSG_OOB 가 비트연산자를 통해 들어갑니다.(0은 지정 안함)
     * 오류가 없으면 전송된 총 바이트 수를 반환하고, 그렇지 않으면 SOCKET_ERROR 값이 반환됩니다.
     * WSAGetLastError를 통해 오류코드를 반환받을 수 있습니다.
     */
    size = send(client, buffer, BUFSIZE, 0);
    if (size == SOCKET_ERROR){
        printf("send error, error code : %d\n", WSAGetLastError());
        system("pause");
        return 1;
    }
    // 전송한 메세지를 출력합니다.
    printf("sent message: \"%s\"\n", buffer);

    // 버퍼를 0으로 비웁니다.
    memset(buffer, 0, BUFSIZE);
    /* recv https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-recv
     * 연결된 소켓에서 데이터를 수신합니다.
     * 
     * 첫번째 인자엔 연결된 소켓의 descriptor가 들어갑니다.
     * 두번째 인자엔 수신할 데이터를 받을 버퍼의 포인터가 들어갑니다.
     * 세번째 인자엔 버퍼의 크기가 들어갑니다.
     * 네번째 인자엔 플래그가 들어갑니다.
     * 
     * MSG_PEEK, MSG_OOB, MSG_WAITALL가 비트연산자를 통해 들어갑니다.(0은 지정 안함)
     * 오류가 없으면 수신된 총 바이트 수를 반환하고, 그렇지 않으면 SOCKET_ERROR 값이 반환됩니다.
     * WSAGetLastError를 통해 오류코드를 반환받을 수 있습니다.
     */
    size = recv(client, buffer, BUFSIZE, 0);
    if (size == SOCKET_ERROR){
        printf("receive error, error code : %d\n", WSAGetLastError());
        system("pause");
        return 1;
    }
    // 수신한 메세지를 출력합니다.
    printf("received message: \"%s\"\n", buffer);

    /* WSACleanup https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsacleanup
     * WSAStartup 함수와 같이 사용되며 소켓 프로그램의 끝을 나타냅니다.
     * 
     * 작업에 성공하면 0을 반환하고, 그렇지 않으면 SOCKET_ERROR 값을 반환합니다.
     * WSAGetLastError를 통해 오류코드를 반환받을 수 있습니다.
     */
    if (WSACleanup() == SOCKET_ERROR){
        printf("socket terminate error, error code : %d\n", WSAGetLastError());
    }

    system("pause");
}

client는 주석에서 설명을 거의 다 해놨기 때문에, server 부분만 약간 설명을 하겠습니다.

bind()

https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind

소켓에 주소를 연결합니다.

첫번째 인자에는 소켓을 받고, 두번째 인자에는 주소 구조체의 포인터, 세번째 인자는 주소 구조체의 크기를 받습니다.

listen()

https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen

소켓 수신을 시작합니다.

첫번째 인자에는 소켓을 받고, 두번째 인자에는 보류중인 연결의 최대 큐 길이를 받습니다.

accept()

https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-accept

대기열의 맨 앞 소켓 하나를 연결 허용합니다.

첫번째 인자에는 서버 소켓을 받고, 두번째 인자에는 클라이언트 소켓을 저장할 포인터를 받습니다. 세번째 인자는 클라이언트 소켓을 저장할 변수의 크기를 받습니다.

서버에서 listen 함수로 큐에 대기열을 받으면

accept 에서 맨 앞 하나씩 가져와서 통신합니다.

728x90

+ Recent posts