HTTP GET 명령을 통해 얻은 html 문서, 중간에 끊김

cabjaewon의 이미지

소켓 프로그래밍에서 원격의 웹 서버에 80번 포트를 이용해 접속한 후, html 문서를 읽어오려고 하는데.. 원래 문서를 다 읽어오지 못하고 계속 끝부분이 잘린채로 읽혀집니다.

해당 사이트의 html 원문을 브라우저의 소스보기를 이용해 보면, 1550 라인 정도인데.. 소켓 프로그래밍을 이용해 해당 사이트 웹 서버의 80번 포트로 "GET / HTTP/1.0\n\n" 을 메세지로 보내고, recv() 를 이용해 읽어온 결과는 항상 1440 라인 정도?

왜 끝부분이 잘려나가는지랑.. 어떻게 하면 다 읽어올 수 있는지 알고 싶습니다.

참고하시라고 아래 코드의 일부를 화면 캡쳐해 올려드립니다.

희안한 것은..

while((numbytes=recv(sockfd,buf,MAXBUFSIZE,0)) != 0) 이 부분을 아래와 같이

while((numbytes=recv(sockfd,buf,MAXBUFSIZE,0)) != -1) 로 바꾸어 주면 다 읽어오긴 합니다.

그런데.. 무한 루프가 돕니다.

그 결과, Ctrl-C 를 이용해 프로그램을 강제 종료 시킨 후, 웹 서버로부터 읽어온 html 문서를 저장하는 파일을 열어보면, 해당 사이트의 html 문서가 100% 읽혀온 후.. 빈 줄이 한 10만개 넘게 찍혀 있습니다.. (빈 줄은 루프를 돌 때마다 한 줄씩 추가되게 있습니다.. 루프가 10만번 넘게 돌았다는 말이지요..)

정상적인 경우에도 recv 해 오는 바이트 수가 0 인 경우가 있다는 얘기 아닌가요?
recv() 의 리턴값은 읽어온 바이트 수 아니면.. 에러발생시 -1 인데.. 원래 html 문서의 끝에 도달했는지를 어떻게 인식할 수 있을까요?

0 바이트를 받은 횟수가 일정 횟수 이상이 되면 종료되는 것도 시도해 봤는데.. 역시 html 문서를 다 읽어오지 못한채로 종료가 됩니다.

어떻게 할 지 난감하네요...^^;;

김민영.의 이미지

펄만 만져봐서 확실한지는 모르겠지만
루프를 검사한후 값이 널(null)일때 break를 해주면 멈추지 않을까요?
저같은 경우는
while (<>) {
print $_;
if (!$_) { last; # - C의 break와 같은 문항 }
}
처럼 루프를 강제종료시키거든요.

#!/usr/bin/perl
##########################################################
for ($i=0;$i<1;$i++,$i--) {
select(undef,undef,undef, 0.5000),print "사랑합니다.\n" if((!$You_Love_Me)&&($Dead_of_My_Heart));
}

cabjaewon의 이미지

while((numbytes=recv(sockfd,buf,MAXBUFSIZE,0)) != NULL)

로 바꿔서 해 봤는데.. 안 되네요..

김민영.의 이미지

NULL을 비교하였기 때문에
논리부정(!=)이아닌
(==) 를 사용하면 가능하지 않을까요?

#!/usr/bin/perl
##########################################################
for ($i=0;$i<1;$i++,$i--) {
select(undef,undef,undef, 0.5000),print "사랑합니다.\n" if((!$You_Love_Me)&&($Dead_of_My_Heart));
}

dormael의 이미지

제가 c로는 소켓프로그램을 안해봐서 정확한건지 모르겠는데 recv의 리턴값으로 알 수 있는건 [읽은 갯수|오류] 둘중에 하나밖에 없는걸로 man 페이지에 나오는데요.
-1이면 오류라고 하네요.
다른식으로 소켓의 상태를 알아봐야 하지 않을까요?

-- Signature --
青い空大好き。
蒼井ソラもっと好き。
파란 하늘 너무 좋아.
아오이 소라 더좋아.

김민영.의 이미지

제 생각 대로라면
A != NULL //A가 NULL이 아닐경우
A == NULL //A가 NULL일 경우
로 ==를 써서 A의 값이 NULL일 경우 실행하도록 하는게
맞을듯 싶네요

#!/usr/bin/perl
##########################################################
for ($i=0;$i<1;$i++,$i--) {
select(undef,undef,undef, 0.5000),print "사랑합니다.\n" if((!$You_Love_Me)&&($Dead_of_My_Heart));
}

김민영.의 이미지

그리고
루프내에 if문을 따로써서
값을 비교한후에 값이 NULL로 나올때 BREAK를 실행하는건 안될까요?

#!/usr/bin/perl
##########################################################
for ($i=0;$i<1;$i++,$i--) {
select(undef,undef,undef, 0.5000),print "사랑합니다.\n" if((!$You_Love_Me)&&($Dead_of_My_Heart));
}

karkayan의 이미지

HTTP Request를
Get / HTTP/1.1\r\n\r\n 보다
GET / HTTP/1.0\r\n\r\n 이라든지
GET / HTTP/1.1\r\n\Connection: close\r\n\r\n
으로 날려보시는 건 어떨까요??

닫힌 커넥션에 대해서 recv를 시도하면 오류가 리턴되지 않던가요??
HTTP/1.1에서는 기본적으로 서버가 Connection을 유지하도록 되어 있기 때문에 recv를해도 오류가 리턴되지 않을 수도 있습니다.
아니면 서버의 응답 헤더부를 보시면 Content-Length 필드가 있을텐데 이 값 만큼만 읽어들이는 방법도 있겠지요.

cabjaewon의 이미지

제가 쓴 글에 오타가 있었네요..
"GET / HTTP/1.0\n\n" 이란 메세지를 보냈습니다.

말씀해주신

"GET / HTTP/1.1\n\Connection: close\n\n"

도 메세지로 날려봤는데... 역시나 안 됩니다..

dormael의 이미지

0바이트 읽혔을때 잠시 쉬는것도 해보셨나요?
그게 10번 이상이면 종료 하면 될듯도 한데..

-- Signature --
青い空大好き。
蒼井ソラもっと好き。
파란 하늘 너무 좋아.
아오이 소라 더좋아.

cabjaewon의 이미지

말씀하신대로 코드 수정해서 돌려봤는데 마찬가지 결과가 나옵니다..

원문의 끝 100라인 정도가 잘린 채로 수집되네요..

첨부화일 참조해 주세요..

dormael의 이미지

밑에 lacovnk 님께서 말씀해 주신대로 http헤더에 컨텐트 크기를 보내오는 경우도 있습니다.
경우에 따라 동적 컨텐트는 크기 헤더가 없을수도 있긴 합니다.
그리고 while루프에서 i++ 해준게 정말 쉬는게 아닐수도 있지 않나요?
sleep같은건 없나요? man페이지에 보면 sleep은 초단위로 밖에 안되는거 같은데.
제가 말한 쉬는거는 네트웍 상황이 안좋을때 버퍼가 채워지도록 시스템에 시간을 할당해 주는건데 저는 java로만 해서 좀 다르게 접근해야 할수도 있겠네요.
제가 c경험이 부족해서 제대로 도움이 안되는거 같네요. ㅡ,.ㅡ

-- Signature --
青い空大好き。
蒼井ソラもっと好き。
파란 하늘 너무 좋아.
아오이 소라 더좋아.

lacovnk의 이미지

1. 동적 컨텐츠를 위해서 chunk 인코딩을 이용합니다. HTTP라면 활용하세요! ㅎㅎ
http://kldp.org/node/54175

2. 저런 루프는 컴파일러가 없앨지도 모릅니다;;

3. MAXBUFSIZE로 할당된 배열의 마지막을 \0 처리 해줘야 하지 않나요? 아니면 recv에서 MAXBUFSIZE-1을 사용하시거나.. 앞에서 잘 처리가 되었나 모르겠습니다. (+1으로 선언하거나 할당..)

4. send도 한번에 다 보내는 것을 가정하셨는데, 그럴 보장이 없는 걸로 알고 있습니다.

일단, 각 recv마다 얻어오는 내용들을 분리해서 한번 살펴보시기 바랍니다... :)

두서 없이 적어서 죄송합니다 -o-;

lacovnk의 이미지

HTTP 헤더에서 size를 알려줍니다.

아니면 chunk로, 여러 덩어리로 보내는데 역시 사이 사이에 다음 덩어리의 크기를 알려줍니다.

이를 이용하면 전체 문서 크기를 알 수 있고, 그 만큼 받아오시면 됩니다.

cabjaewon의 이미지

while 루프 말고, sleep() 을 이용해서 메세지가 도착할 때까지 기다려 주니 문제가 해결되서 html 문서를 다 읽어올 수 있었는데...

몇 번 되다가, 그 후에 다시 잘려서 오네요..

테스트하고 있는 중입니다..

답변들 달아 주셔서 감사합니다..^^

cabjaewon의 이미지

분명히 html 문서 다 읽어왔었는데...

몇 번 되다가... 또 안 됩니다..

아...........

lacovnk의 이미지

앞에 지적해드린 것은 해결하셨나요?

저의 경우 MAXBUF로 메모리 할당하고 그 크기만큼 recv에 넘겨줘버리면 이상한 결과 나왔습니다.

cabjaewon의 이미지

뭐가 잘못된 건지 도통 모르겠습니다..

무엇보다.. 아래 코드로 해서 돌아갔었거든요.. 해당 웹 사이트의 html 문서를 끝까지 다 읽어 왔었습니다..
그런데.. 되다가 왜 안 되는거죠?
lacovnk 님께서 지적해 주신 문제들 때문이라면, 아예 되지를 않아야 할텐데.. 한 두번 정상적으로 작동하다가 마네요.. 원래 HTTP 의 정상적인 작동 방식인가요?

아직 초보라.. 코드가 좀 엉성하지만.. 참고하시라고 한 번 올려봅니다..
ret_html 이라는 함수를 다른 함수에서 호출해서 사용합니다..

/**************************************************************************************
ret_html.c

DESCRIPTION: retrieves web page from web site which is designated by user in command line
DATE : 2005/2~
**************************************************************************************/

#include "engine_header.h"

//PORT : port number through which we can access target site
#define PORT 80

//MAXBUFSIZE : maximum capacity of buffer for retrieving web page from remote site
#define MAXBUFSIZE 20000

int ret_html(char *target_site,char *msg_to_server,pid_t crawler_pid,int doc_num)
{
int sockfd,numbytes;
char buf[MAXBUFSIZE+1]; //buffer for retrieving web text
struct hostent *he;
struct sockaddr_in their_addr;

char *prefix="txt_from_html"; //prefix to be attached to the output filename
char tmpfile[50]; //Temporary file storing raw html document
char txtfile[50];
//Text file which resulted from temporary html document by eliminating tags

//-------------------------------------------------------------------------------
//Create filename : tmp__.txt for raw html file
// __.txt for tag_eliminated file
//To show which crawler has retrieved that page, attach crawler_pid to filename
//To show document number , attach doc_num to file name
//To show this file is text file, attach expension '.txt' to the file name
//-------------------------------------------------------------------------------

sprintf(tmpfile,"%s%d%s%d%s","html_",(int)crawler_pid,"_",doc_num,".txt");
//is a temporary file name storing raw html document
sprintf(txtfile,"%s%s%d%s%d%s",prefix,"_",(int)crawler_pid,"_",doc_num,".txt");
//is a tag-eliminated text file name

//------------------------------------------------------------------------------
FILE *raw_html_fd; //FILE structure for raw html document
FILE *tag_eliminated_fd; //FILE structure for tag-eliminated html document

if((raw_html_fd=fopen(tmpfile,"w+"))==NULL)
{
fatal("Could not open temporary file for storing raw html document");
}

if((tag_eliminated_fd=fopen(txtfile,"w"))==NULL)
{
fatal("Could not open tag-eliminated file");
}
//------------------------------------------------------------------------------

if((he=gethostbyname(target_site))==NULL) {
herror("gethostbyname in ret_html()");
printf("%d\n",h_errno);
exit(1);
}

if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
fatal("socket in ret_html()");
}

their_addr.sin_family=AF_INET;
their_addr.sin_port=htons(PORT);
their_addr.sin_addr=*((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero),8);

if(connect(sockfd,(struct sockaddr *)&their_addr,sizeof(struct sockaddr))==-1)
{
fatal("connect in ret_html()");
}

int bytes_sent;

if((bytes_sent=send(sockfd,msg_to_server,strlen(msg_to_server),0))==-1)
{
fatal("send in ret_html()");
}

//----------------------------------------------------------------
//Following while loop retrieve numbytes bytes from target site
//Each time search engine retrieves web page from target site,
//----------------------------------------------------------------

int i; //for couting the number of zero bytes receptions

int totalbytes=0; //for total bytes read

while((numbytes=recv(sockfd,buf,MAXBUFSIZE,0)) != -1)
{

//Test ----------------------------------------
//printf("numbytes read : %d\n",numbytes);
//Test ----------------------------------------

if(numbytes == 0)
{
i++;

if(i == 5)
{
printf("\nZero bytes received over the predefined limit\n");
break;
}
else
{
// printf("Zero bytes received\n");
sleep(1);
continue;
}
}
else
{

totalbytes += numbytes;

if(fwrite((void *)buf,sizeof(char),numbytes,raw_html_fd) != numbytes)
{
fatal("fputs in ret_html()");
}

//insert '\n' to output file to avoid 'line too long' error
putc('\n',raw_html_fd);

//Wait for receiving new message from remote web server
//(Essential part for retrieving full html document)
sleep(1);

}
}

//Test ------------------------------
printf("totalbytes : %d\n",totalbytes);
printf("numbytes : %d\n",numbytes);
//Test ------------------------------

//----------------------------------------
//Eliminate tags from raw html document
//----------------------------------------
tag_eliminator(raw_html_fd,tag_eliminated_fd);

//Test --------------
printf("raw HTML file : %s\n",tmpfile);
//------------------

//----------------------
//Close file descriptor
//----------------------
fclose(raw_html_fd);
fclose(tag_eliminated_fd);

//---------------------------
//Close socket descriptor
//----------------------------
close(sockfd);

return 0;
}

cabjaewon의 이미지

네트워크상의 오묘한 특성 때문인 걸로 결론 내렸습니다...

코드 수정 안 했는데도.. 정상적으로.. 100%는 아니고.. 끝의 몇 줄이 잘린채로... html 문서를 수집할 수가 있네요..

그런데 다음이 타당한 관찰인지는 모르겠으나..

if((raw_html_fd=fopen(tmpfile,"w+"))==NULL)
{
fatal("Could not open temporary file for storing raw html document");
}

다른 부분은 하나도 안 건드리고.. 위에서 "w+" 을 "w" 으로 고쳐서 수행을 했는데.. 그 이후에 수집이 제대로 됩니다...^^;; (애당초 fwrite 부분을 건드려봐야겠다고 생각한 건, 소켓에서 읽어오는 시점이랑 읽어온 내용을 파일에 쓰는데 걸리는 시간사이에 뭔가 있지 않을까 하는 생각에서였음 -> 어디까지나 초보자의 관점 )
물론 제가 "w+" 을 "w" 로 고쳐서 컴파일을 한 시점과, 네트워크 사정이 양호해진 시점과 일치했을 가능성도 있습니다..

그런데.. 어떤 사이트는 "w+" 로 파일을 열었을 땐 파일에 안 써졌는데.. "w" 로 파일을 여니까 파일에 써졌습니다.. 몇 번 해 봤는데도 같은 결과가 얻어지네요..

이와 관련해서는 별도로 질문을 올리려구요..

바쁘실텐데.. 답글 달아주신 분들 감사드립니다..^^

superwtk의 이미지

주제에서 벗어난 이야기 이지만, 표준에 의하면 HTTP 헤더에서의 개행 문자는 \n 이 아닌 \r\n 을 써야 합니다.

--------------------------------------------------------------------------------
http://blog.superwtk.com

lacovnk의 이미지

헤더의 개행문자가 아니라, 헤더와 본문 사이에 한줄을 띄워야(\n\n) 하는 것이죠? :)

kewlbear의 이미지

그게 아니라 \n대신 \r\n을 써야합니다. 말씀하신 것도 \n\n이 아니라 \r\n\r\n이 맞습니다.

lacovnk의 이미지

아하! 감사합니다.

핑계: \r\n이 잔뜩 붙어있는 바람에 잘못 읽었군요 :(

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.