• Multi Process vs. Multi Thread
    - Multi Process Model
     ■ 장점
      : 독립된 주소공간을 갖고 있기 때문에 안정적이다.
     ■ 단점
      : Context Switching 비용이 높다.
      : 데이터를 공유하기 위해 작업이 필요하다.

    - Multi Thread Model

     ■ 장점
      : Context Switching 비용이 낮다.
      : 주소를 공유하기 때문에 자원공유가 좋다.
     ■ 단점
      : 하나의 Thread가 문제가 발생할 경우 전체 Process가 죽을 수 있다.

    : 다중 스레드와 다중 프로세스가 전체 시스템의 성능에 영향을 미치는 것은 아니지만 어느정도 영향이 있다. (진짜로 영향이 있는 것은 엔진 문제)
    : Linux의 경우 fork 모델이 잘 되어 있어서 다중 프로세스 생성 및 관리가 편하지만 Windows의 경우 fork 모델이 적용되어있지 않아 멀티 스레드를 선호한다.

  • C#, C++, JAVA 서버
    : 구현 방법이 언어가 다를 뿐 거의 차이가 없지만, 그 언어의 철학을 이해할 필요가 있다.
    ex)
    이벤트 처리를 할 때 이벤트 핸들러를 전달할 때 차이
    C++, C: 함수 포인터
    C# : 델리게이트
    JAVA: Listener 인터페이스 구현

    - 왜 게임 Server는 C++을 주로 사용하여 만드는가? 
     : java의 경우 nio를 통해 C++과의 io 접근 속도 차이를 많이 줄였다. 조금은 C++이 빠르겠지만, 성능 결정에 큰 요소가 아니다. 그럼
    도 C++을 사용하는 이유는? GC(가비지 컬렉터) 때문이다. 서버는 객체가 생성,소멸이 빈번하게 일어날 수 밖에 없다. C++ 경우는 사용자가 메모리 해지하고 관리할 수 있지만, C#, JAVA의 언어의 경우 GC에 의한 메모리 관리로 성능저하가 발생할 수 있다.

    - C로 Server? C++ Server 만 답인가? 
     : C 같은 경우는 코드의 양이 많아져서 관리하기가 힘들다. C++은 문법의 양이 너무 많고, 계속 나오고 있다. 그러므로 꼭 C++를 사용하여 서버를 구현하라는 법은 없으며 서버의 특성에 따라 유동적으로 언어를 선택하여 구현하면 된다.


  • 구조체 정렬
      : CPU가 데이터를 처리하는 방식에 효율적인 얼라이먼트를 사용
      : 얼라이먼트를 1byte로 변경?
      -> 리눅스는 각각의 구조체마다 __attribute__((packed))
      -> 윈도우즈는 #pragma pack(push, 1)

    #include <stdio.h>
    
    // 구조체 정렬 : 내부의 필드 중 가장 큰 크기에 기반하여 정렬한다.
    struct AAA {
      int a;
      char b;
    };
    // 5byte가 아닌 8byte로
    // 본래 시스템에 맞게 5byte가 아닌 8byte로 패딩이 채워져 나와야 하지만
    // 네트워크 개념에서 빈 패딩은 오버패킷을 발생하므로 packed를 채워 5바이트로 만들수 있다.
    
    struct AAA {
      int a;
      char b;
    } __attribute__((packed));
    // 5byte
    
    #if 0
    // Windows
    struct BBB {
      int a;
      char b;
      double c;
    };
    // 16byte
    
    #pragma pack(push, 1)
    struct BBB {
      int a;
      char b;
      double c;
    };
    #pragma pack(pop)
    // 13byte
    
    #endif
    
    int main()
    {
      printf("sizeof(AAA) : %ld\n", sizeof(struct AAA));
    }


  • Little Endian vs. Big Endian
    : network byte order는 항상 고려해야한다.
    -> Network 에서는 Big Endian을 사용하기로 규약하였다. 그렇기 때문에 Little Endian을 사용하는 시스템(리눅스)의 경우 Big Endian으로 변환해야한다.
         

    #include <stdio.h>
    // 엔디안(endian) : CPU가 데이터를 메모리에 저장하는 방식
    //                  CPU가 데이터를 해석하는 방식
    
    // 1) Big Endian: 상위 바이트의 값을 하위 번지수에 저장
    // 2) Little Endian: 하위 바이트의 값을 하위 번지수에 저장
    
    // socket API
    // htons(short)
    // htonl(long)
    
    /* int int32ToBigEndian(int n) {
      return ((n & 0xff000000) >> 24) |
             ((n & 0xff0000) >> 8)    |
             ((n & 0xff00)   << 8)    |
             ((n & 0xff)     << 24);
    }
    */
    
    int int32ToBigEndian(unsigned int n) {
      return ((n & 0xff000000) >> 24) |
             ((n & 0xff0000) >> 8)    |
             ((n & 0xff00)   << 8)    |
             ((n & 0xff)     << 24);
    }
    // 16진수 표현: 0x
    // 1byte: 0x00 ~ 0xff
    // 2byte: 0x0000 ~ 0xffff
    //        0x00 0x00
    
    // n = 0x12345678 
    //    = (0001 0010) (0011 0100) (0101 0110) (0111 1000)  
    // 0xff000000 
    //    = (1111 1111) (0000 0000) (0000 0000) (0000 0000)
    // n & 0xff000000 
    //    = (0001 0010) (0000 0000) (0000 0000) (0000 0000)
    //    = 0x12
    // 0x12 >> 24
    //    = (0000 0000) (0000 0000) (0000 0000) (0001 0010)
    //    = 0x00000012
    
    void printIntByteOrder(int n) {
      char *p = (char*)&n;
    
      for (int i = 0 ; i < sizeof(n) ; ++i) {
        printf("%x ", p[i]);
      }
      printf("\n");
    }
    
    int main()
    {
      int ip = 0x12345678;
      printf("%x\n", ip);
    
      printIntByteOrder(ip);
      ip = int32ToBigEndian(ip);
    
      printIntByteOrder(ip);
    }
    
    
    - int32ToBigEndian의 argument가 unsigned인 이유?
     : logic shift가 이루어 져야 하기 때문에... 
     -> arithmatic shift - 앞의 부호 비트가 채워짐. vs. logic shift - 앞이 무조건 0으로 채워짐. 
     + 윈도우와 리눅스가 long을 처리하는 방식이 다르다 (64 비트 기준, linux의 long: 8byte, windows의 long 4byte) -> 그렇기 때문에 기본적으로 4byte를 기준으로 하고 때문에 int형을 사용한다.
     : java의 char은 unsigned 
     -> arithmatic shift - >>, logic shift - >>>

    - htons(), htonl() / ntohs(), ntohl()
     : htons(host to networks) 와 같은 함수가 내부적으로 조건부 컴파일이 되어있기 때문에 little endian일 경우 big endian으로 바뀌지만 big endian의 경우 아무것도 수행하지 않는다.

    // 1. 데이터를 네트워크로 보낼 때
    //  htons, htonl
    
    // 2. 네트워크 데이터를 읽을 때
    //  ntohs, ntohl
    
    #include <arpa/inet.h> 
    #include <stdio.h>
    
    template <typename T>
    void printIntByteOrder(T n) {
      char *p = (char*)&n;
    
      for (int i = 0 ; i < sizeof(n) ; ++i) {
        printf("%x ", p[i]);
      }
      printf("\n");
    }
    
    int main()
    {
      short s = 0x1234;
      s = htons(s);
      s = ntohs(s);
      printIntByteOrder(s);
    
      int n = 0x12345678;
      n = htonl(n);
      n = ntohl(n);
    
      printIntByteOrder(n);
    }


  • OSI 7 Layer Model vs. BSD TCP/IP Protocol Suite 

    OSI(Open Systems Interconnection Reference Model)
     7 Layer는 사용되고 있지 않다. 실질적인 구현은 생각하지 않고 개념적으로 설계되어, 현실적으로 개방형 시스템은 여러개의 시스템에 적용되기 때문에 공통으로 구현하기 힘들고 그에 따른 비용이 증가한다.

    : BSD 에서 만든 TCP suite의 경우도 마찬가지로 학술적인 목적으로 나왔지만, 실질적인 모델을 기반으로 개발을 하고 구축하였기 때문에 사실상 표준이 되었다.

    - Ethernet
     : C:\> ipconfig /all
           0A-00-27    00-00-00
           회사명      고유번호
     : Ethernet의 mac address 를 보면 00-00-00-00-00-00과 같이 6byte인데 이중 vendor 3byte, unique no 3byte인데 이는 요즘 추세로 볼 때 너무 수가 적다. 이를 해결 하고자 로컬 네트워크내에서만 mac address가 겹치지 않으면 되므로 임의적으로 공유기에서 mac address를 바꾸는 식으로 늘릴 수 있다.

      ■ header

    #define ETH_ALEN    6
    #define ETH_HLEN    14 
    
    struct ethhdr {
        unsigned char   h_dest[ETH_ALEN];   /* destination eth addr */
        unsigned char   h_source[ETH_ALEN]; /* source ether addr    */
        __be16      h_proto;        /* packet type ID field, 어떤 상위 프로토콜로 갈지에 대한 방향성 */
    } __attribute__((packed));
    
    # ifconfig
    HWaddr 08:00:27:1f:2d:6c

     

      ■ CSMA (Carrier Sense Multiple Access) / CD (Collision Detect) 방식
       : 브로드캐스트 방식 (모두에게 보내고 자신것이 아니면 버림)

        


    - ARP (Address Resolution Protocol)

     : 이더넷의 상위 프로토콜
     : IP주소와 하드웨어 주소 매핑

     : ARP는 캐시 되어있다.

     : arp 명령어 존재 arp -a, arp -d로 제거 가능, 관리자 권한 필요
       
    - IP (Internet Protocol)
     : 4byte 얼라이먼트 기반으로 작동
     : ip는 host, port는 프로세스 구분
     fragmetation 왜 발생? 라우터마다 MTU가 다르므로... ip는 offset 기반으로 쪼개진다. 이때, id 필요
     : MTU(max transmission unity) 최대 전송 단위가 쪼개질 때 fragment와 ipheader의 id를 기준으로 다시 조합해야 되는데 시퀀스를 통해 조합한다.

     

     

     

     


      ■ header

    struct iphdr {
        __u8    ihl:4,         // 헤더의 길이( 옵션 체크 때문에 존재 ) : 4byte정렬
            version:4;         // version 정보 : ipv4 => 4 , ipv6 => 6
        __u8    tos;           // 현재는 사용하지 않는다. 
        __be16  tot_len;       // 패킷전체길이( 유효 데이터 길이 때문에 존재 ) : 60 
        __be16  id;            // 패킷의 고유번호 ( 조각난 패킷을 조립 할때 필요 )
        __be16  frag_off;      // 데이터의 떨어진 거리 ( 원본 데이터로 부터의 거리 )
                               // 단위가 : 8byte 
                               // MF : 1 중간 패킷  MF=0 마지막 패킷 
        __u8    ttl;           // 잘못된 경로의 패킷의 자동 파괴 기능 때문에 존재
        __u8    protocol;      // 6 : TCP,   17 : UDP
        __sum16 check;         // 수신측에서 해당 데이터가 정확한지 알려줌.
        __be32  saddr;
        __be32  daddr;	
    };
    


    - ICMP (Internet Control Message Protocol)
     : ttl(time to live)은 같은 네트워크안에 존재하면 바뀌지 않음. 라우터(1홉)마다 1씩 감소하는 형태 
     -> ping www.google.com 확인 가능
     : ttl이 58이면? 64 - 58 = 4번의 라우터를 거쳤다.
     : tts는 현재 사용하지 않는다.
     
     

    - RIP (Routing Information Protocol)
     : 라우팅 정보, 홉의 정보
     : 인접한 라우터와의 router table을 교환함.
     

     

     

     ■ UDP Hole Punching
      : 서로 다른 망에서 사설 IP로 잡고 있을 때 서로 어떻게 데이터를 보낼 것인가?
      : A - 192.168.1.2

        B - 192.168.10.3
      : 다른 방법도 존재하지만 Real Time Server를 구축해야 할 경우 이 방법이 가장 효율적.
      : 많은 라우터에서 이 방법을 실제로 사용.( A와 B를 내부적인 라우팅 테이블 조작으로...)

  • TCP vs. UDP
     - TCP
      : udp에 비해 느림, 하지만 무시할만큼은 아니다 -> tcp의 성능을 간과하지마라
      : 신뢰성있는 통신을 보장, 데이터 에러 처리, 버퍼 overflow 방지, 순서 제어 등
      : 기본적으로 tcp는 데이터를 보내고 ack 신호를 기다리는 방식으로 동작한다.
      : 기존 연결에서 적당한 응답시간을 분석해서 그 시간의 2배 이상 동안 ack 신호가 돌아오지 않는다면, 재전송한다.
     

       three way hand shaking
       : Listen
       : TCP를 다시 말하면 read, write의 channel이 생성되는 것이다. -> sequence no를 동기화 하는 역할을 함.
       : clinet의 seq와 server의 seq를 서로 ack_seq 전송을 통해 확인

       1. 최초에 클라이언트가 seq 번호 1000을 보내면 server 에서는 seq 1000 에 1 을 더해서 ack 로 보낸다. 이때, 자신의 seq 번호 2000 도 포함해서 보낸다.
       2. 그러면 클라이언트에서는 server 부터 넘어온 패킷의 ack가 자신의 seq 번호와 일치하는지 확인하고, 확인이 되면 server 의 seq 번호인 2000 에 1을 더해서 ack로 보낸다.
       3. server 에서는 client 가 보낸 ack 의 번호와 자신의 seq 번호가 일치하는지 확인해서 일치하면 연결이 제대로 되었다는것을 인증하고 데이터 통신에 들어가게 된다.


     

      ■ sliding window
       : 송신측과 수신측의 버퍼의 사이즈, 클라이언트의 IO가 느리면 오버플로우 발생 가능성이 있다.
       : 기본적으로 buffer가 한정되어 있기 때문에 window를 통해 제어한다. 만약 server측에서 window가 0으로 ack 신호가 온다면 client는 data를 보내지 않고 기다린다. 이 후 server측에서 window의 크기를 늘리는 ack 신호가 온다면 다시 data를 보낸다.

       
       
       



       four way hand shaking
       : connection을 끊을 때 사용
       : 비정상 종료로 연결상태가 남아있다면 타이머를 통해 처리한다
       1. 
    클라이언트가 연결을 종료하겠다는 FIN플래그를 전송한다.

       2. 서버는 일단 확인메시지를 보내고 자신의 통신이 끝날때까지 기다리는데 이것이 클라이언트에서는 TIME_WAIT, 서버에서는 CLOSE_WAIT 상태다.

       3. 서버가 통신이 끝났으면 연결이 종료되었다고 클라이언트에게 FIN플래그를 전송한다.

       4. 클라이언트는 확인했다는 메시지를 보낸다.
       
       
       

    ∴ Nagle 알고리즘
     : 상대방이 받을 수 있는 사이즈(window size)와 내가 보낼 데이터가 MSS보다 크다면 문제없이 바로 전송한다. 
     : 더 이상 SEND 처리할 데이터가 없다면 현재 있는것 그대로 보낸다.

     : 둘다 아니라면, 아래의 그림 (Nagle On) 처럼 동작한다.
     
      : Nagle은 이런 작은 패킷을 가능한 지연시키기 위하여, ACK가 올때까지 전송을 중지하고 ACK가 도착한 시점에, 지금까지 버퍼에 모인 데이터를 패킷으로 만들어서 전송한다.  ACK를 기다리는 지연방식으로 네이글은 작은 패킷을 연속해서 보내는 네트워크의 비효율을 극복하였다. 하지만 이런 지연방식을 사용하면 위 그림처럼 데이터 전송속도가 더 늦다. 

      : 패스트핑 같은 프로그램은 이 알고리즘을 OFF 하는 것이다. 어떤 데이터를 보내도 ack신호를 받아야 다음 데이터를 전송할 수 있다. 그러나 보내는 데이터가 작고 빈번히 보내는 경우에는 nagle 알고리즘을 끊어 ack신호를 안보냄으로써 실시간성을 보장할 수 있다.
      : 소켓 통신은 기본적으로 이 옵션이 ON 되어 있지만 파라미터 옵션을 통해 OFF 할 수 있다.


     - UDP
      : connection missing...
      : 데이터 유실 가능성과 비순서대로 들어옴
      : udp의 대부분의 문제점은 connection missing에서 발생하기 때문에 항상 connect를 확보하고 보냄으로써 문제점을 해결할 수 있음
      -> RUDP


+ Recent posts