소켓프로그램에서 read()와 recv(MSG_WAITALL)의 차이점

thisnome의 이미지

서버쪽에서 클라이언트의 요청을 preforking방식으로 받아서 처리하는 부분을 작업하고 있습니다.
각 Child에서의 Read부분을 다음과 같이 두가지중 어떤것을 사용하는게 더 좋은건지 고민중입니다. (OS는 Redhat 9.0 입니다)

while(total_count < sizeof(DBIF_REQ))
{
	again:
	if((count = read(conn_fd, ((void *) &reqData) + total_count, sizeof(DBIF_REQ) - total_count)) < 0)
	{
		if(errno == EINTR)
			goto again;

		Trace("read() : error");
		return -1;
	}
	else if(count == 0)
	{
		Trace("read() : closed");
		return 0;
	}
	total_count += count;
}

again:
if((count = recv(conn_fd, ((void *) &reqData), sizeof(DBIF_REQ), MSG_WAITALL)) < 0)
{
	if(errno == EINTR)
		goto again;

	Trace("read() : error");
	return -1;
}
else if(count == 0)
{
	Trace("read() : closed");
	return 0;
}

나름대로 클라이언트에서 몇십만건씩 보내어 시간을 측정해봐도 양쪽다 거의 동일한 시간을 보여줍니다. read에서 한번에 받을 수 없게끔 reqData의 길이를 10k정도로 해도 결과는 마찬가지구요.
recv()에서 MSG_WAITALL을 사용했을때 내부에서 어떻게 동작되는지 궁금한데.. 이 차이점을 아시는분 꼭 답변 부탁드립니다.
제가 별거 아닌거것에 괜한 시간을 허비하는건 아닌지 모르겠네요.. :shock:

thisnome의 이미지

답변이 계속 없으셔서 덧붙임니다.

실전에서는 이렇게 block mode로는 사용하시지 않나 봅니다. 아무도 관련된 메세지를 남기시지 않는걸 보면..
prefork 방식 역시 사용되지 않는것 같고.. 쩝~

별 생각 없이 아무거나 하나만 고르면 되는건지.. 아니면 보통 이런식으로 사용하질 않으시는건지 궁금하네요.

이제 퇴근들 할 시간인데.. 주말 다들 잘 보내세요.. :D

verena의 이미지

read는 standard i/o library, recv는 socket library 이것만으로도 상당한 힌트를 얻으실수 있으리라 봅니다.

read를 사용하면 일종에 윈도우의 overlapped와 같은 효과 즉, 운영체제가 read와 관련된 일을 좀더 해주게 된다는거죠. 리눅스는 더 좀더 일해주는부분이 어떤건지는 자세히 살펴보진 않아서 뭐라 말씀 드리진 못하겠네요.

recv는 말그대로 socket library만을 다루게 되겠죠. 그래서 read보단 좀더 light하게 사용하실 수 있겠습니다.

물론 운영체제의 내부 구현에 따라 달라지겠죠. 하지만 중요한건 read는 file에 대해서도 대응되니깐 socket을 파일처럼 쓸 수 있다는게 recv와의 가장 큰 차이점이라 할 수 있겠습니다.

그러면...

kslee80의 이미지

몇몇 경우에 문제가 되는 경우가 있으며,
(1:n 모델이며, Single-Thread 인 경우에는 필히 피해야 합니다.)

글 쓰신분께서는 성능 차이도 없는것 같다..라고 하셨는데...
전 성능 하락을 느꼈습니다 -_-;;;
(특히나, 대량의 데이타 전송시에는 퍼포먼스가 말도 안되게
많이 떨어집니다.)

성능 하락도 거의 없고, 의미 그대로 제대로 동작한다면야..
간단한 C-S 프로그램에서는 자주 애용되지 않을까 싶습니다.

thisnome의 이미지

verena wrote:
read는 standard i/o library, recv는 socket library 이것만으로도 상당한 힌트를 얻으실수 있으리라 봅니다.

read()나 recv()는 둘다 socket library가 아닌지요.
standard i/o library쪽이라면 fgets()나 fgetc()가 아닌가 생각됩니다.

제가 이해한 바로는 두가지 함수의 차이는 소켓의 recv버퍼를 커널이 처리해줄때 어느기본값(socket option에서 설정값) 이상이면 return하느냐, 아니면 recv(WAITALL)에서 설정해 준 양까지 받아야 return하느냐의 차이같습니다.

추가 : 그래서 recv쪽이 커널에서 시간을 좀더 잡아먹지 않을까 싶은데..

thisnome의 이미지

답변을 듣게되어 좋은데 좀더 자세히 설명해주셨으면 합니다.
제가 올린 두개의 예제(read(), recv(MSG_WAITALL))중 어느쪽에 해당되는것인지요..

kslee80 wrote:
몇몇 경우에 문제가 되는 경우가 있으며,

recv를 말씀하시는것인지.. 그리고 대략적으로 그 몇몇 경우의 예라도 들어주시면 좋겠네요.

kslee80 wrote:
(1:n 모델이며, Single-Thread 인 경우에는 필히 피해야 합니다.)

이부분은 read, recv 두가지 다 해당되는것 같은데요.. 아니면 recv쪽만 해당되는 것인지?

kslee80 wrote:
글 쓰신분께서는 성능 차이도 없는것 같다..라고 하셨는데...
전 성능 하락을 느꼈습니다 -_-;;;
(특히나, 대량의 데이타 전송시에는 퍼포먼스가 말도 안되게
많이 떨어집니다.)

recv의 퍼포먼스가 더 떨어지는 것이라고 이해했습니다.

kslee80 wrote:
성능 하락도 거의 없고, 의미 그대로 제대로 동작한다면야..
간단한 C-S 프로그램에서는 자주 애용되지 않을까 싶습니다.

복잡한 C-S쪽에서는 pre fork나 pre thread에서 이런 read나 recv쪽(block mode)은 안 사용하나요?

꼭 답변 부탁드립니다. 다른 분들도 생각을 적어주시면 좋겠구요...

moonzoo의 이미지

recv의 MSG_WAITALL 플래그는 이식성에 문제가 있습니다.

그렇기 때문에 일반적으로 원하는 바이트를 받을때까지

반복해서 받는 방법을 씁니다.

님께서 예제로 쓰신 read의 경우처럼 말입니다.

또한 read와 recv는 별차이가 없는 걸로 알고있습니다.

다만 read가 범용이라면 recv는 소켓에 맞는 여러 옵션을 갖고 있지요.

recv의 마지막 flag를 0으로 준다면 read와 별반 다를바 없이 동작한다고

알고 있습니다. 이 사실은 제가 실험해 본것이 아니라 --;

책등에서 소개된 내용입니다.

verena의 이미지

파일만 해당되진 않죠^^;

kslee80의 이미지

1:n 모델 & Single-Threaded 에서는
주로 select() 나 poll() 을 이용한 동기화를 사용하게 되는데..
이때 recv(MSG_WAITALL) 을 사용할시에는 recv중 다른 소켓의 반응을
처리할수 없기 때문에 문제가 됩니다.
(아시겠지만, recv(MSG_WAITALL) 의 제일 큰 문제점은 메세지를 다 받을때까지 블록 상태가 된다는 점이죠.
Non-blocking 소켓에 MSG_WAITALL 을 사용할수도 있지만, 이렇게 코딩할 경우에는 MSG_WAITALL 을 사용했을시 얻을수 있는 장점이 많이 없어집니다.)

먼저 쓴 글에서 몇몇 경우라는 것중에 하나가 이 경우를 말한 것이였습니다.

그리고, 대량의 데이타 전송시의 퍼포먼스 저하는 recv(MSG_WAITALL) 시에
많이 느꼈습니다. TCP 소켓으로 데이타 전송시에는 한방향으로 전송을 할때가
가장 성능이 좋게 나오는데,(FTP 의 ftp-data 포트가 이 예죠.)
MSG_WAITALL 을 사용하는 경우에는 전송 속도가 절반 이하로 떨어지더군요.
(똑같은 데이타 전송 코드에서 플래그만 줬을때 이랬었습니다.
Client, Server 둘다 Linux 였구, 커널 버젼도 2.4 였습니다. 플래그를 켰을때와
껐을때를 둘다 같은 머신끼리 테스트했으므로, 랜카드에 의한 문제는 없을듯...
Server 는 EtherExpress 100 Pro, Client 는 3c905b 였습니다.)

아..그리고..
read() 와 recv() 의 경우에는 두 녀석의 성능차는 거의 없습니다.
소켓에 대한 read() 콜은 커널 내부적으로 recv() 콜로 대치되는듯 싶더군요.

옥정훈의 이미지

MSG_WAITALL이 당연히 오버헤드가 적지 않겠습니까?
rcv_buffer에 데이터가 들어올 때마다 어플리케이션에게
리턴을 하는 오버헤드도 없고,
또 어플리케이션은 그때마다 남은 사이즈를 다시 계산해서
system call을 호출하는 오버헤드도 없으니까요.
하지만 그것이 성능에 큰 영향을 미칠 것이라고는 생각지 않습니다.
이유는, 사이즈가 1메가인데, rcv_buffer로 1바이트씩 들어온다면
커널 <--> 어플리케이션 간에 100만번의 통신이 필요하겠지만,
정상적인 경우에 1바이트씩 rcv_buffer로 데이터가 들어오진 않을테니까요.
다만 악의적인 클라이언트가 그런 식으로 조작을 할 수는 있을것이므로
굳이 선택을 하자면 2번째 경우가 더 나을 수는 있으리라 생각합니다.

그런데요
질문의 요지는 둘의 성능비교라 약간 다른 이야기이긴 하지만
저라면 위의 둘 중 어느것을 사용할 것인가보다는
blocking에 대비한 대책에 더 치중하겠습니다.

recv()의 세번째 인자,
즉 사이즈만큼의 데이터가 영원히 안 들어올 경우에 대한 대책이죠.
악의를 가진 클라이언트가 1바이트만을 보낸 후
죽지도 않고 sleep을 해 버릴 경우 해당 프로세스는
계속 blocking 상태로 될 것입니다.

그러한 악의적인 클라이언트 수가 무한정 늘어나게 되면
서버 자원의 최대치까지 프로세스가 생성되는데다
그들이 모두 blocking 상태가 되어 더 이상의 프로세스를 생성시킬 수도 없는
상황까지 이를 수 있습니다.

결론적으로 말씀드리면,
제시하신 예만 생각한다면,
즉 1바이트만을 보내고 sleep하는 악의적인 클라이언트가 없다고 확신한다면
MSG_WAITALL이 오버헤드가 약간은 더 적을 것입니다.
하지만, blocking을 막는 대책을 세워서 다시 코딩하게 되면
MSG_WAITALL을 사용할 수 없게 될 것입니다.

thisnome의 이미지

많은 분들이 답변을 주셔서 read, recv(MSG_WAITALL)의 차이점에 대해 알고자 했던 소기의 목적은 달성했다고 해야겠네요.. :D

주제와는 상관없이 정훈님께서 올려주신 답변에 몇가지 의문이 들어 조금 더 적겠습니다.

옥정훈 wrote:
MSG_WAITALL이 당연히 오버헤드가 적지 않겠습니까?
rcv_buffer에 데이터가 들어올 때마다 어플리케이션에게
리턴을 하는 오버헤드도 없고,
또 어플리케이션은 그때마다 남은 사이즈를 다시 계산해서
system call을 호출하는 오버헤드도 없으니까요.
하지만 그것이 성능에 큰 영향을 미칠 것이라고는 생각지 않습니다.
이유는, 사이즈가 1메가인데, rcv_buffer로 1바이트씩 들어온다면
커널 <--> 어플리케이션 간에 100만번의 통신이 필요하겠지만,
정상적인 경우에 1바이트씩 rcv_buffer로 데이터가 들어오진 않을테니까요.
다만 악의적인 클라이언트가 그런 식으로 조작을 할 수는 있을것이므로
굳이 선택을 하자면 2번째 경우가 더 나을 수는 있으리라 생각합니다.

정훈님 말씀이 맞다고 생각되어지나, 실험결과(루프를 대략 3번정도 도는 data양(10k)을 십만번정도 보내었을때의 총왕복시간 및 data당 왕복시간등) 비슷하게 나왔다는점, 그리고 kslee80님의 경험담과는 반대인점.. 이 좀 걸리는군요.. :shock:

옥정훈 wrote:

악의를 가진 클라이언트가 1바이트만을 보낸 후
죽지도 않고 sleep을 해 버릴 경우 해당 프로세스는
계속 blocking 상태로 될 것입니다.

그러한 악의적인 클라이언트 수가 무한정 늘어나게 되면
서버 자원의 최대치까지 프로세스가 생성되는데다
그들이 모두 blocking 상태가 되어 더 이상의 프로세스를 생성시킬 수도 없는
상황까지 이를 수 있습니다.

결론적으로 말씀드리면,
제시하신 예만 생각한다면,
즉 1바이트만을 보내고 sleep하는 악의적인 클라이언트가 없다고 확신한다면
MSG_WAITALL이 오버헤드가 약간은 더 적을 것입니다.
하지만, blocking을 막는 대책을 세워서 다시 코딩하게 되면
MSG_WAITALL을 사용할 수 없게 될 것입니다.

옥정훈님께서 단일 read를 생각하셨다면 맞는 글이지만, 젤 윗글에 나와있는 read쪽과 recv쪽 예제의 경우에는 두 경우 다 악의적인 1 byte코드에 대해 오류를 가지고 있습니다.

select를 사용하지 않는 이런 pre fork나 pre thread에서 read나 recv를 Blocking mode로 사용하는 경우에 1byte코드에 대해 대처방법이 어떤것들이 있을까요?
제 경우에는 socket option에서 SO_RCVTIMEO를 사용해서 해결한적이 있는데 경험상으로나 여기 KLDP에 올라온 글들을 읽어봐도, 정석적인 방법이 아닌것 같더군요.. 문제점이 있었습니다.

어쨋거나 이런 경우의 해결책은 없는건가요? 어느 책이나 이런식의 예제는 다 있던데 결국 해결책이 없다면, 이런 방식은 결국 교육용일 뿐인가요? :D

이제 곧 12월달이네요.. :P
즐거운 한주 보내시길..

옥정훈의 이미지

thisnome님이 바로 위에 말씀하신 첫번째 문제에 대해 먼저 말씀드리면,
MSG_WAITALL을 사용하는 것이 오버헤드가 덜 하다는 것이 제 주장이었습니다.
그런데 실제 thisnome님이 사용해 보니 별 차이가 없더라는 것이죠?
이렇게 이해하면 어떨까요?
MSG_WAITALL을 사용하지 않는 경우의 오버헤드라는 것이
CPU의 입장에서는 너무 작은 분량이라 큰 차이가 드러나지 않는다고
생각할 수 있지 않을까요?

두번째 말씀하신 내용은 스티븐스의 네트웍프로그래밍 13장에
3가지 경우를 제시하면서 잘 나와 있습니다.

댓글 달기

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