1ebec223e0dc2bae61ab96e746837270dab2393308a2792bf45b20651fcacfe6e93c2c4278b73958b79e4c4fe2e583



[open_socket]

리눅스에서만 소켓도 파일이다. 

파일디스크립터(번호표)- 비어있는번호중 가장 작은수부터 순서대로

단 0,1,2는 c언어파일이 실행될때 미리 배정(입출력,에러) 되어있으므로 3번부터 시작한다

(파일을 열때, 또는 소켓을 생성할때 준다)

> open과 socket으로 그걸 한다. 여는데 실패하면 -1을 반환한다


sys/socket.h - socket함수 - 소켓(연결창구)생성 인자들:

주소체계 : PF_INET(상수2) > ipv4주소사용 : 인터넷 프로토콜 패밀리들중 사용할게!

통신방식 : SOCK_STREAM > TCP사용, SOCK_DGRAM > UDP사용

0 > 위 방식대로 자동결정(tcp,udp는0넣으면되고, SOCK_RAW로 로우소켓/ip계층바로이용할땐 구체적지정! 소켓은 tcp/ip만을 위한것이아니다)


파일이나 소켓을 관리할때는 앞으로 FD를 이용한다 즉 close를 할때도 

close(번호표)를 해야한다는 것임 그럼 번호표 반납해서 다른게 거기 들어갈수있음


소켓은 5가지 정보가 필요하다

1.프로토콜(TCP/UDP)

2.자신ip주소, 포트번호

3.상대ip주소, 포트번호


[byte_order]

netdb.h

servent 구조체 : 네트워크서비스 정보를 담는상자 

[함수가 만들어주는 거. 가변적이다<->내가 직접 만드는sockaddr_in/in_addr]

struct servent {

    char  *s_name;    // 서비스의 공식 이름 (예: "http", "echo")

    char **s_aliases; // 서비스의 별명들

    int    s_port;    // 포트 번호 (가장 중요!)

    char  *s_proto;   // 사용하는 프로토콜 (예: "tcp", "udp")

};

struct servent *servent (servent타입으로 servent라는 포인터변수 선언 : 여기에는 오직 servent구조체의 주소만 저장이 가능함)

로 선언하면 첨에는 쓰레기값을 가르킴

getservbyname("서비스이름","프로토콜")로 전번에서 서비스정보 찾아서

그 구조체에 넣고 위치주소를 던져줌. 없으면 널값을 던짐(주소로 반환하기때문에 -1이아니다 주의)

포트번호(int) 뽑아내려면 servent->s_port          (*servent).s_port 와 같다

*servent로 전체출력은 불가능하다. 하나씩빼서 출력해야한다

에코의 포트번호는 7번!!! tcp/udp는 다른 포트지만 편의상 서비스가 같으면 포트가 같게 맞춰둔거다 그래서 정확히 tcp/udp를 명시해야 한다


근데 호스트는 리틀엔디안, 네트워크는 빅엔디안을 씀. 

ntohs : 네트워크 투 호스트 쇼트(2바이트)는 int값을 리틀엔디안으로 읽는다

16진수는 4비트, 2개씩 하면 1바이트다. os는 1바이트씩 읽는다. 주소단위가 1바이트다

00 07 이 각각 메모리에 "순서대로" 저장되는데, 이 병신같은 cpu는 07 00 이렇게 거꾸로 조립한다

그래서 ntohs는 애초에 메모리를 07 00으로 2바이트를 뒤집은 값을 반환하는것이다


포트번호는 보통 unsignted short로 0~65535까지 있지만 ntohs에 int형을 넣어도 문제없이 작동한다 [00 00 00 07] > 캐스팅되어 뒤에 2바이트만 처리한다

데이터를 읽는거니까 ntohs를 사용한다. 반대로 포트번호를 직접 입력할때는 htons를 사용

근데 왜 s_port가 short가 아니라 int형이냐? 나도 모름 그냥 그렇게 쓴대 서비스니깨~

이렇게 파고들면 s_proto는 왜 int아님!! 우길수도 있음


<stdlib.h>무조건 써준다 엑시트로 예외처리 하려면 필요함

<sys/types.h> 그냥 써준다



[ascii_ip]

struct in_addr inaddr는 그냥 ip주소를 담기 위한 상자다. 안에는 unsigned int s_addr

ipv4주소는 총4바이트로, 1바이트(0~255)씩 점으로 나누어 4개의 10진수로 나온다

inaddr는 포인터가 아니라 진짜 구조체 본체다. 그래서 값가져올때 화살표를 안쓰고 inaddr.s_addr씀


p는 프레젠테이션, 즉 문자열을 의미하고 n은 실제 컴퓨터의 언어를 의미한다

inet_pton(AF_INET,argv[1],&inaddr.s_addr)

사실 PF나 AF나 ipv4주소체계 쓰는건 맞는데, 프로토콜이 중요하지 않고 ip주소체계를 쓴다는 의미에서 관례로 AF를 쓴다

두번째 인자에는 p 즉 문자열을 넣고, 세번째는 n 변환된 숫자를 저장할 메모리를 적는다

메모리 적을때 그냥 일반변수처럼 &앞에 적어주는거 주의. 포인터가 아니다

7F 00 00 01 라고 저장되는데 읽어올땐 01 00 00 7F 로 가서 0x100007F 나온다(%x 16진수출력)

inet_ntop는 그 메모리로 가서 다시 우리가 읽을수있는 ip주소로 바꾸는 것이다

char은 1바이트라 각각 한문자가 저장되고 마지막에 널값(00)이 저장되서 총 10바이트저장

버퍼오버플로우가 발생하지 않기위해, 네번째 인자로 버퍼의 사이즈를 넣는다


[get_hostent]

구조체hostent : 호스트정보를 모두 담고 있다. 이름 별명 (주소체계 주소길이) ip주소들

char *h_name > 도메인네임

char **h_aliases > 별명들의 배열

int h_addrtype > ipv4면 2

int h_length > ipv4면 4(byte)

char **h_addr_list > ip주소들의 배열

(구조체의 앞글자를 딴다. 그리고 char*(문자열)또는 int형이다)


gethostbyname("도메인네임")으로 호스트정보를 구조체에 포장하여 구조체의 메모리주소를 hp에 준다. 없으면 널값

서비스 servent(getservbyname) <> 호스트 hostent(gethostbyname) 비교


for문의 hp->h_addr_list[i] 이 배열이 모두 끝날때(null)까지 진행

memcpy - 뒤에꺼를 앞에꺼에 복사한다(전부 주소), sizeof(4바이트만큼)

hp구조체에 h_addr_list[0]의 주소에 접근해서(이때 이건 ->썼다고 값이 아니다. 여전히 char* 포인터이므로 주소다) 그로부터 4바이트만큼 복사해서

in.s_addr 주소에 붙여넣어

이때 타입은 따지지도 않는다 그래서 char*과 int가 호환이 되는것

&in은 &in.s_addr와 같다. 상자의 시작주소나 알맹이의 시작주소나 같다.

근데 그냥 inet_ntop(AF_INET,hp->h_addr_list[i],buf,sizeof(buf)) 해도 문제는 없다

어차피 ntop는 타입에 상관없이 그냥 그 주소로 가서 AF_INET에 따라 4바이트를 긁어와서

읽을수있는 char[]타입으로 변환함. (항상 붙여넣기 하는 타입의 사이즈를 써준다)

7F 00 00 01이 있다면 이게 한 덩어리의 int타입이든, 

1바이트짜리 char 네 덩어리를 char*가 가리키고 있든.


그리고 hp->h_addrtype를 만약에 주소로 바꾼다면 앞에다가 &만 붙여라

                   변수/변수        포인터/변수         포인터/포인터

출력용(값)    in.s_addr       hp->h_addrtype         hp->h_name

입력용(주소) &in.s_addr    &hp->addrtype          hp->h_name

포인터는 값 자체가 주소다.


in_addr와 hostent구조체의 차이

in_addr의 in은 직접 상자를 스택메모리(앞마당)에 만든다. 이 스택메모리에 있는것들은 함수가 끝나면 종료됨

반대로 hostent는 뒷마당에 외부 상자를 만들고 그걸 가리키는 hp라는 *8바이트짜리*주소표를 준다. (모든 주소표는 8바이트다. 앞에붙은 타입은 상관없다. 타입은 그저 몇바이트를 읽어야 하는지 알려주는 가이드라인일뿐이다. 물론 이 주소표에는 그 타입만 저장이 가능할뿐) 

물론 이 hp라는 포인터 변수 자체는 스택메모리에 저장된다

뒷마당=정적메모리=데이터영역

포인터변수를 여러개 선언해도, 그냥 쪽지를 2개 준비한것뿐, 가리키는 상자는 하나다

왜냐? getservbyname/gethostbyname함수가 문제다 이새끼들이 상자를 만드는 주범인데 하나만 만들도록 해놨다 그래서 servent와 hostent가.....


# char *p = "echo"와 char q[] = "echo"의 차이

박물관의 전시품과 내 복사본의 차이다. 전자는 rodata영역에 박제하고 p라는 주소만 주는것이다 그래서 수정이 불가하다. 다만 후자는 rodata로부터 스택메모리로 5바이트짜리를 복사하는거여서 수정가능. 그리고 q자체가 주소값이 아니고 그 방의 값을 나타낸다

근데 printf %s 로 표현할때는 "주소"로 받는다. 그래서 p은 이미 주소이므로 그대로 p쓰면 됨

(hp->h_name자체가 char*이니까 그냥 쓰는것과 같음)

근데 q의 경우는 q가 가능하다! 아니 방이름인데 왜? &q[0]로 자동변환되기 때문

즉 배열은 대부분의 상황에서 시작주소를 가진 포인터로 변환된다(Array Decay)

[배열이 포인터로 안변할때 : sizeof(q) 5바이트 (8바이트X)

&q로 쓰일때, 5바이트짜리 배열 덩어리 전체의 주소를 의미함

&&는 애초에 불가능하니까 예외가생기는거임]


int num같은 경우처럼 q도 결국 방이름이기 때문에 포인터와는 달리 주소변경이 안된다.

방이름은 항상 그 메모리번지에 고정된다

(num = &d 이러면 당연히 엉뚱한 값이 들어가겠지만, p=&d는 가능한것)

만약 반대로 쓰레기값을 주소에 넣는다면 세그먼트폴트가 뜬다



printf %d의 경우는 "값"을 기대하니까 그냥 방이름 (num) 적어주면 된다

printf %c도 "값"을 기대하니까 s1[0] 이거 넣어도 성립함>H 

s1[0]은 *(s1+0)으로 값을 읽어오는거임!!! 수정만 안될뿐 읽어오는건 당연히 가능함



** int *p = 10; 이지랄 하면 안된다!!! 무조건 선언할때는 주소가 저장되니 10번지가 저장됨

int num = 10; int* p = &num; 이렇게 해야 맞죠. 그리고 *p = 20 해버리면 num값도 20된다

p = &a; 해버리면 변경을 하는거고. p=p2이면 p2는 포인터인거고.



** &q특강

&q와 &q[0]의 차이 - 주소값자체는 같다(100가정)

&q+1 >106        타입char(*)[5]

&q[0]+1 > 101    타입char*

** 포인터 주소 더하기 연산은 타입도 고려해야 한다

char *p      p+1 /1바이트이동       (그냥 간단하게 p[1]했던게 이런 원리가)

int *p        p+1 /4바이트이동

int p[3]      p+1 <==> &p[0]+1 / 4바이트이동/ 값읽을려면 *로 <==> p[1]

               단, &p+1 / 12바이트이동/타입 int(*)[3]이기 때문



    char *p = "echo";

    char q[] = "echo";

    printf("%s %s\n",p,q);                 echo

    printf("%c %c\n",*p,*q);               e

    printf("%c %c\n",p[1],q[1]);           c

    printf("%c %c\n",*(p+2),*(q+2));    h

//여기까지 사용법 동일

    #중요#printf("%p %p\n",&p,&q);           0x7ffc2dbe71e8(포인터자체주소!!)   0x7ffc2dbe71e3(예외에의해서 그냥 echo의 주소를가리킴)

    printf("%p %p\n",&p[0],&q[0]);     0x402004(p="p[0]=*p값"의 주소)                                 0x7ffc2dbe71e3(q = echo의 주소)//echo주소출력(스태틱,스택)



**int형도 포인터로 만들수있나? 없다. 문자열만 특혜

일단 배열로 만들고 초기화 int p[]={1,2,3}; (선언할때 초기화 안하면 루프돌려야함)

**char *p = "echo"; printf("%c",*(&p+1));

이러면 &p값 자체가 포인터자체주소를 가르키고 1을 더하면 연산규칙에의해

타입이 char** 즉 8바이트를 더하는것이다. 즉 8바이트떨어진 쓰레기값을 읽어온다

만약 %s면 엉뚱한주소기 때문에 세그먼트폴트가 뜬다


**&연산자는 임시포인터 생성기다

int num =1; printf("%p",&num+1); 하면 당연히 &num이 int*타입이니까 

"가리키는 대상"(int)의 크기인 4바이트를 건너뛴다


[tcp_daytimecli]

sockaddr_in상자는 함수가 생성해주는게 아닌 내가 앞마당에 만드는 것이므로 포인터가 아닌 그저 변수를 사용한다. 이 상자에는 상대방의 정보를 작성한다. 크기는 16바이트로 고정이다(in_addr는 4바이트 고정) servent,hostent는 현실의 정보를 담기때문에 유동적이다

struct sockaddr_in {

    short sin_family;   // 2바이트

    unsigned short sin_port; // 2바이트

    struct in_addr sin_addr; // 4바이트

    char sin_zero[8];   // 패딩

};

내가 만드니까 초기화도 내가 한다. bzero