64kb 넘는 데이터를 write() 할때 패킷로스가 납니다.

wegaia의 이미지

안녕하세요.
로컬(리눅스)에 있는 JPEG 이미지들을 읽어서 tcp 소켓으로 원격 ftp 서버에 업로드하는 프로그램을 짜고 있는데요.
64kb 보다 작은 이미지들일때는 문제없이 보내지는데 그보다 클 경우에는 write()에서는 에러가 나지 않는데 받은 이미지들을
익스플로러나 파이어폭스로 보면 이미지 하단이 깨져보입니다. alsee 같은걸로는 아예 보이지 않구요.
이게 스티븐스 책보니까 tcp 소켓 버퍼크기의 제한때문인거 같은데요. 64kb 이상은 SO_SNDBUF 로 늘려봐도 별 효과가 없네요.
ftp 서버도 제가 짠다면 새로운 옵션을 주면 되지만 서버쪽은 여러상용 서버를 이용해야 하는 판국이라서요.
write()할때 데이터가 큰 경우는 쪼개서 write()해보는 꼼수를 써봤는데 안되네요.
어떤 방법이 있을지 조언 좀 부탁드려도 될까요..
미리 감사를..

jick의 이미지

read할 때 EOF가 (결과값 0) 나올 때까지 반복해서 read하셨나요?

wegaia의 이미지

제가 짜는건 ftp 서버에 업로드만 하는거라서 write() 만 하거든요.

brucewang의 이미지

먼저, 언급 하신 SO_SNDBUF 내용을 보니 어떻게든 제대로 된 방향으로 정보를 찾아보신 것 같습니다.

전에 어떤 분의 socket통신 문의에 간단하게 설명드렸는데, 제 설명만 보면 어쩌면 wegaia 님처럼 의문이 들 수 있겠다는 생각이 들었습니다.

그래서 몇가지 간단하게 정리해야 할 것 같습니다.

먼저, TCP window 에 대해서 알아보셔야 할 필요가 있습니다. 간단히 말씀드리면 (또 오해의 소지가 있지만) TCP window size란 receiver가 받아들일 수 있는 데이터 버퍼의 크기라고 생각하시면 편하겠습니다. 그 사이즈를 초과하는 데이터를 보내면, 버퍼가 꽉 찬 다음 그 버퍼 내용을 수신 프로그램이 다 소비해서 빈 공간이 생기기 전까지는 보내는 데이터가 제대로 들어오지 못하게 됩니다.

다음 내용들이 도움이 될 것 같습니다.
http://www.tcpipguide.com/free/t_TCPWindowSizeAdjustmentandFlowControl.htm
http://en.wikipedia.org/wiki/Transmission_Control_Protocol
http://proj.sunet.se/E2E/tcptune.html (OS별 TCP window size 설정 변경 방법)

또 다음 링크에서는 rick jones라는 사람이 보다 명확하고 쉽게 window size를 설명해 주고 있네요.

Quote:

http://fixunix.com/tcp-ip/66633-understanding-how-calculate-tcp-window-size.html
There are, in broad handwaving terms, three things that limit how much
data is "in flight" at one time on a TCP connection.

The first is what I like to call the "classic" TCP window - it is the
"receive" window advertised by the receiver, and is seen in the TCP
header. Indeed, it is maintained by the receiver.

The second is the congestion window. It is the _sender's_ best guess
as to how much data can be in flight at one time without overloading
some component between the sender and the receiver. It is purely a
figment of the sender's imagination :) (as it were) and does not
appear on the wire in the TCP header.

The third I call the "SO_SNDBUF" window. TCP must retain a reference
to the data it has sent until it receives an ACK from the remote.
Generally, the place where TCP keeps this data is the socket send
buffer, and that is controlled in part by setsockopt() calls setting
SO_SNDBUF - typically bounded by some sort of system configuration
limits. If TCP has no place to keep the reference to the data, it
cannot send it, so it can send no more data at one time than it can
track.

So, the maximum quantity of data outstanding at any one time will be
the minimum of those three things.

rick jones

어쨌든, TCP window size 보다 작은 단위로 loop를 돌려 전송해야 안전합니다. 게다가 TCP window size는 OS마다 약간씩 그 기본 사이즈 가 다르며, 또한 변경도 가능하므로 그때그때 다르기때문에 어떤 특정 값을 언제나 기대할 수는 없습니다. 따라서 깊숙한 부분까지 완벽하게 확인하고 처리하시려다보면 코드가 어려워질 수 있고, 사실 이런 복잡한 처리는 오히려 코드의 가독성과 유지보수에 전혀 도움이 안됩니다 (저의 경우..)

그래서 간단하게 '적당한' 사이즈로 잘라서 전송해야 하는데, 그 적당한 사이즈는 무엇이냐...

먼저 Path MTU라는 것에 대해 알아보십시오. 여러 네트워크 장비(랜카드,라우터, 모뎀 등..)마다 전송 가능한 최대 데이터 크기가 있습니다. 그것이 다 다르며, 전송자와 수신자 사이에 존재하는 이 장비들 중 가장 적은놈의 사이즈가 전송자/수신자 사이의 path MTU가 됩니다.

일반적으로 우리가 사용하는 이더넷의 경우 1500바이트가 최대 크기입니다.
(http://www.geocities.com/SiliconValley/Vista/8672/network/ethernet.html#A19)

그러니까 대부분 Path MTU의 사이즈는 1500 바이트 아래 일 것이고, OS별로 TCP window의 크기가 다 다르지만 대충 인터넷의 자료들을 보니 64kb 정도가 '기본' 최대 크기 인것 같군요.

저의 경우 그러니까 대~~충~~~ 4kb정도를 최대 버퍼 크기로 잡고 전송을 합니다...

write()도 무조건 loop하는게 아니라, send가능한 시점인지 체크해서 (select 함수등을 사용) 아직 상대방이 데이터를 받아들이지 못하겠다 싶으면 잠시 대기해 주면 더 좋겠습니다.

저의 허접 답변이 조금이라도 도움이 되었다면 좋겠네요...
-------------------------------------------------
$yes 4 8 15 16 23 42

-------------------------------------------------
$yes 4 8 15 16 23 42

wegaia의 이미지

답변 감사드립니다.
님의 말씀중에 select()로 send가능한 시점인지 체크하는것은 소켓이 writable 한지를 검사한 후에 보내라는 의미인가요?
그렇게 했는데도 여전히 똑같은 증상이군요.
아래가 대략의 코드입니다 길진 않구요.
접속함수에서 전송버퍼 사이즈를 60K 정도로 잡습니다. brucewang 님 말씀처럼 8k,4k 잡고도 해봤는데요.
그럴때는 아예 파일 중간부터 겹쳐지는듯 깨져서 나오네요.
미리 감사를..

전송함수 :
int split_unit = 10*1024; // 쪼개서 보내는 단위
fd_set ww_set;
int max_fd;
struct timeval rtimeout;
int s;

max_fd = data_fd;
printf("too big frame, slicing\n");
while(1)
{
FD_ZERO(&ww_set);
FD_SET(data_fd,&ww_set);

rtimeout.tv_sec = 0L;
rtimeout.tv_usec = 100*1000;

if((s= select(max_fd+1,NULL,&ww_set,NULL,&rtimeout)) < 0)
{
perror("ftp_lib: select()");
return -1;
}

if(s == 0) // time out
{
continue;
}
// if(s>0 && FD_ISSET(data_fd,&ww_set))
// printf("writeable ok\n");

// split_unit 단위로 전송
if(rlen >= split_unit)
wlen = write(data_fd, file_ptr+offset,split_unit);
else
wlen = write(data_fd, file_ptr+offset,rlen);

if(wlen < 0)
{
printf("wlen < 0\n\n");
return -1;
}
offset += wlen;
rlen -= wlen;

if(rlen == 0)
{
break;
}

}

아래는 SO_SNDBUF 설정을 하는 함수입니다.

업로드를 위해 ftp에 접속하는 함수(Passive Mode라서 데이터를 보낼때 서버쪽으로 connect 해야됨)
/* control connection or data connection */
int Connect_FTPserver(unsigned long addr, __u16 port)
{
struct sockaddr_in servaddr;
int fd;

int ret, sock_buf_size;
if ( (fd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("can't create socket\n");
return -1;
}

sock_buf_size = 60*1024;

ret = setsockopt( fd, SOL_SOCKET, SO_SNDBUF,(char *)&sock_buf_size, sizeof(sock_buf_size) );

memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = addr;
servaddr.sin_port = htons(port);

if (connect(fd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){
printf("can't connect\n");
return -1;
}
return fd;
}

brucewang의 이미지

코드상으로는 특별히 문제가 될 것은 없어 보이는데요...

그러니까 정리하자면, "전송함수에서는 아무 문제 없이 전송이 완료되었는데, 서버에 업로드 된 파일을 보니 내용이 이상하다" 라는 것인가요? 그리고 FTP 서버는 직접 만드신것이 아니라 잘 알려진 서버로 이미 가동중인것이고요..?

결정적으로, 그 테스트중인 FTP서버에 다른 FTP client로 64kb이상의 이미지를 올릴경우 정말 아무 문제가 없던지요? 그냥 확인 질문입니다. (혹시나 FTP서버의 upload limitation관련 설정 이슈일까 싶어서..)

-------------------------------------------------
$yes 4 8 15 16 23 42

-------------------------------------------------
$yes 4 8 15 16 23 42

wegaia의 이미지

안녕하세요.
에러도 안나고 아무 이상없이 전송완료가 됫는데도 불구하고 업로드된 이미지 파일의 하단부가 깨져보입니다.
첨부파일처럼요. 물론 64K 이하에서는 깨끗하게 전송되구요.
FTP 서버는 두개 확인했습니다. Serv-U 하고 Xlight..
지금 다시 확인해봤는데 다른 클라이언트로 올릴 경우는 멀쩡하구요.
지금은 혹시나 해서 커널쪽 호출 함수들 따라댕기면서 쳐다보고 있네요.
점점 갑갑시러워지고 있네요 ㅠㅠ..
우쨋든 도움 감사드립니다~

PS: 사진은 로스트의 로크(? 본지가 좀 되서)인가요 ^^

댓글 첨부 파일: 
첨부파일 크기
Image icon Corrupted_image.jpg67.03 KB
brucewang의 이미지

제 답변이 실질적으로 도움이 된건 하나도 없던 것 같네요.. 죄송스럽고, 저도 정말 결말이 궁금해 집니다.

그런데, 커널 쪽 함수를 보시는 것 보다는
가능하다면 sender쪽/receiver쪽 패킷 덤프를 일반 클라이언트 사용시와 코드 사용시 두개를 비교해보시는 것도 좋지 않을까요.
처음에는 FTP 내부 명령어쪽에 뭔가 차이가 있을지 모르겠다고 생각했지만 그것도 아닐 것 같고 아무튼 의미는 있는 시도가 아닐까 생각합니다.

성공하시면 소식좀 알려주세요.

PS> 네 '로크' 맞습니다..

-------------------------------------------------
$yes 4 8 15 16 23 42

-------------------------------------------------
$yes 4 8 15 16 23 42

wegaia의 이미지

깜빡깜빡하는 머리지만 함 올려볼게요 ㅎㅎ..
해결은 될려나 근데 -_-;

gamgi의 이미지

코드상으로 보아서는 write가 한번만 호출이 되도록 되어 있는것 같은데요?
혹시 while 루프를 돌면서 rlen이 초기화되는 것 아닌가요?
소켓전송부의 전체 코드를 보여주셔야 정확하게 알 수 있을 것 같네요..

wegaia의 이미지

루프를 돌면서 쪼개보내는 단위만큼 줄어듭니다.

gamgi의 이미지

jpg 파일을 읽어서 보내는 것 같은데, 파일을 읽어들인 메모리를 체크해 보셨나요?
메모리에 쓰레기 값이 들어가 있는 건 아닌지요?
다른 경우이긴 하지만, dsp 프로그램을 사용했을 때 memcpy의 속도가 느려서, 데이터가
깨지는 경우가 있었습니다.

소켓 전송시에 다른 문제가 없다면, 원데이터를 의심해봐야 할 것 같네요.

정태영의 이미지

file_ptr (변수 이름이 좀 의미와 다르게 쓰이는 느낌이네요.) 에 데이타가 제대로 들어가있는지부터 의심해보세요. 저같으면 소켓에다가 write 하지 않고 그냥 file 에 write 한 뒤 제대로 되는지부터 체크해보겠습니다.

그리고 nonblocking I/O 를 사용하지도 않는 것 같은데, 구찮게 select 같은걸 쓸 필요는 없어보입니다.

do {
  wlen = write(data_fd, file_ptr+offset,rlen);
 
  if(wlen == -1)
  {
    // errno 를 확인해보고 어떤 에러인지를 print
    switch( errno ){
      // blahblah
    }
    return -1;
  }
 
  offset += wlen;
  rlen -= wlen;
 
} while( rlen > 0 );

위 코드 정도면 되지 않을까 싶은데요...

--
오랫동안 꿈을 그리는 사람은 그 꿈을 닮아간다...

http://mytears.org ~(~_~)~
나 한줄기 바람처럼..

오랫동안 꿈을 그리는 사람은 그 꿈을 닮아간다...

http://mytears.org ~(~_~)~
나 한줄기 바람처럼..

simpid의 이미지

제가 그동안 공부하고 개발하고 테스트했던 경험으로는

전송 오류에 대해 MTU 등을 걱정할 필요는 없습니다.

MTU 보다 큰 데이터를 전송하면 MTU 크기로 알아서 잘 잘려서 보내집니다.
그 모든건 TCP 가 알아서 하게됩니다.

MTU 크기는 성능에 관련된 문제이지 전송 안정성에 대한 문제는 아닌걸로 알고있습니다.

서버/클라이언트 코드나 기타 잘못된데 없는지 확인해 보셔야 할것 같습니다.

wegaia의 이미지

근데 오랫동안 별문제 없이 써오던 코드거든요.
거기다 60k를 넘어서면서 일어나는 일이라 혹시나 그문제가 아닌가해서요.
스티븐스의 유닉스 네트웍 프로그래밍 책에서도 전송버퍼(윈도우 크기)보다 한꺼번에 더 보내면 수신하는 쪽에서
나머지 부분을 버린다고 되어 있어서(Section 7.5 page.191 하단,SO_SNDBUF 부분) 혹시나 그런 문제가 아닐까 생각해본건데요.
아직까진 이래저래 헷갈리고 있네요.

simpid의 이미지

버리긴 버립니다.

단지 UDP 만요.

TCP 는 그렇지 않습니다.

haewoo의 이미지

정확히 원하는 값만 전송이 되는지 tcpdump와 같은 tool을 이용해서 그 값을 확인해 보는 것도 좋은 방법이 될것 같습니다.

wsong의 이미지

깨졌다는 걸 보면, 적는 부분에서 문제가 있는거 같은데요..
2개의 이미지를 보내시고, 바이너리 에디터로 보내기전 원본하고 올려진 파일을 바이너리 데이터로 비교해서, 동일한 패턴이 보이면, 실마리가 보일듯한데요.

codepage의 이미지

제 경험으로는 TCP Window Size는
Application layer와는 아무 상관이 없습니다.
보내고 받는 쪽에서의 동기화 문제인 것 같은데
그냥 send..receive하지 말고 상태 체크해가면서 해보시죠..

wegaia의 이미지

위의 writeable 상태인지 보는 코드와 write()후의 리턴값 체크..이정도 외에 또 뭐가 있을까요.

brucewang의 이미지

아직 해결 되지 않았나요?

궁금해서 방금 위 댓글에 올려주신 jpg 파일을 보았더니 10b40 byte 이후로 잘못된 값들이 들어가 있군요 (0으로 채워진).

haewoo 님과 wsong 님도 말씀해 주신 것 처럼 패킷 덤프를 확인해 보시고, 혹시 전송루틴에 전송되는 데이터 자체에 문제는 없는지 확인 해 보시는게 좋을 것 같습니다.

그리고 simpid님과 codepage님이 제 첫 댓글의 오해소지 부분을 잘 지적해 주셨습니다. TCP에서 window 가 꽉 차서 congestion이 일어나더라도 그를 해결하는 TCP레벨에서의 방법으로 '중간에 연결이 끊어지지 않는이상' 어떻게든 재전송 등의 방법으로 TCP는 데이터를 그대로 보내도록 '노력' 합니다 (이 노력하는 시간동안 socket레벨에서는 block이 되겠죠). --> 즉, wegaia 님의 전송루틴에서 아무런 예외가 발생하지 않고 보내졌다는 것은 전송 데이터 자체, 혹은 데이터 수신쪽에서 문제가 있을 수 있다는 것인데 후자의 경우는 다른 FTP클라이언트에서는 문제가 없는것으로 봐서 전송루틴에 넘겨지는 원본 데이터 쪽에 더 의심이 가게 하네요.

꼭 후기 올려주세요~ 넘 궁금해요. ^^

-------------------------------------------------
$yes 4 8 15 16 23 42

-------------------------------------------------
$yes 4 8 15 16 23 42

모지리의 이미지

아직 해결되지 않은것이면요 전송 루틴 소스만 파일로 업로드 해놔주세요.

TCP 전송에서 버퍼라던지 이런것을 USER APP에서 신경 써줄 내용은 아닙니다.
99% 전송 루틴 잘못입니다. 만약 파일을 전송한다면...

1. 파일 읽어 들이는 루틴 잘못
2. 데이터 전송시 쓰기 가능아닌데 무조건 쓰기(리턴값뿐만 아니라 에러 번호도 확인)
등등 다양합니다.

IsExist의 이미지

strace 로 바이너리 동작을 추적해 보세요.
---------
간디가 말한 우리를 파괴시키는 7가지 요소

첫째, 노동 없는 부(富)/둘째, 양심 없는 쾌락
셋째, 인격 없는 지! 식/넷째, 윤리 없는 비지니스

이익추구를 위해서라면..

다섯째, 인성(人性)없는 과학
여섯째, 희생 없는 종교/일곱째, 신념 없는 정치

---------
간디가 말한 우리를 파괴시키는 7가지 요소

첫째, 노동 없는 부(富)/둘째, 양심 없는 쾌락
셋째, 인격 없는 지! 식/넷째, 윤리 없는 비지니스

이익추구를 위해서라면..

다섯째, 인성(人性)없는 과학
여섯째, 희생 없는 종교/일곱째, 신념 없는 정치

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.