패킷 파싱의 방법...

3권에서 새로 도입된 네트웍 및 멀티플레이어 프로그로그래밍 섹션을 위한 게시판입니다.

Moderator: 류광

Locked
비회원

패킷 파싱의 방법...

Post by 비회원 »

패킷 구조가 보통 아래와 같이 구조를 가지는 것으로 알고 있는데요.

Code: Select all

struct header
{
	int size;
	int	id;
	//.. 
};

struct body : public header
{
	//....
};
얼마전까지는 서버상에서 패킷을 읽을때 Socket layer에서는 가능한한 읽을수 있는 만큼 읽어서 별도의 큐 등에 집어 넣고 별도의 파싱을 통해서 패킷을 구분하는게 당연하다고 생각하고 있었습니다.

아래와 같은 구조와 비슷할거라고 생각됩니다.(실제 돌아가는 코드는 당연히 아니며, 구조만 봐주세요.. :oops: )

Code: Select all

::socket layer
wait_recv()
{	
	buffer_info.buf = buffer;
	buffer_info.len = max_buffer;
	recv(socket, buffer, buffer_info, ....);
}
complete_recv(socket, async_result)
{
	queue.enqueue(async_result.buffer, async_result.len);
	wait_recv();
}


::application layer
while( queue.peek(header) == complete )
{
	if( queue.peek(body) == complete )
	{
		queue.dequeue(message);
		dispatch(message);
	}
}
그런데, 얼마전 팀원 중 한분이 직접 C#으로 만든 네트웍 모듈을 볼 기회가 있었는데 이분의 코드 구조는 아래와 같은 구조를 가지고 있었습니다.

Code: Select all

wait_header_recv()
{		
	buffer_info.buf = buffer;
	buffer_info.len = header_size;
	recv(socket, buffer, buffer_info, ....);
}

wait_body_recv()
{		
	buffer_info.buf = buffer;
	buffer_info.len = header.bodysize;
	recv(socket, buffer, buffer_info, ....);
}

wait_recv()
{
	if( recv_header == true )
		wait_header_recv();
	else
		wait_body_recv();
}

complete_recv(socket, async_result)
{
	async_result.buffer, async_result.len);
	if( recv_header )
	{
		if( message.verify_header(async_result.buffer, async_result.len) == ok )
		{
			recv_header = false;
			wait_recv()
		}
		else
			.. wait_remain_header;
	}
	else
	{
		if( message.verify_body(async_result.buffer, async_result.len) == ok )
		{
			dispatch(message);
			recv_header = true;
			wait_recv()
		}
		else
			.. wait_remain_body;
		
	}
}
socket layer에서 header만큼 읽고, header가 정상적인 구조이면, body크기만큼 읽어서 하나의 message를 만들어서 처리를 하고, 다시 header를 읽어 내는 과정을 반복하는 구조입니다.
(물론 실제코드는 위와 같지는 않습니다. 제가 임의의 의사코드 비슷하게 만든겁니다...)

저 구조를 보고, 별도의 큐처리는 없어지고, 구조가 간결해 지는 대신, 성능상의 이슈가 있지 않겠는냐고 물었더니, 지금까지 문제 없이 잘쓰고 있었다고 합니다.(거의 십만 단위의 유저의 트랜젝션을 감당하는 서버에서 직접 사용되고 있고 잘 돌아 간다고 합니다(connection이 "십만"은 아닙니다 :) ) )

그래서 조금은 찜찜하지만, 그냥 넘어갔다가, 최근 boost::asio를 보고 있는데, chat sample코드의 구조가 위와 같네요.

실제 예제는 아래와 같습니다.

Code: Select all

 void handle_read_header(const boost::system::error_code& error)
  {
    if (!error && read_msg_.decode_header())
    {
      boost::asio::async_read(socket_,
          boost::asio::buffer(read_msg_.body(), read_msg_.body_length()),
          boost::bind(&chat_session::handle_read_body, shared_from_this(),
            boost::asio::placeholders::error));
    }
    else
    {
      room_.leave(shared_from_this());
    }
  }

  void handle_read_body(const boost::system::error_code& error)
  {
    if (!error)
    {
      room_.deliver(read_msg_);
      boost::asio::async_read(socket_,
          boost::asio::buffer(read_msg_.data(), chat_message::header_length),
          boost::bind(&chat_session::handle_read_header, shared_from_this(),
            boost::asio::placeholders::error));
    }
    else
    {
      room_.leave(shared_from_this());
    }
  }
보면 hadle_read_header에서는 recv handler를 handle_read_body로 handle_read_body는 hadle_read_header를 handler로 해서 switch하고 있습니다.

좀 정신없게 얘기한거 같지만, 여기서 문제

위와 같은 구조로 서버단 패킷을 처리 하면, 생길수 있는 문제는 어떤게 있을가요?
또는 실제 위와 같은 방식으로 패킷처리를 하고 계시는 분들이 계시면, 그 분들의 의견도 들어보고 싶습니다.

감사합니다.
aser0070
Posts: 103
Joined: 2001-10-15 09:00
Location: if

Post by aser0070 »

일단은 메세지에 대한 처리를 리시브 스레드에서 처리함으로, 메세지 처리 쪽에서 스레드 동기화 부분에 신경써줘야 한다 정도일듯 한데요.
그리고 리시브 스레드가 한개 이상이라고 한다면,,
스레드 스위칭에 의해 나중에 온 메세지가 먼저 처리될 경우도 생기겠네요. 보통 큐를 쓰는게 순차적인 처리를 하기 위한 것도 있으니까요. 만약에 나중에 들어온 메세지가 먼저 처리되도 상관없다면 위에처럼 해도 될듯 한데요.
비회원

위하고 아래하고 차이

Post by 비회원 »

위의 방법은 네트워크 리시브 쓰레드를 여러개 돌릴때 써야되는 방법이고
아래의 방법은 네트워크 리시브 쓰레드를 하나만 쓸때 써야되는 방법입니다.

뭐 꼭 반드시 그렇다는건 아니지만 미묘한 차이 때문에 구분지어 쓰는게 좋습니다.

위의 방법에서 메시지 큐를 썼는데,
이는 사실상 'OS 차원에서 구현되어있는 네트워크메시지버퍼'의 '사용자 차원에서의 재구현'에 불과하거든요 -_-;
아래방법에 비해 그냥 불필요한 오버헤드만 오히려 더 안고 가는거죠.

대신 네트워크 리시브 쓰레드를 여러개 한다면 저런 방법으로 해서 분산이 가능할것입니다
(사실 그래야할 경우가 거의 없겠지만요; 혹은 'OS의 네트워크메시지버퍼구현'이 너무 구려서 '사용자차원'에서 만드는게 더 빠르다면)
비회원

Post by 비회원 »

글쓴이 입니다....

제 문장력이 형편이 없나 봅니다... 두분이 답변해 주셨는데, 전혀 의도와는 다른 답변을 해 주시는네요 T_T

두분 답변 감사합니다.

그런데 제가 정말 궁금한건 적당히 최적화 된 "사용자메시지버퍼(또는 큐등)"가 빠를까요? "socket::recv"가 빠를까요? 입니다. ;;;

ps. 모든 기준은 다중리시브 쓰레드를 기준으로 합니다. 또한 쓰레드를 여러개 쓴다고 해도, 메시지 순서에 대한 보장은 문제가 되지 않는걸로 알고 있습니다.

감사합니다.
xster
Posts: 214
Joined: 2006-10-30 10:56

Post by xster »

단순히 Body 를 얻는 곳까지의 성능이라면
첫번째 방식이 복사가 한 번 더 일어나야 하기 때문에 좀 더 느리지 않을까요?
첫번째 방식의 경우 데이터를 필요한 만큼 받는 것이 아니라 여러번 잘린 채로 받고
그 조각들을 큐에다 넣는 방식인 것 같은데 그렇게 되면 나중에 메시지를 구성하면서
복사가 발생하게 되어 문제가 될 것 같습니다.

두번째 방식은 받을만큼의 버퍼를 미리 마련해두었기 때문에 버퍼의 복사가 발생하지 않아서 조금 유리하지 않을까 생각됩니다.
비회원

;

Post by 비회원 »

글쓴분,

어차피 하나의 자원(네트워크버퍼)을 획득하는데
왜 무리해서 쓰레드를 여러개만들어 서로 자원을 획득시키는데 경쟁(lock machanism)을 시키죠?
(이미 OS layer에서 해당 네트워크버퍼에 LOCK이 걸립니다)

'멀티쓰레드 네트워크 프로그래밍'은
'읽는작업'을 멀티쓰레드화 시키는게 아니라
'처리하는작업'을 멀티쓰레드화 시키는겁니다.
('하나의 리시브쓰레드'에서 '워킹쓰레드풀'로 자료 전달)
aser0070
Posts: 103
Joined: 2001-10-15 09:00
Location: if

Re: ;

Post by aser0070 »

비회원 wrote:글쓴분,

어차피 하나의 자원(네트워크버퍼)을 획득하는데
왜 무리해서 쓰레드를 여러개만들어 서로 자원을 획득시키는데 경쟁(lock machanism)을 시키죠?
(이미 OS layer에서 해당 네트워크버퍼에 LOCK이 걸립니다)

'멀티쓰레드 네트워크 프로그래밍'은
'읽는작업'을 멀티쓰레드화 시키는게 아니라
'처리하는작업'을 멀티쓰레드화 시키는겁니다.
('하나의 리시브쓰레드'에서 '워킹쓰레드풀'로 자료 전달)
읽는것을 싱글로 한다해도 처리하는쪽에서 패킷을 얻어오기 위해서는 lock이 필요합니다.
이쪽에 걸든 저쪽에 걸든,, 그건 짜는사람 스타일일과 용도에 따라 다를듯 한데요.
비회원

Re: ;

Post by 비회원 »

비회원 wrote:글쓴분,

어차피 하나의 자원(네트워크버퍼)을 획득하는데
왜 무리해서 쓰레드를 여러개만들어 서로 자원을 획득시키는데 경쟁(lock machanism)을 시키죠?
(이미 OS layer에서 해당 네트워크버퍼에 LOCK이 걸립니다)

'멀티쓰레드 네트워크 프로그래밍'은
'읽는작업'을 멀티쓰레드화 시키는게 아니라
'처리하는작업'을 멀티쓰레드화 시키는겁니다.
('하나의 리시브쓰레드'에서 '워킹쓰레드풀'로 자료 전달)

음..제 의견은 읽는 작업도 멀티쓰레드화 시켜야 한다고 생각합니다.

서버같은 경우를 예를 들면.. 동접이 5000명이고 초당 1번씩만 날려도 5000개의 패킷이
날라옵니다. 그럼 하나의 쓰레드로 5000개의 소켓에서 하나씩 받는것보다.
여러개로 동시에 빼는게 낫지 않을까싶습니다.
그리고 소켓 내부 버퍼는 연결당 있는데. 각각에서 빼올때도 락이 걸리는지... 모르겟습니다.
네트웍카드로 들어온 데이터가 우리의 소켓 내부 버퍼에 복사될때 그 소켓 버퍼에 있는것을 빨리
꺼내가지 않으면 소켓 버퍼가 풀나서 데이터가 더 이상 받지 못하는 사태가 벌어지기도 합니다.

결론은..
하나의 소켓에서 데이터를 받는데 그걸 멀티로 돌리냐 아니냐면.. 그냥 하나로 돌려도 상관없다고 생각하지만
다수의 소켓에서 데이터를 받는다면 멀티로 돌리면 훨씬 효율적일거 같습니다.:)
비회원

Post by 비회원 »

단순히 Body 를 얻는 곳까지의 성능이라면
첫번째 방식이 복사가 한 번 더 일어나야 하기 때문에 좀 더 느리지 않을까요?
첫번째 방식의 경우 데이터를 필요한 만큼 받는 것이 아니라 여러번 잘린 채로 받고
그 조각들을 큐에다 넣는 방식인 것 같은데 그렇게 되면 나중에 메시지를 구성하면서
복사가 발생하게 되어 문제가 될 것 같습니다.

두번째 방식은 받을만큼의 버퍼를 미리 마련해두었기 때문에 버퍼의 복사가 발생하지 않아서 조금 유리하지 않을까 생각됩니다.
첫번째 방식으로 했을때 예상되는 부하는 메시지 버퍼로의 복사와 완성된 메시지를 확인하기 위한 부하정도 일거라고 생각됩니다. 대신 하나의 완성된 메시지를 받기 위한 SOCKET RECV는 최대 1번이 될수 있겠죠. TCP특성상 여러번이 될수도 있겠지만요...
두번째 방식은 별로의 메시지 버퍼에 대한 관리 이슈는 없어지겠지만, 하나의 완성된 메시지를 받기 위한 SOCKET RECV는 최소 2번 이상(Header, Body)이 되어야 됩니다.

복사가 한 번 더 일어나는게 SOCKET RECV한번 더 하는거 보다 얼마나 빠를까 하는게 저의 의문 사항입니다.
(여기서 아시겠지만, 제가 이 쓰레드를 연 사람입니다. :oops: )

직접 테스트를 해 봐도... PC한대로 테스트 하는걸로는 딱히 차이를 알기 힘들어서 혹시 실제 두번째 방법과 비슷한 방식으로 서비스하시는 분이 계시다면 의견을 좀 들어 보고 싶어서 입니다.

성능차가 그리 크지 않는다면 두 번째 방법으로 서버를 구성해 보고 싶어서 입니다.
글쓴분,

어차피 하나의 자원(네트워크버퍼)을 획득하는데
왜 무리해서 쓰레드를 여러개만들어 서로 자원을 획득시키는데 경쟁(lock machanism)을 시키죠?
(이미 OS layer에서 해당 네트워크버퍼에 LOCK이 걸립니다)

'멀티쓰레드 네트워크 프로그래밍'은
'읽는작업'을 멀티쓰레드화 시키는게 아니라
'처리하는작업'을 멀티쓰레드화 시키는겁니다.
('하나의 리시브쓰레드'에서 '워킹쓰레드풀'로 자료 전달)
제가 위의 글에서의 용어 상의 혼란이 있네요.

"리시브쓰레드" 라는게 실제 SOCKET RECV를 수행하는 쓰레드를 얘기하는 것이 아닌가요? 일반 적으로 IOCP에 GetQueuedCompletionStatus()를 실행 시키는 워커 쓰레드를 "리시브 쓰레드"라고 얘기 하지 않나요? 이게 맞다면, 여러개의 쓰레드를 사용하는거 아닌가요?) 아니면 싱글쓰레드로 실행이 되는 어떤 역할을 하는 "리시브쓰레드"라는게 존재하는건가요? 이해가 잘 안됩니다 .-_-
비회원

Post by 비회원 »

저의 경우에는 질문하신 분이 말씀하신 두 가지 방법 모두를 써보았습니다. Windows IOCP 를 사용했고요. IOCP 에 걸리는 TCP 의 Recv 는 Issue / OnRecv 처리가 Lock 없이 원래 구조적으로 Serialize 가 되도록 되어 있으니 둘 의 차이는 Issue 횟수와 버퍼 관리가 다 인 듯 싶습니다.

처음 작성한 서버에서는 Header, Body 를 나눠서 이슈하는 두번쨰 방식을 써보았고요. 패킷량이 조금 빡쎈 캐주얼 게임에서 무리없이 (저렴한 스펙의 서버에서) 대당 동접 일만명 정도는 처리했었습니다. 그 다음에 작성한 서버에서는 첫번째 방법을 사용했습니다. 이 방법의 장점이 버퍼관리만 잘 해주면 길이가 짧은 여러 패킷이 몰려 오는 경우 한 번의 Recv 콜에 여러 패킷을 처리할 수 있다는 점입니다. (물론 Recv 콜 자체가 단순하게 소켓의 Recv Buffer 에서 데이터를 가져오는 정도의 비용이겠지만요) Circular Queue Buffer 같은걸 쓰면 버퍼 재복사 비용을 아낄 수 있습니다.

개인적인 취향으로는 저는 첫번째가 좋습니다만 첫번째나 두번째나 기본적으로 보장해주는 성능이 좋기 때문에 무얼써도 괜찮지 않을까 싶네요. : )
derrican
Posts: 10
Joined: 2005-02-15 09:55
Location: Bluecat

Post by derrican »

위와 아래의 차이에 대해서는 많은 분들의 답변으로 문제가 해결된 것 같네요...


그런데...
밑에 asio 예제는, 질문 내용과는 무관한 것 같습니다.. -_-;

바디가 몇 바이트인지 확인해야 스트림에서 가져올 수 있으니까,
헤더를 먼저 읽는 예제인 것 같습니다.

헤더 읽고 바디 크기를 확인하고, 다시 바디를 읽을 비동기 오퍼레이션을 불러주고,
해당 크기만큼 쌓여서 컴플리션 됐으면, 방에 메세지 뿌려주는 루틴 같습니다.

asio 예제는 무관한 것 같으니 신경쓰지 않으셔도 될 것 같습니다.
비회원

Post by 비회원 »

derrican wrote:그런데...
밑에 asio 예제는, 질문 내용과는 무관한 것 같습니다.. -_-;
질문 내용과 무관 하지 않습니다.

asio예제와 같은 방법의 효용성에 대해서 질문 한것이었습니다.
ikpil
Posts: 98
Joined: 2008-11-12 21:17

Post by ikpil »

저도 어제 아시오 보고 "이렇게 파싱하면, 확실히 큐잉이 필요 없겠구나" 라고 생각 했었습니다.
실제로 테스트 해보지 못해 어떤 문제점이 생기겠다는 것은 모르겠습니다.
비회원

Post by 비회원 »

두번째꺼가 더 느리지 안나여
헤더랑 바디랑 따로따로 recv하는 만큼 recv시스템 콜 호출수가 많아질텐데요...

그리고 받은 데이터가 자신이 정의한 헤더 형식이 아닌
다른 이상한 데이터가 들어온다면 그거 예외 처리하는데
또 그만큼 recv호출 횟수가 늘어날텐데요..
비회원

Post by 비회원 »

비회원 wrote:두번째꺼가 더 느리지 안나여
헤더랑 바디랑 따로따로 recv하는 만큼 recv시스템 콜 호출수가 많아질텐데요...

그리고 받은 데이터가 자신이 정의한 헤더 형식이 아닌
다른 이상한 데이터가 들어온다면 그거 예외 처리하는데
또 그만큼 recv호출 횟수가 늘어날텐데요..
이상한 데이터가 오면 그냥 끊는게 정석이 아닌가여?

recv가 시스템 콜이라고 생각되어지지만 실제로는 유저모드에서 그냥 간단히 버퍼에서 가져오는것에
불과하지 않나 싶습니다

혹시라도 커널모드로 빠지든가 해서 쓰레드가 바뀐다든지의 그런 오버헤드는 없을겁니다
recv가 대단한 부하의 호출인것처럼 느껴지신다면
한번 테스트 해보세요 ,
그저 일반 큐에서 메모리 가져오는 정도의 부하보다 조금더 오버헤드가 걸리는 정도에 한표 던집니다
비회원

Post by 비회원 »

비회원 wrote:
비회원 wrote:두번째꺼가 더 느리지 안나여
헤더랑 바디랑 따로따로 recv하는 만큼 recv시스템 콜 호출수가 많아질텐데요...

그리고 받은 데이터가 자신이 정의한 헤더 형식이 아닌
다른 이상한 데이터가 들어온다면 그거 예외 처리하는데
또 그만큼 recv호출 횟수가 늘어날텐데요..
이상한 데이터가 오면 그냥 끊는게 정석이 아닌가여?

recv가 시스템 콜이라고 생각되어지지만 실제로는 유저모드에서 그냥 간단히 버퍼에서 가져오는것에
불과하지 않나 싶습니다

혹시라도 커널모드로 빠지든가 해서 쓰레드가 바뀐다든지의 그런 오버헤드는 없을겁니다
recv가 대단한 부하의 호출인것처럼 느껴지신다면
한번 테스트 해보세요 ,
그저 일반 큐에서 메모리 가져오는 정도의 부하보다 조금더 오버헤드가 걸리는 정도에 한표 던집니다

이상한 데이터가 오면 그냥 끊는게 정석이 맞나요?
저는 잘 모르겠습니다.

recv는 파일 i/o작업의 하나니까 시스템 콜 맞습니다 단순 버퍼만 옴기는 작업만한다고해도 그전에
커널단에서 해당 파일의 접근관련(보안쪽포함) 처리와 i/o동기화를 위해서 락을건다던가 하는 작업을 할텐데요..
제가 말한부분은 이런부분을 얘기한겁니다.(메모리 카피 보다 파일 i/o가 더느린건 아시죠?)

굳이 테스트까지 안하고 단순히 생각해봐도
송신측에서 10바이트 짜리 패킷을 100개 보냇다고 했을때 패킷의 헤더,바디 처리수만큼 recv를 200번 호출하는거보단
그냥 1000바이트짜리 recv 1번호출하는게 더빠르겟죠
mastercho
Posts: 587
Joined: 2004-05-09 20:37

Post by mastercho »

운영체제마다 다르겠지만

recv 의 구현을 쉽게 생각해보면, 단일 CPU하에서는 락 자체가 필요없을테고요
[ 제가 운영체제 책들을 읽은 지가 꽤 오래되어서 확실하지 않지만 (유닉스 관련 OS책이었는데 확실한 기억은... )
굳이 락이 필요 없게끔 설계 한다고 본거 같습니다 , 유저모드 상에서 스레드 교체가 아니라서요]
아니라면 그냥 바로 밑으로 --;]


멀티 CPU하에서는 커널 메모리에서 유저메모리로 간단한 버퍼 복사 정도기에 아마 락이 있다해도 스핀락정도 있을것으로 추측됩니다

게다가 그 락도 소켓마다 있을것으로 추측되기에 실제로 락이 진짜로 걸리는 경우는 거의 없을거라 보여집니다

실제로 테스트 해보면 그 결과를 쉽게 알수 있지 않을까 하네요

위에 1번과 200번 차이라 비교를 하신건 잘못된거라 보여지고요


원문에 recv 1번과 2번의 차이에서 원형 버퍼를 운영하는 비용을 따져볼때 , 심플하고 간단한 recv2번 호출
구현이 더 나을거 같다는 생각이 저도 듭니다

테스트는 실무에서 소켓을 쓰시는분이 올려주지 않을까 싶네요 ;)
비회원

Post by 비회원 »

mastercho wrote:운영체제마다 다르겠지만

recv 의 구현을 쉽게 생각해보면, 단일 CPU하에서는 락 자체가 필요없을테고요
[ 제가 운영체제 책들을 읽은 지가 꽤 오래되어서 확실하지 않지만 (유닉스 관련 OS책이었는데 확실한 기억은... )
굳이 락이 필요 없게끔 설계 한다고 본거 같습니다 , 유저모드 상에서 스레드 교체가 아니라서요]
아니라면 그냥 바로 밑으로 --;]


멀티 CPU하에서는 커널 메모리에서 유저메모리로 간단한 버퍼 복사 정도기에 아마 락이 있다해도 스핀락정도 있을것으로 추측됩니다

게다가 그 락도 소켓마다 있을것으로 추측되기에 실제로 락이 진짜로 걸리는 경우는 거의 없을거라 보여집니다

실제로 테스트 해보면 그 결과를 쉽게 알수 있지 않을까 하네요

위에 1번과 200번 차이라 비교를 하신건 잘못된거라 보여지고요


원문에 recv 1번과 2번의 차이에서 원형 버퍼를 운영하는 비용을 따져볼때 , 심플하고 간단한 recv2번 호출
구현이 더 나을거 같다는 생각이 저도 듭니다

테스트는 실무에서 소켓을 쓰시는분이 올려주지 않을까 싶네요 ;)
cpu가 i/o처리하는것도 아닌데 파일 i/o에 cpu락과 cpu갯수가 먼상관이져...
말씀 대로 recv구현 을 쉽게 생각해 보면 i/o호출ㅤㄷㅚㅆ을때
먼저 해당 파일이 커널내 존재하는지와 접근 가능성(보안)을 검사하고
i/o에 따른 버퍼자원을 얻어와서 락을 걸고 i/o작업후
다시 시스템 에자원을 반납하는 형식으로 돌아갈텐데 i/o호출이 많아지면
이런거도 당연히 늘어나잔아요..

우리가 recv를 구현한게 아니니 테스트를 통해 결과를 보자는건 좋은생각입니다만
호출 횟수가 두번째꺼가 많은건 당연하기 때문에 굳이 테스트할 필요없이 생각만으로
1번과 200번 호출같은 예시를 들었는데 그게 왜 잘못된건가요? 설명좀...

구현이 쉬운점에서는 두번째꺼가 더쉬운거는 저도 동의합니다만
질문자 의도는 성능이 뭐가 좋냐입니다.

자꾸 첫번째꺼가 낫다는 식의 '추측'만 쓴다면 질문자나 이글을 보는 다른분들도
헷갈려 하실텐데요..
Zeprod
Posts: 480
Joined: 2006-11-04 16:24
Location: Creaty Networks
Contact:

Post by Zeprod »

네트워크 버퍼도 결국 메모리상에 있으므로, 버퍼에 더 많이 접근한다고 느려지는 것이 아니라, 단순히 함수 호출에 대한 부담만 절약합니다.

객관적으로 봤을때 실행시간은 첫번째 예제가 더 짧을 것입니다. 버퍼를 한번에 가져온다기보다, 함수호출 부하가 더 적다는 것이죠.


하지만, 네트워크 버퍼도 크기 제한이 있습니다. 그 제한을 넘어가는 데이터를 받아오는 경우라면 첫번째 코드는 문제가 생길수밖에 없는 구조입니다.



사실 보통 예제 프로그램들에서는 거의 차이가 없습니다. 함수호출에 의한 성능차이도, 버퍼가 넘칠많큼 데이터를 받게 되는 경우도 거의 없기 때문입니다.



단지, 네트워크 서버를 만든다던지 대용량 파일을 전송하는 P2P 등을 만들때 그 차이가 들어나게 됩니다.



이 때문에 데이터가 모두 들어올때까지 네트워크 버퍼에 쌓도록 기다리지 않고,

들어오는 족족 가능한 모두 버퍼에서 빼내 따로 마련한 공간으로 옮겨주는 두번째 코드가 범용성이 있는 것입니다. 그 덕분에 이 코드가 많이 사용되는 것이기도 하죠.
세상이 기다리는 나만의 SHOW!
----------------------------------------------
Zeprod 홈 : http://Zeprod.org
Project. Creaty : http://Creaty.net/
Creaty 게임제작 커뮤니티 : http://Creaty.net/game/
----------------------------------------------
mastercho
Posts: 587
Joined: 2004-05-09 20:37

Post by mastercho »

cpu가 i/o처리하는것도 아닌데 파일 i/o에 cpu락과 cpu갯수가 먼상관이져...
IO 작업한 내용을 메모리에 누가 올리는것일까요? 일단 메모리 접근은 CPU의 동시 접근이 고려될수
있다는 차원에서 말한것입니다

메모리에 올리는 작업은 모두 CPU가 해주는겁니다


제가 잘못 이해한건지 모르겠는데 ,
설마 recv가 IO 작업처리를 한다고 생각하시는건가요?

수많은 네트워크책를 보면

recv는 이미 IO처리가 끝나 이미 소켓 버퍼에 올라와 있는 메모리만을 가져오는
역활만 수행한다고 나와 있습니다

윈도우도 TCP/IP 스텍은 BSD 유닉스 커널에서 가져온것으로 아는데
따라서 , 일반적인 소켓처리와 크게 다르지 않을거라 추측이 됩니다
말씀 대로 recv구현 을 쉽게 생각해 보면 i/o호출ㅤㄷㅚㅆ을때
먼저 해당 파일이 커널내 존재하는지와 접근 가능성(보안)을 검사하고
i/o에 따른 버퍼자원을 얻어와서 락을 걸고 i/o작업후
다시 시스템 에자원을 반납하는 형식으로 돌아갈텐데 i/o호출이 많아지면
이런거도 당연히 늘어나잔아요..
다시 말씀 드리지만 소켓과 파일처리는 엄연히 다른겁니다

소켓과 파일 처리가 I/O 인터페이스 차원에서는 비슷하기에
유닉스에서는 파일헨들과 소켓핸들를 똑같이 취급할수 있기는 합니다만
내부 구현은 완전 다른거죠

한마디로 소켓은 생성되면 위와 같은 작업을 해줄 필요가 없을겁니다
TCP/IP 스택 처리에 관해 파일 처리와 혼돈하고 계신게 아닌가요?

만약 저런 처리가 이루어진다해도 , 소켓이 생성될때 하지 recv 할때마다 이루어지진 않습니다
오히려 부하는 소켓의 생성 소멸에서 많이 걸린다 생각하면 될거 같고요


TCP/IP 단에서 보면 처음 질문자님의 첫번째 처리나 2번째 처리나 똑같습니다
프로세스 단에 이미 올라와 있는 버퍼를 가져오는 방법만 조금 다를뿐이지요

커널에서는 소켓에 메세지가 오면 프로그램에서(유저모드)에서 recv를 하지 않아도 자동적으로 소켓 버퍼에 "복사"
를 합니다 recv는 이미 프로세스안에 복사되어진 그 버퍼를 그저 가져오는 역활밖에 하지 않는거고요


우리가 recv를 구현한게 아니니 테스트를 통해 결과를 보자는건 좋은생각입니다만
호출 횟수가 두번째꺼가 많은건 당연하기 때문에 굳이 테스트할 필요없이 생각만으로
1번과 200번 호출같은 예시를 들었는데 그게 왜 잘못된건가요? 설명좀...
제가 이해를 못한건지 모르겠는데 , 헤더와 바디 를 읽는것( 보통의 경우 2번이면 되니까 )에서 2번이라
한것이 왜 200번까지 거론되는건지 알수가 없는겁니다

200번은 함수 호출 횟수만으로도 엄청난겁니다 메세지 처리하는데 대단히 오바한거라 봅니다
그래서 비교가 잘못된거라 생각했습니다
Locked