반응형

출처: 지식IN(edson)

http://cafe.naver.com/devctrl.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=3272




__attribute__는 GCC 컴파일러에 있는 메커니즘으로 컴파일러에게 추가적인 에러 체킹같은 것을 하도록 지시하는 것이다.

 

형식 : __attribute__ (x)

 

괄호 안의 x에는 가능한 몇 가지들이 있다. 특히, packed는 채우라는 의미이다.

 

typedef struct

{

    char a;

    int b;

} myST;

 

위 구조체의 sizeof(myST)는 8이다.

상식적으로, char =1byte, int = 4byte이므로, 5가 나와야 하는데.. 이런 문제때문에, 구조체를 가지고 작업할 경우 예상치 못한 버그로 고생을 가끔한다.

이런 현상이 생기는 이유는, 현재 사용하는 컴퓨터는 32비트이다. 즉, CPU가 메모리 어드레스를 지정할 때 4바이트(32비트/8) 단위가 내부적으로 가장 최적화되어 빠른 데이터 접근을 가능하게 한다. 4바이트 배수 단위가 아닌 경우 당연히 접근은 가능하지만 속도 차이가 있어, 디폴트로 저런 접근을 하도록 해 둔 것이다. 

 

그럼, 메모리에 저 구조체가 잡힐 경우 어떻게 존재하느냐는, 4바이트 단위로 채워지기 때문에, char a;는 1바이트이지만, 4바이트를 할당해서 거기다 char a;를 담아둔다. 다음, int b;는 4바이트므로 그냥 4바이트를 할당한다. 이렇게 해서 8바이트의 크기가 필요한 것이다. 

 

첫 char a;에서 1바이트를 제외한 나머지 3바이트는 어떤 걸로 채워질까? 의미없는 값이 채워진다. 그냥 공간만 예약되는 것이다.

그래서, 이런 현상을 방지할 수 있도록, 컴파일러는 __attribute__ ((packed)) 메커니즘을 두어서 우리가 예상한 1바이트+4바이트, 크기가 되도록 빌드한다. 

 

typedef struct

{

    char a;

    int b;

} __attribute__ ((packed)) myST;

 

결국, 위 처럼 사용하게되면, 컴파일러는 구조체 멤버 실제 크기 만큼 할당한다. 그래서 정확한 5바이트가 나오게된다. 물론, 속도는 약간 더 늦어진다. 

 

여기에서 __attribute__라는 의미는 , 컴파일러에게 특성을 주겠다는 것을 의미하며 괄호 안은 여러 가지가 가능한데, 여기서는 그 중 한가지인 구조체 정렬을 사용하겠다는 의미인 packed를 사용했다.

 

packed의 의미는 꽉 채우다라는 것인데,  이 기능을 사용하지 않았을 경우 char a;는 실제 1바이트 외에 나머지 3바이트가 자리만 차지한 채 남아있다. 이런 비어있는 것들을 제거해서 구조체 멤버들이 빈 자리없이 꽉 채우라는 단어 의미이다. 이런 것을 구조체 정렬(alignment)이라고 한다. 

 

위는 unix/linux의 GCC에서 사용하는 것이고 윈도우 상의 비주얼 씨의 경우는

#pragma pack(1)

처럼 사용한다. 괄호 안의 의미는 1바이트 단위까지 체크해서 빈 자리없도록 차곡차곡 채우라는 것이다. 4가 올수도 있겠지만, 그렇게 되면 가장 작은 단위가 4가되어 char 형 같은 경우 역시 4로 채워지게 되어 낭패이다. 따라서 확실한 경우 아니면, 위 처럼 사용하면 된다.

 


반응형

'도움받은 정보' 카테고리의 다른 글

숨김파일 표시 안될 때  (0) 2010.03.23
고속도로와 소방서  (0) 2010.03.19
윈도우XP 최적화 팁 모음  (0) 2009.07.28
Virtualbox could not create temporary directory.  (0) 2009.07.28
네로 서치 제거법  (0) 2009.07.28
반응형

9-1 소켓의 옵션

1. 소켓의 옵션 조작 : 소켓의 기본적인 특성(다양함)을 변경하는 것.

※ 운영체제에 의해 관리. 운영체제는 소켓의 특성에 대한 정보를 보관하고 있다.

2. 소켓의 기본적인 특성

 - 입력 및 출력 버퍼의 크기

 - 데이터 전송 방식(TCP 또는 UDP)

 - TTL(Time To Live) : 건너뛸 수 있는 라우터의 개수. hop과 관련됨.

3. 옵션은 대부분 변경(set) 가능하지만 참조(get)만 가능한 것도 있다.


소켓의 옵션 관련 함수

1. 현재 설정 상태 정보를 가져오는 함수.

#include <sys/types.h>

#include <sys/socket.h>

int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);

sock : 소켓을 나타내는 디스크립터

level : 확인할 옵션의 프로토콜 레벨

optname : 확인할 옵션의 이름

optval : 원하는 옵션의 이름의 값을 얻음.

optlen : optval 포인터가 가리키는 버퍼의 크기.


#include<winsock2.h>

int getsockopt(SOCKET s, int level, int optname, char FAR *optval, int FAR *optlen);

s : 소켓 핸들


2. 옵션을 변경하는 함수.

#include<sys/types.h>

#include<sys/socket.h>

int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);

sock : 소켓을 나타내는 디스크립터

level : 변경할 옵션의 프로토콜 레벨

optname : 변경할 옵션의 이름

optval : 원하는 옵션의 값을 저장한 포인터 전달.

optlen : optval 포인터가 가리키는 버퍼의 크기.


#include<winsock2.h>

int setsockopt(SOCKET s, int level, int optname, const char FAR * optval, int optlen);

s : 소켓 핸들


소켓의 옵션

Protocol Level(프로토콜 레벨) : 옵션이름을 그룹짓기 위해 필요.

SOL_SOCKET : 일반적인 소켓의 옵션.

IPPROTO_IP : IP에 대한 소켓의 옵션.

IPPROTO_TCP : TCP에 대한 소켓의 옵션


※ SO_TYPE 옵션 : 소켓의 타입 정보(TCP(1)/UDP(2))값을 얻는다. 다시 재설정할 수 없다.

윈도우즈용 프로그램


9-2 SO_SNDBUF & SO_RCVBUF

getsockopt함수를 통해서 디폴트 버퍼의 크기를 구할 수 있다.

※ 리눅스 시스템 마다 입력 버퍼, 출력 버퍼의 크기는 다르다.

내 컴퓨터 우분투의 경우

입력버퍼 : 87380

출력버퍼 : 16384


setsockopt함수를 통해서 버퍼의 크기를 변경할 수 있다.

※ 바꿀 수 있는 최소 버퍼의 크기는 정해져 있다

※ 버퍼가 없으면 TCP를 할 수 없기 때문에 0으로 설정해도 최소값으로 바뀌어서 버퍼의 크기가 설정된다. 성능과 직결되는 문제이다.


9-3. SO_REUSEADDR

TIME_WAIT 상태?

연결 종료시 마지막 패킷 전송 실패를 대비하기 위한 상태


종료를 먼저 요청한 호스트가 나중에 소켓을 소멸시킨다. 

마지막 ACK 신호가 잘 전달되지 않았을 때를 대비해서 TIMEOUT이 존재한다.

이유 : 상대방(B)의 종료 요청(FIN)을 기다리는 데 걸리는 TIMEOUT이다.


서버의 연결 종료

서버에서 먼저 종료 요청 후, 다시 서버를 가동시키면?

서버가 종료 메세지를 보내고 TIMEOUT 시간동안은 서버 재가동 불가. 

이유 : 그 아이피와 포트가 사용중이기 때문.


TIME_WAIT 타이머의 재시작

TIME-WAIT 상태는 우리의 생각보다 더 길어 질 수 있다.


마지막 ACK신호를 못 보낼 경우 다시 FIN을 보낼 때 다시 TIMEOUT을 설정해야 되기 때문이다.


※ TIMEOUT이 걸린 소켓이라고 그 주소와 포트에 다시 소켓을 만들 수 있게 설정한다.

server_socket = socket(PF_INET, SOCK_STREAM, 0);

option = sizeof(option);

option = TRUE;

setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));


9-4. TCP_NODELAY

네트워크 상의 패킷 수를 줄이기 위해 제안된 알고리즘

ACK을 수신해야만 다음 전송을 진행하는 알고리즘.

※ ACK이 올 때까지 보낼 내용은 버퍼에 쌓아 둔다. (전송버퍼가 필요한 이유)

예시) "agle"은 버퍼에 쌓여 있다가 보내진다.

※ nagle 알고리즘이 off면 데이터가 오면 무조건 상대방에게 보낸다.


Nagle 알고리즘의 장점과 단점

장점 : 네트워크의 효율성이 높아진다. (적은 패킷의 양)

단점 : 전송 속도가 느리다. (ACK 수신 후 패킷 전송)

생각해 볼 문제 : Nagle 알고리즘의 중단이 데이터 전송 속도를 무조건 향상 시켜주는 것은 아니다.

※ 네트워크 트래픽 양이 많다면 (차가 많으므로)속도가 꼭 빨라지지는 않을 수 있음.


TCP_NODELAY

serv_sock = socket(PF_INET, SOCK_STREAM, 0);

opt_val = TRUE;    // option 값이 TRUE면 nagle알고리즘 off 됨.

setsockopt(serv_sock, IPPROTO_TCP, TCP_NODELAY, &opt_val, sizeof(opt_val));

반응형
반응형

8-1. DNS(Domain Name System)

1) 도메인 이름이란? 

1. 영문으로 표현되는 계층적 주소 체계 방식.

※ IP 이후에 도메인이 나왔음. 중복되면 안 된다.

2. 각 나라마다 존재하는 Network Information Center에서 관리. => 약속

한국은 KRNIC(http://www.nic.or.kr, Korea Network Information Center)에서 담당.

3. 도메인 이름 = 호스트 이름 + 도메인 이름

예시) academy.freelec.co.kr = academy + freelec.co.kr

4. TCP/IP는 도메인 이름을 인식 못함.

※ IP와 도메인의 중계 역할을 하는 서버 : 도메인 서버


현실세계의 주소체계


인터넷 상에서의 주소체계

root 도메인

com : 회사, zw : 짐바브웨, kr : 한국

2차 도메인

co : 나라 안의 기업, ac : 나라 안의 대학, or : 행정기관, 


2) DNS 서버

1. 모든 도메인 이름은 해당 IP와 함께 DNS 서버에 등록되어야 한다.

※ DNS 서버는 테이블 형식으로 정보를 가지고 있다.

2. 도메임 이름을 IP 주소로 변환해 주는 작업을 한다.


1. host는 DNS 서버에 도메인 주소 질의

2. DNS 서버는 테이블 정보를 검색하여 IP를 답변해 준다.

※ 분산 데이터베이스 : 모르면 다른 DNS 서버에 물어본다.

3. host는 받은 IP정보를 통해 원하는 도메인 주소로 접속한다.


도메인 이름과 IP변환 과정

※ DNS 서버는 계층적 구조로 되어 있다. 모를 경우 부모에게 물어본다. 

루트 DNS는 모든 주소에 대한 정보를 어느 자식에 있는 지 알고 있다. (자손까지는 아님.)


8-2 IP 주소와 도메인 이름 사이의 변환

변환(도메인 이름 <-> IP주소)의 필요성

상대적으로 잦은 IP 변화에 대한 능동적인 문제 해결

※ IP를 하드코딩하면 IP주소가 바뀌었을 때는 접속할 수 없기 때문에 도메인 이름을 하드 코딩하여 IP가 바뀌더라도 도메인 이름으로 접속할 수 있도록 한다.

※ 한 도메인에 대해 IP를 여러 개 두고 상황에 따라 분산하여 접속할 수 있게 한다.


도메인 이름->IP주소 함수

※ 함수 자체가 DNS 서버에게 물어보고 IP주소등의 정보가 리턴된다.


#include<netdb.h>

struct hostent* gethostbyname(const char* name);

name : 도메인 이름 문자열


#include<winsock2.h>

struct hostent FAR *gethostbyname(const char FAR *name);

name : 도메인 이름 문자열


struct hostent 구조체 


struct hostent

{

char *h_name;

char **h_aliases;

int h_addrtype;

int h_length;

char **h_addr_list;

};


공식 이름(official name)

별명 목록(alias list)

호스트 주소 형태(host address type)

주소의 길이(IPV4 : 4, IPV6 : 16)

주소 목록(list of addresses)

struct hostent

{

char FAR * h_name;

char FAR * FAR * h_aliases;

short h_addrtype;

short h_length;

char FAR * FAR * h_addr_list;

}


※ IP주소를 일반화 하기 위해 char*를 사용하였다. IN_ADDR 구조체 형태로 IP주소가 저장되기 때문에 void* 포인터 형태가 더 좋았을 듯.


struct hostent 구조체 변수


반응형
반응형

※ 연결에 대한 내용이므로 TCP/IP 프로토콜에 대한 이야기.

※ 우아한 종료 = graceful close. (MSDN 참고)


7-1 소켓 연결 종료의 문제점

2개의 가상의 통로가 생성 (각 통로는 단방향)


스트림 : 연결된 상태 (데이터를 주고 받을 수 있도록 연결된 시스템의 내부적인 상황)

입력 스트림 : 데이터 수신을 위한 스트림

출력 스트림 : 데이터 전송을 위한 스트림


※ TCP 소켓은 데이터를 전송하면서 동시에 수신도 가능하다.


소켓 연결 종료의 문제점

1. close, closesocket 함수 호출 : 입력, 출력 스트림 완전 종료

 => 데이터를 송수신 할 수 없다. 데이터가 소멸된다.

2. 일방적인 방식의 완전 종료는 경우에 따라서 문제가 될 수 있다.



7-2 우아한 소켓의 연결 종료

half close : 입력 혹은 출력 스트림 중 하나의 스트림만 종료하는 행위

예시) A 호스트의 출력 스트림만 종료

#include <sys/socket.h>

int shutdown(int s, int how);

리턴 : 성공시 0, 실패시 -1

s : 종료하고자 하는 소켓의 파일 디스크립터

how : 종료 모드를 인자로 전달한다.

 상수값 모드 정의 
 0  SHUT_RD  입력 스트림 종료 
 1  SHUT_WR 출력 스트림 종료 
 2  SHUT_RDWR  입출력 스트림 종료


출력 스트림의 종료의 필요성

※ Thank you를 받기 위해 shutdown함수를 사용한다.


1. 출력 스트림을 종료하게 되면, 연결되어 있던 호스트로 EOF 메세지 전달.

※ 출력 스트림을 종료하는 방법 : close(closesocket), shutdown 함수.

2. EOF 전송으로 데이터 전송의 끝을 알려 줄 수 있다.

3. EOF 전송 시, 상대 호스트의 데이터 수신 함수(read, recv)는 0을 리턴.


※ EOF를 생략하고 보내면 서버도 read() 무한 대기, 클라이언트도 파일의 끝을 모르므로 read() 무한대기.


※ EOF의 값 : -1 (int type) -> 1바이트를 4바이트로 casting하면 -1이 아닐 수 있다.


7-3. 윈도우즈 기반으로 구현하기

※ 윈도우즈 기반에서는 파일 열기를 위해 표준 라이브러리를 사용하였다.


#include <winsock2.h>

int shutdown(SOCKET s, int how);

리턴 : 성공시 0, 실패시 SOCKET_ERROR

s : 종료하고자 하는 소켓의 파일 디스크립터

how : 종료 모드를 인자로 전달한다.

 상수값 모드 정의 
 0  SD_RECEIVE 입력 스트림 종료 
 1  SD_SEND 출력 스트림 종료 
 2  SD_BOTH 입출력 스트림 종료

반응형
반응형

※ TCP와 UDP를 특징을 비교하면서 공부하는 것이 좋다.

6-1. UDP의 이해

1. IP를 기반으로 데이터를 전송한다. (TCP와 공통점)

2. 흐름제어(flow control)을 하지 않기 때문에 데이터 전송을 보장 받지 못한다. (TCP와 차이점)

3. 연결설정 및 연결 종료 과정도 존재하지 않는다. (TCP와 차이점)

4. 연결 상태가 존재하지 않는다. (TCP와 차이점)


UDP의 역할

포트 정보에 의한 프로세스의 구분

UDP 패킷 = 데이터 그램(Datagram)


6-2. UDP 기반 서버/클라이언트의 구현

※ 소켓을 생성하고 bind까지만 하면 바로 입출력함수를 사용하면 된다. 

(socket~bind : 서버, socket : 클라이언트)

일반적으로 연결 설정 과정을 거치지 않는다.

데이터를 주고 받기 위한 소켓(우체통에 비유)은 하나만 생성해도 된다. (패킷은 우편)


※ UDP는 비연결 프로토콜이기 때문에 주소 정보를 알아야 한다.

데이터 전송함수

int sendto(int sock, const void* msg, int len, unsigned flags, const struct sockaddr *addr, int addrlen);

int sendto(SOCKET s, const char FAR *buf, int len, int flags, const struct sockaddr FAR *to, int tolen);

1~4번째 인자는 TCP send함수와 비슷.

addr : 목적지 주소 정보

addrlen : 목적지 주소 정보 구조체 크기


데이터 수신함수

int recvfrom(int sock, void *buf, int len, unsigned flags, struct sockaddr *addr, int *addrlen);

int recvfrom(SOCKET s, char FAR *buf, int len, int flags, struct sockaddr FAR *from, int FAR *fromlen);

1~4번째 인자는 TCP recv함수와 비슷.

addr : 목적지 주소 정보

addrlen : 목적지 주소 정보 구조체 크기


※ uecho_client_win.c : 윈도우 UDP 에코 클라이언트는 리눅스 용과 다르게 제작되어 있음.

※ UDP에서 클라이언트의 주소는 언제 할당될까? 6-4에 있음.


6-3. 데이터의 경계(boundary)가 존재하는 UDP 소켓

UDP 소켓은 데이터를 송수신하는데 필요한 함수 호출의 수를 정확히 일치 시켜야 한다.


6-4. connect 함수 호출을 통한 성능의 향상

1. TCP 소켓에서의 connect 함수의 의미

 - IP와 포트의 할당

 - Three-way handshaking

2. UDP 소켓에서의 connect 함수의 의미

 - IP와 포트의 할당

※ connect함수가 없으면 sendto 함수가 제일 처음 호출되는 시점에 IP와 Port가 할당된다. 

클라이언트에서 IP와 Port가 한 번 할당되면 close()함수 호출시까지 변하지 않는다.


TCP/UDP 소켓에서 공통적으로 가지는 connect의 의미


connect함수의 의미 

커널과 소켓이 논리적으로 연결하고 그것을 유지한다.


connect함수를 호출하지 않을 때 UDP 클라이언트의 데이터 송수신

1. sendto, recvfrom 함수를 호출하면 커널과 소켓이 연결되고 소켓과 호스트와 통신할 준비를 한다.

2. 호출이 끝나면 커널과 소켓의 연결이 해제된다.

※ 위의 1~2를 반복하는 데 많은 시간을 잡아먹고 커널과 소켓을 연결했다 해제했다 하기 때문에 성능 효율이 떨어진다.


connect 함수 호출이 주는 이점

1. 데이터를 주고 받는 속도가 빨라진다.

2. TCP 소켓 기반의 데이터 입출력 함수를 그대로 사용할 수 있다.


반응형
반응형
반응형
반응형

5-1. 에코 클라이언트! TCP 기반에서의 완벽 구현

TCP 기반의 데이터 전송 특징

한 번의 데이터 전송함수 호출이 늘 하나의 패킷을 형성하는 것은 아니다.

※ 버퍼 : 서버 프로그램 상의 문자 배열.

※ ABCD를 클라이언트에서 서버로 보냈지만 A,B,C,D와 같이 패킷이 나누어져서 서버가 클라이언트로 보낼 수 있다. 

※ echo server에서는 클라이언트에서 문제가 된다.

※ 구현 : 클라이언트는 여러번 read(recv)함수를 호출해야 한다. -> for문에서 읽어들인 바이트수가 보낸 바이스만큼 읽을 때까지 반복한다.


패킷이 나누어져 보내지는 이유? 뒤에 밝힘


5-2. 경계가 없는 TCP 기반의 데이터 전송

데이터 송수신 함수의 호출 회수는 큰 의미를 지니지 않는다.

예시) 위의 내용을 증명하기 위한 예제 프로그램

※ 서버에서는 한 번에 메세지를 읽기 위해서 sleep(5);를 한다.

※ 클라이언트에서는 4번에 나누어서 메세지 수신을 하기 위해 sleep(10);을 추가하였다.


이미 전송된 데이터는 어디에 존재하고 있었는가?

예시) 서버가 전송하면 클라이언트 (소켓)버퍼에 서버에서 받은 내용을 저장하고 있다.


서버에서 accept 했을 때 생기는 소켓에서 입력버퍼와 출력버퍼가 생성된다.

클라이언트는 소켓에 입출력버퍼가 있다.

※ read함수는 입력버퍼에 있는 내용을 읽는다. 바로 읽지 못할 수 있기 때문에 버퍼가 존재한다.


출력버퍼의 역할?

※ (서버에서) 송신할 때 (클라이언트의) 입력버퍼보다 큰 양을 보낼 수 없다.

=> 가능한 이유 : 클라이언트가 입력버퍼의 양만큼만 보내라는 신호를 보내기 때문에...

※ 이 이유때문에 출력(송신)버퍼가 필요하다.


※ 흐름제어를 위해서 입출력 버퍼가 존재한다.


TCP 기반의 전송제어

1. 버퍼가 수용할 수 있는 크기 이상의 데이터 전송은 이루어지지 않는다.

2. 따라서 TCP 기반의 데이터 전송 함수는 여러 개의 패킷을 생성하기도 한다.

3. 슬라이딩 윈도우 프로토콜 : 남은 버퍼양만큼만 데이터 전송을 하도록 돕는다.


5-3 TCP의 내부 구조

TCP의 데이터 전송 과정

(서버와 클라이언트는 서로 의견을 존중한다.)


1. 연결 설정 단계

클라이언트가 connect 함수 호출 시 진행

Three-way handshaking

① SYN(C) : 동기화. 싱크. 데이터를 주고 받을 수 있습니까?

② SYN + ACK(S) : 대화가 가능(OK)합니다. SYNchronize(동기화)에 대한 ACKnowgement(확인)의 기능.

③ ACK(C) : 잘 받았다라는 뜻으로 확인한다.

※ ③을 보내야 하는 이유 : 클라이언트가 ACK을 보내지 않으면 서버 입장에서 확인 메세지를 못받았다고 생각한다.


SEQ : 시퀀스 번호. 메세지를 잘 받았는 지 확인하기 위해서 붙이는 번호.

(메세지 번호) 내가 보낸 번호를 ACK으로 대답해 달라.

ACK : 확인 번호. 잘 받았다는 것을 응답하기 위해 붙이는 번호.

다음 SEQ 번호를 보내라는 뜻.


2. 데이터 송수신 단계

서버/클라이언트 간 데이터 송수신 함수 호출 과정에서 진행.

1. SEQ:1301,100바이트를 보낸다.

2. 100바이트를 잘 받았다는 것을 알리기 위해 SEQ번호+100을 해서 ACK을 보낸다.

※ SEQ번호는 응답받은 ACK번호와 같아야 한다.

3. SEQ:1401, 100바이트를 보냈는데 손실되었다. 보내는 동시에 타이머를 작동.

손실되었으므로 응답이 없다. 상대방이 못받았다고 생각하므로

4. SEQ:1401, 100바이트를 다시 보낸다.

5. ACK을 보낸다.


3. 연결 종료 단계

클라이언트 혹은 서버가 close(closesocket) 함수 호출 시 진행

four-way handshaking

1. FIN(A->B) : 종료 요청의 메세지를 담은 패킷을 보낸다. FINish

2. ACK(B->A) : 단순히 패킷을 잘 받았다는 신호만 보낸다. 아직 종료할 상황이라는 뜻은 아니다.

3. FIN(B->A) : 종료해도 좋다는 의미의 FINish를 보낸다.

4. ACK(A->B) : 최종적인 수신 응답 메세지 ACK을 전송한다.

반응형
반응형

4-1 TCP/UDP에 대한 이해

TCP/IP 프로토콜 스택

응용프로그램 계층      : 응용프로그램     응용프로그램

전송(Transport)  계층 :       TCP                UDP

네트워크 (Network)계층:                  IP

데이터링크 계층 :                          LINK

물리 계층 


데이터링크 계층

LAN, WAN, MAN과 같은 네트워크 표준과 관련된 프로토콜의 정의한다.


IP 계층

어떻게 길을 찾아 갈 것인가?

라우터끼리는 (도로사정)이 어떤지 서로 통신하여 패킷을 보낼 최적의 길을 찾는다.

신뢰할 수 없는 프로토콜(데이터가 손실 될 수 있고) 경로가 일정치 않으며 패킷을 보낸 순서도 보장하지 못한다. 비연결 지향 프로토콜이다.


TCP/UDP

(IP 계층을 기반으로 하여 ) 데이터를 어떻게 전송할 것인가?

TCP/UDP(2가지 프로토콜) 중에 원하는 프로토콜을 선택한다.


TCP와 IP의 관계

IP를 기반으로 길을 찾고 TCP를 통해 연결 지향 및 신뢰성 있는 통신을 제공한다.

호스트대 호스트가 어떻게 데이터를 주고 받을 것인지 약속.

IP(편지에 비유) -> 패킷(편지)을 잃어버림


TCP의 역할 

A 호스트가 B 호스트에게 패킷을 하나 전송한다.

1. B가 잘 받았을 경우 : 응답용 패킷을 A에게 전송한다.

2. B가 잘 못 받았을 경우 : A는 B가 데이터를 수신하지 못했다고 간주하고 임의의 시간 후 재전송.


4-2. TCP 기반 서버의 구현

socket - 소켓 생성

bind - 주소할당

listen - 연결 요청 대기 상태 (친구에게 전화가 올 수 있는 상태)

accept - 연결 허용(수화기를 든다.)

read & write - 데이터 송수신

close - 연결 종료


'연결 요청 대기 상태'로의 진입

1. listen 함수는 전달되는 인자의 소켓을 '서버 소켓'이 되게 한다.

2. listen 함수는 backlog의 수(대기실의 크기)만큼 '연결 요청 대기 큐'를 생성 한다.

3. 성공하면 '연결 요청 대기 상태'가 된다.

#include<sys/type.h>

int listen(int s, int backlog);

#include<winsock2.h>

int listen(SOCKET s, int backlog);

 - socket : 서버소켓으로 만들 핸들 혹은 파일 디스크립터.

 - backlog : 대기 큐(실)의 크기, 클라이언트 수.


서버의 역할과 연결요청 대기상태

서버 소켓은 일종의 '문지기'이다. (클라이언트의 연결 요청을 감지하고 받는다.)

클라이언트 - 서버로의 연결요청 - 서버 소켓 -> 대기실

※ 여러 사람의 요청을 받게 하기 위해 '큐(대기실)'를 만든다.


연결요청 수락하기

연결요청 대기 큐에 존재하는 클라이언트의 연결 요청 수락.


#include<sys/type.h>

#include<sys/socket.h>

int accept(int s, struct sockaddr *addr, int *addrlen);

#include<winsock2.h>

SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);

리턴값 : 클라이언트와 연결하기 위한 (밑의 그림의)New 소켓

addr : 클라이언트의 주소 정보가 채워진다.

addrlen : 정보구조체의 크기 정보가 채워진다.

 

listen ~ accept 과정

※ New 소켓은 자동적으로 만들어지며 이 New 소켓으로 클라이언트와 데이터 송수신이 가능하다.


4-3 TCP 기반 클라이언트의 구현


#include<sys/types.h>

#include<sys/socket.h>

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

sockfd : 연결 요청을 할 소켓

serv_addr : 주소정보

addrlen : 구조체 크기


※ 소켓은 연결하기 전에 미리 운영체제(커널)에 의해 임의로 할당된다.


4-4 서버/클라이언트 함수 호출 관계




※ listen()에서 대기 큐를 만들고 연결 요청을 받을 수 있는 서버 소켓을 만든다.

※ connect 함수는 서버의 IP, 포트를 설정해 주면 자동으로 클라이언트 컴퓨터 내의 임의의 포트에 운영체제가 통신하기 위한 소켓을 생성한다.

※ accept() 함수는 연결 요청 대기 큐가 비어있을 때까지 혹은 클라이언트가 연결할 때까지 block되어 있다.

=> 리턴 되면서 연결된다.


4-5 Iterative 서버의 구현

Iterative 서버 : 반복해서 클라이언트의 요청을 처리한다.

여러 클라이언트가 서버로 연결 요청을 한다. -> 연결 요청 대기 큐로 들어간다. -> 서버가 accept를 호출할 때마다 대기 큐에서 하나의 소켓을 꺼낸다. -> 그 소켓과 통신을 하고 끝나면 반복해서(Iterative) accept를 호출한다. 


Iterative Server Template

※ 리턴 클라이언트 소켓은 하나다.


4-6 에코(echo) 서버/클라이언트의 구현

concurrent 서버(서버 하나와 클라이언트 여러 개 관계)

※ 버퍼 : 여기서는 프로그램 상의 버퍼. 문자열을 저장할 수 있는 배열.

※ 클라이언트에서 close(소켓핸들);을 호출하면 서버의 read함수의 리턴 값은 0이 된다.

반응형
반응형

3-1 Internet Address

IP(Internet Address) 

인터넷에 존재하는 호스트들을 구분하기 위한 32비트 주소 체계

점이 찍힌 십진수 표현 방식(Dotted-decimal Notation) : 211.217.10.9


클래스

Class A : 0.0.0.0 ~ 127.255.255.255, 1바이트는 네트워크 ID, 3바이트는 호스트 ID

Class B : 128.0.0.0 ~ 191.255.255.255, 2바이트는 네트워크 ID, 2바이트는 호스트 ID

Class C : 192.0.0.0 ~ 223.255.255.255, 3바이트는 네트워크 ID, 1바이트는 호스트 ID

Class D : 멀티캐스트 주소. 224.0.0.0 ~ 239.255.255.255

Class E : 예약됨. 240.0.0.0 ~ 255.255.255.255


IP 주소 : 네트워크 주소(네트워크를 구분지음) + 호스트 주소(호스트를 구분지음)

subnet mask에 따라서 네트워크 주소(ID)와 호스트 주소(ID)로 나누어진다.


※ 라우터는 네트워크 ID(주소)만 참조한다.

※ 루프백 주소 : 127.x.x.x, 네트워크 상으로 패킷을 전송하지 않고 자기자신에게 돌려준다.


3-2 Port란 무엇인가?

Port

호스트 내에서 실행되고 있는 프로세스를 구분 짓기 위한 16비트의 논리적인 값. (소켓에 할당)

논리적인 값 : 소프트웨어 적으로 구현. 

Well-known ports : 0~1023


3-3 주소 정보의 표현

IPV4의 주소 체계를 나타내는 구조체

※ 하나의 프로토콜 내에 2개 이상 프로토콜이 있을 것을 대비해서 만든 구조체. ()는 윈도우즈용.

※ 모든 데이터는 네트워크 바이트 순서로 저장해야 한다.

struct sockaddr_in

{

sa_family_t(short) sin_family; // 주소 체계(address_family) - AF_INET

uint16_t(unsigned short) sin_port; // 16비트 TCP 혹은 UDP 포트

struct in_addr sin_addr; // 32비트 IPv4 주소

char  sin_zero[8]; // 사용되지 않음. padding 용도

};


struct in_addr

{

uint32_t(unsigned long) s_addr; // 32비트 IPv4 인터넷 주소

};



3-4 네트워크 바이트 순서

0x12345678을 Big-Endian으로는 메모리 순서대로 0번지 0x12, 1번지 0x34, 2번지 0x56, 3번지 0x78

0x12345678을 Little-Endian으로는 메모리 순서대로 0번지 0x78, 1번지 0x56, 2번지 0x34, 3번지 0x12

형태로 저장.


호스트 바이트 순서 

어떤 시스템(motorola, sun사 기계)은 Big-Endian, 어떤 시스템(intel사)은 Little-Endian을 사용하므로 일정하지 않다.

※ 데이터 표현 방식이 다르므로 다른 플랫폼끼리 문제가 발생한다.


네트워크 바이트 순서

Big-Endian 방식을 적용하기로 하였다.


바이트 순서 변환 함수

※ 어느 시스템에서 돌아갈 지 모르기 때문에 반드시 사용해야 한다.

unsigned short htons(unsigned short); // 호스트 -> 네트워크 short

unsigned short ntohs(unsigned short); // 네트워크 -> 호스트 short

unsigned long htonl(unsigned long); // 호스트 -> 네트워크 long

unsigned long ntohl(unsigned long); // 네트워크 -> 호스트 long


'h' : host byte order

'n' : network byte order

's' : short(16비트)

'l' : long(32비트)


3-5 인터넷 주소 조작하기

1. 점이 찍힌 십진수 방식(Dotted-Decimal Notation)을 Big-Endian 32비트 정수형 데이터로 바꿔주는 함수(네트워크)

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

unsigned long inet_addr(const char *string);

리턴 : 성공시 32비트 big-endian값, 실패시 INADDR_NONE


#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

int inet_aton(const char* string, struct in_addr *addr);
리턴 : 성공시 true, 실패시 false
※ 바로 struct in_addr에 값을 제공할 수 있다.


2. Big-Endian 32비트 정수형 데이터를 점이 찍힌 십진수 방식로 바꿔주는 함수(네트워크)

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

char* inet_ntoa(struct in_addr addr);

※ 따로 inet_ntoa 내부적으로 static 배열을 통해 주소에 대한 문자열이 존재한다.


3-6 인터넷 주소 초기화

struct sockaddr_in addr;

char *serv_ip = "...";

char *serv_port = "...";

memset(&addr, 0, sizeof(addr_len)); // 주소에 해당하는 구조체 값을 0으로 초기화. 좋은 습관.

addr.sin_family = AF_INET;

addr.sin_addr.s_addr = inet_addr(serv_ip);

addr.sin_port = htons(atoi(serv_port));


INADDR_ANY?

addr.sin_addr.s_addr = htons(INADDR_ANY);

// 서버의 IP가 뭐더라? 답은 INADDR_ANY : 내 시스템의 IP 주소를 찾아 알아서 할당.


3-7 주소 정보 할당하기

리눅스

#include<sys/types.h>

#include<sys/socket.h>

int bind(int sockfd, struct sockaddr *myaddr, int addrlen);

윈도우즈

#include<winsock2.h>

int bind(SOCKET s, const struct sockaddr FAR *name, int namelen);


sockfd/s : 주소를 할당하고자 하는 소켓의 파일 디스크립터 인자/핸들 인자

myaddr : 할당하기를 원하는 주소 정보를 지닌 sockaddr_in 구조체 변수의 포인터. 

※ sockaddr 형태로 형변환 해서 넘겨야 한다. 

※ 프로토콜에 독립적으로 사용하기 위해서. void 포인터 보다 먼저 이 함수가 개발되었다. 

※ FAR 포인터는 과거의 잔재. 현재는 무시한다.

addrlen/namelen : 주소 정보 구조체의 길이.


3-8 윈도우즈 기반으로 구현하기

※ SOCKADDR_IN = struct sockaddr_in

※ SOCKADDR = struct sockaddr

※ 서버 프로그래밍을 할 때는 htons(INADDR_ANY);를 사용한다.


3-9 WSAStringToAddress & WSAAddressToString

주소 정보를 나타내는 문자열을 가지고 주소 정보 구조체 변수를 채운다.

※ 윈도우즈 기반 주소 변환 함수.

#include<winsock2.h>

INT WSAStringToAddress(

LPTSTR AddressString, // 점이 찍힌 십진수 표현(포트정보 포함)과 같은 주소 정보 문자열 포인터.

INT AddressFamily,      // 주소 정보 문자열이 속한 주소 체계(AF_INET)

LPWSAPROTOCOL_INFO lpProtocolInfo,   // 프로토콜 제공자를 설정. 일반적으로 NULL.

LPSOCKADDR lpAddress,  // 주소 정보 구조체 변수 포인터

LPINT lpAddressLength);  // lpAddress 포인터가 가리키는 버퍼의 크기.

리턴 : 성공시 0, 실패시 SOCKET_ERROR


#include<winsock2.h>

INT WSAAddressToString(

LPSOCKADDR lpsaAddress, // 문자열로 변환할 주소 정보를 가진 구조체 포인터

dwAddressLength,              // lpsaAddress 포인터가 가리키는 변수의 크기

LPWSAPROTOCOL_INFO lpProtocolInfo,   // 프로토콜 제공자를 설정. 일반적으로 NULL.

LPTSTR lpszAddressString,  // 문자열로 변경된 결과를 저장할 버퍼 포인터.

LPDWORD lpdwAddressStringLength);     // lpszAddressString 버퍼의 크기.

리턴 : 성공시 0, 실패시 SOCKET_ERROR




반응형
반응형

2-1 프로토콜 

컴퓨터 상호간의 대화에 필요한 통신 규약

혼돈의 여지가 있으면 안되고 잘 정의(FM)되어야 한다.

※ 안 좋은 예 : 서버에서 accept 하자마자 읽는 연산을 수행.


2-2 소켓의 생성

기본적인 통신 도구

프로토콜에 독립적 : 사용자가 프로토콜을 정해주어야 한다는 점에서...

데이터 전송 형태(타입)를 지정해야 한다.


2-3 프로토콜 체계

#include<sys/types.h>

#include<sys/socket.h>

int socket(int domain, int type, int protocol);

domain : 프로토콜 체계. family

(여러 가지 프로토콜이 합쳐져서 체계를 이룬다.)

PF_INET : IPv4 (4바이트 2^32승개의 IP)

PF_INET6 : IPv6 (16바이트 2^128승개의 IP, 약 10의 38승)

PF_LOCAL : Local 통신을 위한 UNIX 프로토콜

PF_PACKET : Low level socket을 위한 인터페이스

PF_IPX : IPX 노벨 프로토콜

※ 127.0.0.1 : loopback address


2-4 소켓의 타입

type : 형태

1. 연결지향형(connection oriented) 

전송하는 순서대로 데이터가 전달.

에러나 데이터 오류 없이 전달.

전송되는 데이터의 경계가 존재하지 않는다.

 => write함수를 2번호출한다고 해서 read함수가 꼭 2번 호출될 필요가 없다. 


2. 비연결지향형(connectionless)

예시) 우편이면 일반우편, 빠른 우편, 등기 등.

전송되는 순서에 상관없이 가장 빠른 전송.

데이터는 손실되고 에러가 발생할 수도 있다.

데이터의 경계가 존재하고 그 크기는 제한되어 있다.

 => write함수를 2번 호출하면 read함수가 꼭 2번 호출해야 한다.


2-5. 프로토콜의 선택

protocol : 프로토콜을 구체화할 때 필요하다. (raw_socket을 다룰 때 유용하다.)

IPPROTO_TCP : TCP를 기반으로 하는 소켓을 생성. PF_INET + SOCK_STREAM

IPPROTO_UDP : UDP를 기반으로 하는 소켓을 생성. PF_INET + SOCK_DGRAM


※ 파일 핸들도 정수형 데이터이다. 

int a = socket(...); = SOCKET a = socket(...);

앞으로 소켓의 데이터 형이 바뀔 것을 대비해서 SOCKET을 사용하는 것이 좋다.


소켓의 종료

리눅스 

#include<unistd.h>

int close(int filedes);

리턴 : 성공시 0, 실패시 -1

filedes : 닫아줄 파일의 파일 디스크립터


#include<winsock2.h>

int closesocket(SOCKET s);

리턴 : 성공시 0, 실패시 SOCKET_ERROR

s : 닫아줄 소켓의 핸들

반응형

+ Recent posts