C++ 심층 네트워크 프로그래밍

2022. 12. 13. 19:32프로그래밍

728x90

https://www.codeproject.com/Tips/5345359/In-Depth-Network-Programming-in-Cplusplus

 

In-Depth Network Programming in C++

Network programming in C++

www.codeproject.com

 

C++ 네트워크 프로그래밍

 

우리는 기본 및 시스템 작동을 위한 TCP(전송 제어 프로토콜) 및 UDP(사용자 데이터그램 프로토콜)에서 작동하는 두 개의 네트워킹 또는 유틸리티 프로그램 예를 들어서 시스템 기초에 대해서 생각해 보는 시간을 마련해 보았습니다.

 

소개

이 기사에서는 버전 2.0용 winsock2.h 파일에 정의된 소켓 작업에 사용되는 기본 프리미티브를 소개합니다.

 

또한 다양한 네트워킹 작업들을 해결하는 두 가지 유용한 유틸리티 프로그램을 소개 할 것입니다.

물론, 비동기 프로그래밍을 위해 MFC(Microsoft Foundation Classes)를 사용할 수도 있지만 우리의 접근 방식은 시스템 네트워크 프로그래밍에 더 적합하도록 만들었습니다.

따라서 본 기사에서 제시하는 CPing 프로그램은 사용자 정의 프로토콜을 이용한 Ping으로 연결 속도를 측정하는 유틸리티를 소개 합니다.

또 다른 예 인, CSocks은 IRC(Internet Relay Chat)와 같은 메신저용 SOCKS-5 프로토콜 상에서 프록시를 위한 터널링 알고리즘을 소개하려고 합니다.

 

배경

이 기사는 ICMP(Internet Control Message Protocl)와 같은 시스템 수준(레벨)과 프로토콜에서 동작하는 " ping" 유틸리티와 동일하기 보다는 사용자 레벨 그 이상에서 동작 하도록 SOCKS-4/5 인터넷 TCP 프로토콜에 대한 명령 순서를 사용하는 CSocks 프로젝트를 위한 Request For Comment (RFC) 표준에 의거 한 연구 결과에 대한 글 입니다.

 

따라서 HTTP(HyperText Transfer Protocol) 및 사용자 정의 수준에서 동작하는 기타 기능을 테스트하기 위해 동일한 ping 유틸리티를 CPing 프로젝트에 도입합니다.

 

코드 사용해 보기

소켓을 사용한 네트워크 프로그래밍을 위한 프로그래밍 기술에 대한 설명을 진행하기 전에 Winsock API의 몇가지 함수에 대해 설명하도록 하겠습니다:

  • socket() - 소켓 엔터티 생성;
  • send() - 데이터 패킷을 소켓의 끝점으로 보냅니다.
  • recv() - 동일한 끝점에서 데이터 패킷을 수신합니다.
  • setsockopt() - 타임아웃 등과 같은 소켓 옵션을 설정합니다.
  • connect() - 네트워킹 프로그램이 작동하는 호스트 시스템과 다른 소켓의 끝점에 연결합니다.
  • select() - 끝점에서 작업이 수행된 현재 활성 소켓을 선택합니다.

 

그럼 CPing 유틸리티에서 다음과 같이 소켓의 라이브러리 초기화를 진행해 보겠습니다:

#include <windows.h>
#include <winsock2.h> // necessary
#include "CPing.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>


CPing::CPing()
{
    iTimeOut = 1000;
    iBytesToRecv = 0;
    iLastError = WSAStartup (MAKEWORD(2,0),&wsadata); // initialize Windows Sockets
    strcpy (szNoop, NOOP);
}


CPing::~CPing()
{
    WSACleanup(); // clean up the Windows Sockets cache
}
 

 

클래스 CPing은 다음과 같이 정의됩니다.

#ifndef __CPING_CLASS
#define __CPING_CLASS

#define MAX_SENDS 1111111
#define NOOP "NOOP"

typedef struct {
    unsigned int iTimeSend; // measured time to send TCP packet
    unsigned int iTimeRecv; // measured time to receive TCP packet
    unsigned int iTotalSent, iTotalRecvd; // total number of bytes sent and received
    unsigned int iPort; // TCP protocol port
} pingstore;


class CPing
{
public:
    CPing();
    ~CPing();
    char szNoop[256];
    int iLastError, iTotalRes, iBytesToRecv;
    unsigned int iTimeOut, iTotalSent, iTotalRecvd;
    unsigned int PingContinuous (char* szHost, unsigned int iPort, unsigned int iPackets);
    unsigned int PingConnective (char* szHost, unsigned int iPort, unsigned int iPackets);
    pingstore* Res;
private:
    WSADATA wsadata;
};

#endif
 

좀 더 명확하게 하기 위해서, 다음과 같이 CPing 유틸리티에서 Windows 소켓을 지원하는 객체 지향 프로그래밍의 사용 예를 소개 해야만 하겠군요:

unsigned int CPing::PingContinuous (char* szHost, unsigned int iPort, unsigned int iPackets)
{
    struct hostent* host = NULL;
    struct sockaddr_in saddr;
    unsigned int s = 0;
    unsigned int dw1, dw2, dw3;
    char szBuffer[256];

    if (!iBytesToRecv) iBytesToRecv = strlen(szNoop);

    if (iPackets>MAX_SENDS) return (0);
    free (Res);
    Res = (pingstore*)malloc (sizeof(pingstore)*iPackets);
    memset (Res, 0, sizeof(pingstore)*iPackets);

    s = socket(AF_INET, SOCK_STREAM, 0);
    if (!s) return (0);
    setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char*)&iTimeOut, sizeof(iTimeOut));
    setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (char*)&iTimeOut, sizeof(iTimeOut));

    host = gethostbyname (szHost);
    if (host==NULL) return (0);
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(iPort);
    saddr.sin_addr = *((struct in_addr*)host->h_addr);

    if (connect (s,(struct sockaddr*)&saddr, sizeof(saddr)) == -1) return (0);    
    for (int i=0;i<iPackets;i++)
    {
        iTotalRes++;
        sprintf (szBuffer, "%s\r\n", szNoop);
        dw1 = GetTickCount();
        int iSent = send (s, szBuffer, strlen(szBuffer), 0);
        dw2 = GetTickCount();
        int iRecv = recv (s, szBuffer, iBytesToRecv, 0);
        dw3 = GetTickCount();
        Res[i].iPort = iPort;
        Res[i].iTimeSend = dw2-dw1;
        Res[i].iTimeRecv = dw3-dw2;
        Res[i].iTotalSent = ((iSent==SOCKET_ERROR)?0:iSent);
        Res[i].iTotalRecvd = ((iRecv==SOCKET_ERROR)?0:iRecv);
        if (iRecv<=0)
        {
            closesocket (s);
            return ((iTotalRes)?1:0);
        }
    }
    closesocket (s);
    return (1);
}
 

SOCKS-5 프로토콜에서 프록싱하기 위한 CSocks 네트워크 유틸리티와 같은 또 다른 예는 다음과 같이 정의됩니다:

#ifndef __CSOCKS_HEADER
#define __CSOCKS_HEADER

#include "common.h"
#include "cauth.h"

class CSocks {
private:
    
public:
    PSOCKSNOTIFYPROC pNotifyProc;
    u_short uPort, mode;
    u_int uAccept;
    u_long LastError;
    CSocksBasicAuth* basicauth = NULL;

    CSocks() {
        uAccept = 0;
        mode = SOCKS_MODE_TCP;
        uPort = SOCKS_DEF_PORT;
        LastError = SOCKS_ERROR_NONE;
        pNotifyProc = NULL;
    };

    ~CSocks() {
        if(uAccept) closesock(uAccept);
    };

    bool DNSAddrLookup(char* sHost, struct in_addr* addr);
    bool StartChaining();
    bool SocksTCPTunneling(u_int sres, u_int sdest);
    bool SocksUDPTunneling(void* sadest, char* sData, u_int len);
    bool PrepareListening();
    char* GetLastError();
    virtual bool ForceConnection(u_int sock) = 0;
};

#endif
 

우리는 네트워크 데이터가 매개 소켓을 통과할 때 터널링과 같은 네트워크 작업의 구현 예를 위해 이 클래스를 소개 합니다. 이 개념은 주로 프록시 소프트웨어에서 사용되며 이 작업은 다음과 같이 정의됩니다(sres 및 sdest는 소켓 사이에서 터널링 작업을 수행 한다):

bool CSocks::SocksTCPTunneling(u_int sres, u_int sdest)
{
    register u_int sockr, sockw, ret;
    register u_int uread, uwrote;
    char szBuffer[1024];
    struct fd_set fd;
    struct timeval tv = {0,0};
    
    do
    {
        FD_ZERO(&fd);
        FD_SET(sres,&fd);
        FD_SET(sdest,&fd);

        
        if((ret = select(0,&fd,NULL,NULL,&tv))>0 && VALID_SOCKET(ret))
        {
            if(FD_ISSET(sres,&fd))
            {
                sockr = sres;
                sockw = sdest;
            }
            else
            {
                sockr = sdest;
                sockw = sres;
            }

            uread = recv(sockr,szBuffer,1023,0);
            
            if (uread >= 1023) break;
            
            szBuffer[uread] = 0;
            uwrote = 0;
            if(!VALID_SOCKET(uread) || uread==0) break;

            while(uwrote<uread)
            {
                ret = send(sockw,szBuffer+uwrote,uread-uwrote,0);
                if(!VALID_SOCKET(ret)) goto __quit;
                uwrote += ret;
            }
        }

        FD_ZERO(&fd);
        FD_SET(sres,&fd);
        FD_SET(sdest,&fd);
        if(select(0,NULL,NULL,&fd,&tv)>0) break;
    } while(1);
    
__quit:
    return(true);
}
 
 

지금은 이게 전부 겠군요. 여기에서 좀 다른 사소한 프로시져들과 함수들에 대해서 연구할 수 있겠지만 위의 모든 내용은 시스템 수준에서 C++ 프로그래밍 언어의 소켓으로 작업하기 위해 필요한 지식에 전부 일 수도 있습니다.

 

주요 관점들

따라서 우리는 더 나은 성능과 더 깊은 기능 및 평가를 위해 특정 네트워크 작업에 소켓 계층 모델을 채택하는 방법을 배웠습니다.

 

History

  • 27th October, 2022 - Initial release
  • 2nd November, 2022 - Removed links
  • 3rd November, 2022 - Added thumbnails
  • 4th November, 2022 - License changed

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

이상.

 

728x90