TCP/IP 소켓 통신으로 파일전송중 sleep 이나 prinf 같은걸로 파일은 받는쪽에서 딜레이를 안주면 쓰레기값이 쓰이는 경우 때문에 질문드립니다.

ehaakdl의 이미지

클라이언트 쪽에서 서버에 전송을 하면 서버는 파일 열어서 받은거 쓰는형식인데 클라이언트가 전송하는게 느리다 보니 서버에서 읽는 속도가 빨라서????라고 생각이 들긴 하는데 recv로 어차피 받는거면은 클라이언트가 다 처리를 하고 send 처리 해서 보내는건데 클라이언트가 보내기 전에는 recv할게 없어서 블로킹이 되지 않나요? 이상하게도 못받은 변수 끝까지 읽어서 쓰레기 값이 파일에 쓰이는 경우 떄문에 문의 드립니다.

해당 소스코드 입니다.

while (0 < (nRead = recv(hClntSock, (char*)&test, sizeof(Snd_File_Ptol), 0)))
	{
		printf("%d\n", test.peace_fp_Size);
		fwrite(test.File_Data, sizeof(char), test.peace_fp_Size, fp);
		memset(&test, 0, sizeof(Snd_File_Ptol));
	}
chanik의 이미지

http://man7.org/linux/man-pages/man2/recv.2.html

Quote:
The receive calls normally return any data available, up to the requested amount, rather than waiting for receipt of the full amount requested.

recv() 호출이 성공해도 항상 요청했던 길이의 데이터가 다 읽어지는 것은 아닙니다. 지금의 코드는 항상 데이터가 다 들어왔다고 가정하고 있네요. sleep()으로 충분히 기다려준 다음 recv()하면 덜 읽어질 가능성이 줄어드는 것 뿐이고요.

문제를 해결하려면, 원하는 용량이 채워질때까지 recv()를 반복호출해야 합니다. 호출시마다 조각나서 들어오는 데이터는 적절히 합쳐 쓰셔야 하고, 기확보된 크기만큼 요청크기도 줄여가야죠. 엉성하나마 대충 아래정도로만 고쳐도 될 겁니다. 테스트는 안 해봤습니다.

int req_len = sizeof(Snd_File_Ptol);
char *buf = (char*)&test;
while (0 < (nRead = recv(hClntSock, buf, req_len, 0)))
	{
		if(nRead < req_len) {
			buf     += nRead;
			req_len -= nRead;
			continue;
		}
		printf("%d\n", test.peace_fp_Size);
		fwrite(test.File_Data, sizeof(char), test.peace_fp_Size, fp);
		memset(&test, 0, sizeof(Snd_File_Ptol));
	}
ehaakdl의 이미지

제가 이해가 안되서요

buf += nRead;
이 부분이 포인터인데 가리키는 주소에다가 nRead즉 반환된 바이트수를 대입한다고 이해를 햇는대 저렇게 하면 채워진곳 다음번째에 못 읽어온 데이터가 써진다는 건가여??

chanik의 이미지

이미 파악이 끝났으리라 생각합니다만 뒤늦게나마 올립니다.

질문글을 통해, 블로킹 소켓을 쓰셨고 구조체 단위의 TCP 통신을 하고 있음을 알 수 있습니다. 클라이언트에서는Snd_File_Ptol 구조체를 통째로 send()하고 서버는 이를 수신해서 구조체 내에 정의하신 페이로드를 fwrite()하는 식이겠죠. TCP위에서 HTTP 프로토콜이 동작하듯, Snd_File_Ptol 구조체는 TCP 위에 직접 정의하신 통신 프로토콜의 일부에 해당하겠습니다.

서버에서는 데이터수신용으로 정의하신 Snd_File_Ptol 구조체인 test를 char *로 캐스팅해서 recv() 호출시 수신버퍼로 활용하고 있습니다. 쓰레기값이 파일에 쓰이는 이유는 이전 댓글에 말씀드린대로 recv() 호출 한 번으로 Snd_File_Ptol 구조체가 통째로 test 구조체에 받아질 것이라 가정했기 때문일 것이고, 제가 손댄 부분은 test 구조체를 char * 캐스팅한 buf 포인터를 정의하고 구조체가 다 받아질 때까지 recv()를 반복호출하며 호출시마다 수신된 바이트수만큼 buf를 앞으로 전진시킨 정도 뿐입니다.

즉, 질문글에 있는 코드도 구조체를 char * 캐스팅해서 수신버퍼로 쓰고 있고, 제가 수정한 코드도 마찬가지로 구조체를 char * 캐스팅해서 수신버퍼로 쓰고 있습니다.

buf 포인터는 char * 타입으로 캐스팅된 상태이므로 += 연산자로 값을 더해주면 그 바이트수에 해당하는 만큼 앞으로 전진하게 됩니다. 방금 recv()로 수신된 데이터 바로 다음 offset을 가리키게 되므로 다음번 recv() 호출시엔 기수신데이터 바로 뒤에 연결되어 수신되겠죠. 여기까지 적고 나니 사실상 이 마지막 문단만 적었어도 되겠다는 생각이 드네요. 그런데 이렇게 고치면 문제해결은 되던가요? ^^

chanik의 이미지

위와 같이만 해서는 처음 구조체 하나만 받아 처리하고 이후부터는 오동작하겠군요.
한 구조체 처리가 끝나면 아래와 같이 buf, req_len을 초기화해줘야겠네요.
혹시라도 오동작하는 코드 참조하며 저때문에 시간 뺏기는 분이 생길지 모르니 고쳐둡니다.
(참고: 이것도 테스트없이 올리는 코드입니다)

int req_len = sizeof(Snd_File_Ptol);
char *buf = (char*)&test;
while (0 < (nRead = recv(hClntSock, buf, req_len, 0)))
	{
		if(nRead < req_len) {
			buf     += nRead;
			req_len -= nRead;
			continue;
		}
		printf("%d\n", test.peace_fp_Size);
		fwrite(test.File_Data, sizeof(char), test.peace_fp_Size, fp);
		memset(&test, 0, sizeof(Snd_File_Ptol));
		buf     = (char*)&test;
		req_len = sizeof(Snd_File_Ptol);
	}
ehaakdl의 이미지

이게 구조체라서 음...

라스코니의 이미지

temporary buffer에 썼다가 구조체에 복사해 주면 됩니다.

structure FMT fmt;
unsigned char buf[100000];
while (recv(buf))
...
memcpy (buf, (unsigned char *)&fmt, ...);

익명 사용자의 이미지

감사합니다.

vagabond20의 이미지

TCP/IP 프로그램 할때 흔히 쓰이는 방식이 바로 이처럼 temporary buffer 에 읽어들이다가 원하는 만큼 (즉 구조체 정의에서 정해진 길이만큼) 받아서 읽어들이면 구조체로 넘겨 처리하게 하는 것입니다.

서로 동기화한 클라이언트와 서버가 메세지를 주고 받는다면 기다렸다가 주고, 받은 다음 응답을 보내 확인 시키지만, 그럴 경우 속도가 느려지기 때문에 보내는 쪽은 받는쪽에 상관없이, 즉 비동기식으로 계속 보내고 받는쪽은 알아서 정해진 길이 만큼 끊어서 구조체로 옮겨 다음 프로세스에 넘기는게 리얼타임 프로세싱의 한 모델입니다.

주식거래 예를 들면, 증권사로 모아지는 투자자의 주문이 다시 거래소 등 중앙 시스템으로 몰려서 (즉 여러 증권사들의 실시간 주문이나 조회 트랜잭션들이 '쏟아져') 들어오는것을 이런식으로 버퍼를 이용해서 구조체에 맞게 만들어진 큐에 마구 마구 밀어 넣고, 그 큐의 반대쪽에서 트랜젝션을 하나씩 빠르게 처리하는 그딴 프로그램 짜서 돌리던거 생각나네요.

그리고 이 방식은 정보를 증권사에 뿌려줄때도 사용했었는데.
아직도 이 방식이 사용언어만 다를뿐 계속 쓰이는것 같습니다.

여의도자바

Necromancer의 이미지

blocking socket 인가요?

blocking socket에서 데이터 없을때 recv() 하면 들어올때까지 대기합니다. 들어오면 리턴하는데 이때 읽기 원하는 요청량보다 들어온 데이터가 적으면, 들어온 데이터 양만 들어 있습니다. 예를 들어 1000byte recv() 요청했는데 100byte만 있다 그럼 100이 리턴되고, 담을 버퍼에는 100byte만큼만 유효한 데이터가 들어 있습니다. 도착하지 않은 나머지 900byte는 또 recv() 대기해야 합니다. 요청하면 요청한 만큼 다 들어오는 파일읽기와는 이부분이 다르고요.

non-blocking socket이면 데이터 들어 있을때 recv()하면 blocking sock과 똑같고, 데이터 없을 때 recv() 하면 바로 -1 리턴하고 errno에 EWOULDBLOCK이나 EAGAIN이 있습니다. 이경우는 정상 처리하고 들어올때까지 계속 대기해야 하는데 이때 쓰는게 select(), poll() 등등 동기화 함수 입니다.

여기에 추가해서 데이터 기다려도 안오거나 일부만 오는 경우 있습니다. 이경우에 대한 TIMEOUT을 두고 처리할 필요가 있고, TIMEOUT시 대응할 로직도 또 준비하셔야 합니다. 예를 들어 1000byte 요구했는데 990byte만 오고 나머지 10byte TIMEOUT 넘어서도 안온다. 그럼 어떻게 할것인지. 이건 프로그램 제작하시는 분이 알아서 판단 하셔야겠죠.

Written By the Black Knight of Destruction

vagabond20의 이미지

socket programming 의 친숙한 단어들 :
blocking, non-blocking, EWOULDBLOCK, EAGAIN, select(), poll(), TIMEOUT..
*
socket 위에 SOAP 을 써서 통신할때도 이러한 방법을 씁니다.
(소시적) 경험상, (저처럼) 구름잡듯 말로 설명했을때와, Necromancer 님 처럼 좀 더 구체적으로 설명했을때, 이것을 얼마나 빨리 소화하고 구체적인 솔루션을 맹글어서 쓰느냐가 관건이겠습니다요.

때로는 코딩까지 예로 달라는 사람들도 (바로 제가 그러했슴 ㅎㅎ) 종종 봅니다.

*
그나저나 이 늙은이 (여의도 자바), 일욜날 엄청 심심한가보네..

여의도자바

익명 사용자의 이미지

급하다보니 문제를 해결해 달란식으로 가버렸네요..ㅠㅠ 제 문제점이 문제를 직시 하지 않고 누군가 해결해 주길 바라는거 때문에 프로그래밍할때도
문제를 회피 하다 큰코 다친 경험이 많았습니다. 요번 글로 좀 반성좀 해야것어요.ㅠㅠ

댓글 달기

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