블로그 이미지
문슐랭 upip57@naver.com
진짜귀찮음

Notice

Recent Post

Recent Comment

Recent Trackback

Archive

calendar

1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
  • total
  • today
  • yesterday
2017. 2. 12. 17:26 프로그래밍/C 언어

 C언어 소켓프로그래밍 - Http request -  response


허허 .... 이것 저것 하다 보니 포스팅 한다는 것을 까먹고 있었네요..... 죄송합니다... ㅠ

바로 포스팅하도록 하겠습니다!!!


(★ 아... 그리고 해당 소스코드말고 동적으로 http request 를 보낼 수 있게 하고 받아오는 response

     가 클 경우 짤리는 것, 작을시 쓰레기값 출력 이 부분을 수정한 코드도 있으니 포스팅하고 바로

     올리도록 하겠습니다. ㅎ)



참조 사이트 모음


 http://mintnlatte.tistory.com/

☞ http://forum.falinux.com/zbxe/?mid=network_programming

☞ http://m.blog.naver.com/sarah7_2000/80203615486

☞ http://kyh1026.tistory.com/76



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdio.h>
#include <WinSock2.h>
#include <Windows.h>
#pragma comment(lib,"ws2_32.lib")
 
#define BUFF_SIZE 1024
 
int main()
{
    WSADATA wsa;
    struct hostent *host;
    char msg[BUFF_SIZE]; //나중에 결과값을 저장하기 위한 선언
 
    if (WSAStartup(MAKEWORD(22), &wsa) != 0)
    {
        perror("WSAStart Error ");
        system("pause");
        return -1;
    }
 
    SOCKET sock = socket(PF_INET, SOCK_STREAM, 0);
    SOCKADDR_IN addr;
 
    if (sock == INVALID_SOCKET)
    {
        perror("Sock Error ");
        system("pause");
        return -1;
    }
    host = gethostbyname("www.google.co.kr");
 
    addr.sin_family = AF_INET;
    addr.sin_port = htons(80);
    addr.sin_addr.s_addr = inet_addr(inet_ntoa(*(struct in_addr*)*host->h_addr_list));
 
    if(connect(sock,(SOCKADDR*)&addr,sizeof(addr)) == SOCKET_ERROR) 
    {
        printf("Not Connect \n");
        system("pause");
        return 1;
    }
    send(sock, "GET / HTTP/1.1\r\nHost: www.google.co.kr\r\n\r\n", strlen("GET / HTTP/1.1\r\nHost: www.google.co.kr\r\n\r\n"), 0);
    recv(sock,msg,BUFF_SIZE,0);
 
    printf("%s \n",msg);
 
    closesocket(sock);
    WSACleanup();
    system("pause");
    
    return 0;
}
 
cs



1편이 어디서 끝났지?? 라는 분이 계실까봐 1편의 마지막 부분도 살짝 첨부하도록 하겠습니다.



    SOCKET sock = socket(PF_INET, SOCK_STREAM, 0);
    SOCKADDR_IN addr;


SOCKET 변수 로 소켓을 생성해볼까요?


레퍼런스: int socket(int domain, int type, int protocol); 으로 사용됩니다.


 PF_INET 는 IPv4 프로토콜을 뜻함.

 SOCK_STREAM 은 연결지향을 뜻함. (SOCK_DGRAM 은 비연결성을 뜻함)

● 0 은 특정 프로토콜을 사용하는 부분인데 보통 0을 쓴다고 합니다.


socket() 에 관련해 제가 참조한 사이트를 링크 걸겠습니다. 

정말 자세하게 나와있으므로 세부적인 부분은 여기서 공부하시면 될 듯 합니다.



 http://forum.falinux.com/zbxe/index.php?document_srl=429387&mid=C_LIB



struct sockaddr_in{
   short sin_family;
   unsigned short sin_port;
   struct in_addr sin_addr;
   char sin_zero[8]; 

};



레퍼런스 MS 링크


 https://msdn.microsoft.com/ko-kr/library/zx63b042(v=vs.110).aspx



자!! 이제 진짜 2편 으로 가보도록 하겠습니다.



    if (sock == INVALID_SOCKET)
    {
        perror("Sock Error ");
        system("pause");
        return -1;
    }


sock == INVALID_SOCKET 이란 조건을 일단 확인 하실 수 있으실 겁니다.


이 조건은 소켓생성이 실패하였을떄 INVALID_SOCKET 라는 반환값을 반환하게 됩니다 (-1 값)

그렇게 때문에 오류확인을 위해 넣어준 코드 중 하나라고 보시면 되십니다.



host = gethostbyname("www.google.co.kr");



이 구문을 이제 살펴 보도록 하죠. gethostbyname 이라는 레퍼런스를 사용하였네요.

나중에 www.google.co.kr 의 주소를 ip 주소로 변경해 주기 위한 작업중 하나에요.

만약 '네이버' 를 타켓으로 설정하실려면 '네이버' 도메인 주소로 설정해 주면 되겠죠?



struct hostent *host;



host 는 코드상단부분의 이 부분입니다.

그렇다는 것은?! hostent 의 구조체의 내용을 살펴봐야한다는 뜻이 되겠죠?ㅎ

struct hostent { char *h_name; /* Official name of host. */ char **h_aliases; /* Alias list. */ int h_addrtype; /* Host address type. */ int h_length; /* Length of address. */ char **h_addr_list; /* List of addresses from name server. */ #define h_addr h_addr_list[0] /* Address, for backward compatibility. */ };


이제 거의 제일 중요한 설정 부분이라고 할수 있겠습니다.


addr.sin_family = AF_INET;
addr.sin_port = htons(80);

addr.sin_addr.s_addr = inet_addr(inet_ntoa(*(struct in_addr*)*host->h_addr_list));


포스트 상단에서 확인 하실 수 있드시 socketaddr_in 구조체를 사용한 부분 입니다.



struct sockaddr_in{ short sin_family;            // 인터넷 주소체계 IPv4 or IPv6 unsigned short sin_port;    // Port 주소 struct in_addr sin_addr;    // ip 주소를 나타내는 구조체 char sin_zero[8];         // sockaddr 과 같은 크기 유지

};


이런 구조를 가지고 있구요 상단보다 더 자세하죠? 지금 표현하는게 나은 것 같아서요 ㅎ


addr.sin_family 주소체계를 나타난다고 했죠? 저희는 IPv4 를 사용하니 AF_INET 를 사용해 주는 겁니다.


주소쳬계 및 프로토콜 체계는 아래의 블로그에서 확인해 주시면 되실 것 같습니다.

(AF_INET 와 같은 것들)


링크: http://bbolmin.tistory.com/25



addr.sin_port = htons(80);


addr.sin_port 포트를 지정해주는곳이니 포트를 지정해주도록 합니다.


그런데 왜 htons 라는 레퍼런스를 사용할까요? 그것은 바로 PC 와 네트워크의 데이터 저장 방식이 다르기 때문입니다.


PC의 경우 리틀엔디안 방식으로 데이터를 저장하게 됩니다.

네트워크의 경우 빅엔디안 방식으로 데이터를 저장하게 됩니다.


(구글에 해당 방식을 검색하면 자세히 나오더라구요 ㅎ 제가 포스팅하는거보다 더 이해하시기 편하 실 것 같습니다. 절대 귀찮아서 그런것이 아닙니다!! ㅎ)


저희는 PC 에서 데이터를 저장하고 있기 때문에 리틀엔디안 방식으로 저장이 되죠 그렇기 때문에 빅엔디안 방식으로 변경해 줘야할 필요가 있습니다.


htons() 함수가 그 역활을 담당하고 있죠. 그렇기에 사용해 주는 것 입니다.



send(sock, "GET / HTTP/1.1\r\nHost: www.google.co.kr\r\n\r\n", strlen("GET / HTTP/1.1\r\nHost: www.google.co.kr\r\n\r\n"), 0);


부분의 차례군요 보기만 해도 짜증나시죠?... 이 부분만 하면 90퍼센트 끝입니다. 조금만 더 해보도록 하죠!!


(일단 이 부분은 정형화 되어있기 때문에 저거 그대로 쓰시면 됩니다.)


host 에서 지정해 준 도메인 주소를 socketaddr_in 구조체의 ip 부분으로 옮겨줍니다.

그리고 inet_ntoa 함수를 이용해 주소값으로 변환을 시켜주게 됩니다.


자세한 내용은 아래 링크를 참조해주세요.


링크 : http://mintnlatte.tistory.com/272


이제 끝입니다...!!


send(sock, "GET / HTTP/1.1\r\nHost: www.google.co.kr\r\n\r\n", strlen("GET / HTTP/1.1\r\nHost: www.google.co.kr\r\n\r\n"), 0);

recv(sock,msg,BUFF_SIZE,0);


부분 밖에 남지가 않았네요.


send() 레퍼런스 부터 알아보도록 하죠.

request 구문을 서버로 쏴주는 역활을 하는 작업입니다.


(구문중에 \r\n 이 있는데 이 것이 뜻하는 것은 엔터를 뜻하는 것 입니다.)


int send(int s, const void *msg, size_t len, int flags); 의 형태로 사용이 되어집니다.



자세한 내용은 아래 링크를 참조해주세요.


링크 : http://forum.falinux.com/zbxe/index.php?document_srl=441104&mid=C_LIB



recv() 레퍼런스도 알아보도록 하죠.


int recv(int s, void *buf, size_t len, int flags); 형태로 사용이 되어지고요.


이 함수의 특징은 찾기가 힘드실꺼 같아서 제가 직접 간략하게 포스팅을 하도록 하겠습니다.


recv 의 기능은 send 함수로 request 를 보내면 respond 를 받아오는 역활을 담당하고 있습니다.  해당 레퍼런스를 보시면 버퍼사이즈 만큼의 크기만 받아오도록 설정이 되어있습니다.


만약 버퍼크기를 1024로 가정을 한다고 해보도록 하겠습니다. 그렇다면 받아오는 respond 의 크기가 1024 사이즈보다 크면 어찌할까요?? 의아해 하실겁니다 아마.....ㅎㅎ


recv 의 특징은 만약 respond 가 123456789 라고 가정을 하고 버퍼크기는 5라고 가정을 하겠습니다.

.

그럼 12345 를 받아오게 되겠죠? 6789 는 그럼 버리게 될까요?? 그게 아닙니다. recv 는 계속해서 6789 를 가지고 있습니다.


그러면 이제 슬슬 감이 잡히실 겁니다 2048 의 크기의 respond 가 들어오면 반복을 이용해서 나머지 부분도 출력을 해주면 되겠죠?


그럼 또! 의아점이 생깁니다. 크기를 모르니깐 무한루프를 돌려야하고 크기가 끝이면 나오는 루틴이 필요한데 이건 어찌 해결을 해야하냐?! 라는 의아점이죠.


recv 의 특징 중 또다른 하나를 사용하면 해결이 가능 합니다.


recv 의 반환값을 살펴보는 것이죠. 만약 respond 의 크기가 50 이면 50을 반환하고 60이면 60을 반환하게 됩니다. 그리고 아무런 값이 존재하지 않을때는 0 을 반환하게 됩니다.


이 특징을 딱 보니 감이 오시죠?!! 무한루틴에 0을 반환하면 탈출! 이라는 루틴을 설정해주면 완벽하게 respond 를 출력 할 수 있게 되는 것이죠.


(상단의 코드는 그 부분이 존재하지 않아 짤리려서 나오게 됩니다. 짤리지 않는 소스는 추후에 업로드 하겠습니다.)



아래 부분은 열어주엇던 소켓들을 닫아주는 작업 입니다. ㅎ

후... 드디어 끝이 났습니다!!!


printf("%s \n",msg);

closesocket(sock);
WSACleanup();
system("pause");
    
return 0;


posted by 진짜귀찮음