|
게임제작 |
[1] |
|
등록일:2018-07-31 11:20:03 (0%) 작성자: 제목:소켓 코딩시 실제적으로 주의해야할 사항 |
|
select(1, NULL, &fd_set, NULL, &time_out)의 결과로서 데이터 send가 가능하다고 된 최초의 시점에... 즉, connect(hSocket, ...)한 다음에
fd_set에 hSocket한 개만 넣고 위와 같이 했을 때 쓸 수 있다고 되면 최초의 쓰기 가능한 상태인지 보아서 최초였으면 OnConnect virtual함수를 호출하면 되겠죠. 따로 위의 함수를 connect되었는지 여부를 체크하기 위해 호출하기 보다는 전체 프로그램의 Idle(혹은WM_TIMER)타임에서 어차피 데이터 전송을 위해 체크해야 하기 때문에 그때 체크해서 해당 소켓 핸들에 대한 최초의 쓰기 가능인지 여부만 검사하면 됨... - 물론 만약을 위해 데이터 리딩을 시도했을 때 데이터가 이미 와 있다면 이미 connect된 경우겠지요. - 가능하면 여러 개의 소켓 핸들 전체에 대하여 한 번에 select함수를 호출해야함... 자세한 내용은 select함수 도움말을 보세요
- 언제 소켓 접속이 끊어진 것입니까? 당연 send/recv 등등의 함수들을 호출 했을 때 오류를 리턴할 경우이겠죠...
접속이 끊어지는 경우는 크게 세가지가 있습니다. 1 - 이쪽에서 접속을 끊은 경우 2 - 상대방에서 접속을 끊은 경우 3 - 네트웍 서브시스템(혹은 인터넷 망사업자) 등등의 오류에 의한 강제 종료 <- 이 경우는 뒤에... 1과 2의 경우는 실제적으론 동일할 경우이겠죠? 아래의 글을 읽어보시면 아시겠지만 둘이 동시에 접속 종료를 요구한 경우도 동일합니다... 제대로 접속을 종료하는 방식은 다음과 같습니다. - 먼저 접속을 종료해야 겠다고 마음 먹은 측에서 데이터를 전부 보냈는지 확인(당연 서로 주고 받을게 없을 때 접속을 끊겠죠 ....;;)
b. 더이상 데이터를 보낼게 없거나 통신/프로그램 오류가 발생하여 접속을 종료해야 하는게 확실한 시점에서 shutdown(hSocket, SEND?)를 호출.... 이후 이쪽에서 데이터를 보내려는 어떠한 시도도 send함수는 오류를 리턴함... c. 그래도 상대측에선 데이터를 계속해서 보내고 있을 수 있으므로 recv 함수를 계속 호출해야함... d. 상대측에서는 아직 이쪽에서 데이터 전송을 그만 둔지 모르기 때문에 recv를 계속 호출하고 있을 것임 e. 이쪽(접속을 종료하겠다고 먼저 마음 먹은 쪽)에서 shutdown(hSocket, SEND?)를 통해 더이상 데이터를 안보내겠다고 한 사실이 상대측에 도착하면 상대측 recv 함수가 0을 리턴함... f. 위 e. 이후에 상대측에선 나머지 데이터를 다 보내거나 그만 둔 다음 적당한 시점에 shutdown(hSocket, BOTH? 혹은 SEND?)를 호출 어차피 상대측에서 SEND를 그만둔 상태이므로 이쪽에서 받든 안받든 차이가 없으므로 BOTH/SEND 아무거나 상관없음... 상대측에선 이 시점에 closesocket()를 불러서 socket핸들을 free하면 됨... g. 이쪽에선 c. 상태가 계속 되다가 recv 함수가 0을 리턴하면 상대방도 접속 종료에 동의한 상태 이므로 이 때 closesocket()를 불러서 소켓 핸들을 free하면 됨... - 위의 a~g까지의 단계를 보면 접속을 먼저 종료하겠다고 한 측은 shutdown(SEND?)를 불러서 상대방에 알리고 상대방도 shutdown(SEND?)를 통해 동의하면 그때 closesocket를 부르면 됨 - 상대방이 접속을 종료하겠다고 먼저 shutdown(SEND?)를 호출한 것을 recv의 return값이 0인 것을 확인해서 알았다면 shutdown(SEND?)한 다음 바로 closesocket를 불러도 됨. - 위와 같이 shutdown함수를 closesocket함수 호출 이전에 불러줘야 제대로된 통신 종료가 되는 것인데 이러한 과정을 graceful shutdown이라고 합니다.
*) 만약 위의 과정(graceful shutdown)을 거치지 않고 그냥 접속 종료하고 싶다고 바로 closesocket()을 호출하면 어찌되나요? 상대측의 recv함수가 0을 리턴하지 않고 SOCKET_ERROR값을 리턴함(물론 운이 좋으면이죠... 대개는 그냥 아무일 없다고 나오죠...) 이러한 경우는 aborty shutdown이라고 해서 접속 라인 상태 이상, 상대방 프로그램/시스템 down 등등과 구별하기가 어렵습니다... 물론 이정도만이라면 그냥 그렇게 쓰셔도 되겠죠. 어차피 서로 빠빠이 하는 상태인데요 뭘... 하지만 당연 이렇게 처리하시면 문제가 발생할 소지가 있습니다. 이러한 경우 상대방 TCP/IP 처리부에서 이쪽에서 접속 종료한 사실을 모르기 때문에 계속 접속이 살아있게 되는 경우가 많습니다.-> 이것에 대한 처리를 하지 않으면 서버 소켓에서 더 이상 클라이언트 접속을 받지 않을 수 있다. (-,.-)- 자세한 내용은 모르겠지만 TCP/IP FIN(FINE==끝?) CODE인가를 서로 송수신해야 하는데 그게 전달이 제대로 안된다는 듯... - 그래서 결론은 바로 다음번 접속 시도때 서로 접속이 안되는 경우가 발생하게 됩니다 _ - 이러한 경우 대개는 시스템을 리부트 해야만 접속이 다시 됩니다. 그전엔 벼라별 방법을 써도 잘 안되더군요. - 혹시 이러한 경우에 TCP/IP Keep Alive 옵션을 켜주면 제대로 접속 종료가 될지는 확인 못했네요... 한 번도 써본 적이 없는 옵션...
*) 네트웍 서브시스템(혹은 인터넷 망사업자) 등등의 오류에 의한 강제 종료는 어찌 처리합니까? *) 위의 graceful shutdown과정을 순서대로 밟고 제대로 처리했는데 바로 다시 재접속을 하면 접속이 안돼요. 어찌된 것인가요? struct linger opt_ling = { 1, 0 }; // ling.l_onoff, ling.l_lingererr = setsockopt(hSocket, SOL_SOCKET, SO_LINGER, (const char*)&opt_ling, sizeof(opt_ling)); *) socket함수를 불러 소켓을 만들고 함수를 부르면 블럭이 됩니다. 어떻게 non-blocked call을 할 수 있죠? 클라이언트나 listen소켓 쪽에선 socket() 함수로 소켓을 만들자 마자 설정하면 됩니다.
당연 서버측에선 accept 혹은 WSAAccept 함수를 통해 얻은 소켓 핸들에 대하여 설정하면 되겠죠
뭐 단순히 소켓 핸들이 만들어지자 마자 항상 linger이나 non-blocked-IO 상태 등을 설정하시면 됩니다.
#ifdef WIN32u_long s_ulOPT = 1;err = ioctl(hSocket, FIONBIO, &s_ulOPT);if (err == 0) { // don't use SOCKET_ERRORreturn TRUE;}#else // !WIN32 - 유닉스 계열 OS들에서...int flags = fcntl(hSocket, F_GETFL);flags |= O_NONBLOCK;err = fcntl(hSocket, F_SETFL, flags);if (err != -1) { // don't use SOCKET_ERRORreturn TRUE;}#endif /* WIN32 */ *) OVERLAPPED IO가 잘 동작하지 않습니다. OVERLAPPED IO 사용시 Write하는 쪽만 사용하면 됩니다. Read하는 쪽까지 사용하기엔 프로그램 구조에 문제가 많습니다. Read야 시간 남을 때 정해진 버퍼 사이즈안에서 계속 무작정 받아 두면 되고, 패킷 처리부에서 시간 날 때마다 가져다 처리하면 됩니다. Q) OVERLAPPED IO결과가 성공이긴 한데 요구 되어진게 100 바이트 인데 50바이트만 보내고 50바이트는 못 보냈다고 나올 수 있나요? A) 현재까진 그런 경우는 본 적이 없습니다. 도큐먼트상으론 가능한데 All Or Nothing로 구현된듯 하네요... Q) select(fd_count, NULL, &fd_set, NULL, ...)을 이용하여 제대로 쓰기 가능 상태를 체크하는데 제대로 되지 않습니다.
마치 OVERLAPPED IO처리가 조금씩 지연되고 있는듯 보입니다. 어찌해야 하나요?
A) select(...) 호출 바로 앞에서 SleepEx(0, TRUE); 를 호출하여 OVERLAPPED IO결과가 프로그램에 전달될 수 있도록 해야 합니다.
필요한 경우 SleepEx() 함수 안에서 바로 OVERLAPPED IO콜백 함수가 불립니다.
Q) OVERLAPPED IO의 사용에 가장 큰 장점이 있다면 무엇?
A) 각 소켓 연결마다 필요한 고정 사이즈의 중간 Send용 버퍼의 크기제한/낭비가 없어지며 속도가 빠르다... -> Windows NT Platform에서는 궁극의 소켓인 IOCP를 사용해야 합니다.
*) 자기만의 TCP/IP 용 라이브러리를 설계하고자 하는 사람이 반드시 알아야 할 한가지가 있다면?
*) CSocket을 가능하면(절대라고 표현해도 될 정도죠 ) 사용하지 마세요. - CSocket을 이용하여 프로그램 하시는 분이 있다면 존경받아 마땅한 분이죠... ==> CSocket가 Blocked Call이기 때문에 쓰레드를 꼭 써야하는것도 아니죠....ui에 신경을 쓸필요가 없거나 소켓 옵션에 timeout을 걸어둘수도 있는 문제구욤....꼭 blockedcall이 나쁜건 아니죠..... 그리고 쓰레드을 이용한 코딩이 오브헤드가 그렇게 많지 않답니다. 그리고 telnet데몬들은 1user 1thread로 작성된게 거의 대부분이고 잘돌아 갑니다. 그리고 data sync도 따지고 보면 blocked나 non-blocked나 둘다 비슷할텐데요 ; *) 가능하면 BSD소켓 API만을 사용하여 코딩하십시오... ==> 멀티 플래폼이 아닌 socket프로그램이라면 굳이 bsd소켓 api만 사용하지 않아도 될것입니다. 포팅을 고려한다면...물론 BSD소켓만 사용하시는게 좋겠죠.... 윈속2.0 api의 경우 많은 기능과 더 향상된 성능(미미하겠지만)을 발휘한답니다. *) connect함수를 호출한 다음 언제 상대방이 제대로 접속이 이루어 진 것인가요?
==> 굳이 select을 사용해서 체크할 필요 없을듯합니다 connect의 리턴 값만 체크하는것만으로도 충분하다고 생각됩니다. 이부분은 왜 굳이 select을 사용해서 체크하는지 이유가 있다면 알려주세욤 (전 그냥 connect의 리턴값 체크로도 잘되었습니당) - 언제 소켓 접속이 끊어진 것입니까? ==> 어느쪽이든 끊고 싶은쪽에서 closesocket해버려도 상관없을듯한데요 만약에 보내구 있던중 데이터의 확실한 전송을 원하시면 linger 을 on해주면 .....음.....타임아웃 값을 조금 늘려주든지 하면 될겁니다. *) 네트웍 서브시스템(혹은 인터넷 망사업자) 등등의 오류에 의한 강제 종료는 어찌 처리합니까? ==> 이럴경우에 보통 감지 되지 않습니다. ping/pong으로 체크를 하셔서 종료처리를 서버쪽혹은 클라이언트쪽에서 ...하는거시 *) 위의 graceful shutdown과정을 순서대로 밟고 제대로 처리했는데 바로 다시 재접속을 하면 접속이 안돼요. 어찌된 것인가요? ==> ??? 재접속을 하면 안되는 이유는 다른데 있는듯합니다... *) OVERLAPPED IO가 잘 동작하지 않습니다. ==> 현재 서버구현에서 가장 많이 쓰이는게 iocp(windows nt기반에서)을 이용해서 overlapped i/o입니당..-,-a 그리고 보통 overlapped로 read/write한다구 해서 문제가 되는건 아님니당....오히려 성능이 좋다고 설명되어있습니다(win32네트워크프로그래밍;직접 벤치마킹해보진 않았음) 님이 말씀하신건 혹시 리눅스의 aio_의 단점이긴합니당; ==> 이건 저두 확실히 알지 못하지만 all Or Nothing라구 하긴 문제가 있지 않을까요 (10M~100M)을 보내보셍 그럼 100M가 가야하는데 그렇지 않을듯.....저건 스트리밍i/o인 tcp의 특성인거 같기도 하고.....이부분은 저도 확실하지 않습니당. Q) select(fd_count, NULL, &fd_set, NULL, ...)을 이용하여 제대로 쓰기 가능 상태를 체크하는데 제대로 되지 않습니다. 마치 OVERLAPPED IO처리가 조금씩 지연되고 있는듯 보입니다. 어찌해야 하나요? A) select(...) 호출 바로 앞에서 SleepEx(0, TRUE); 를 호출하여 OVERLAPPED IO결과가 프로그램에 전달될 수 있도록 해야 합니다. 필요한 경우 SleepEx() 함수 안에서 바로 OVERLAPPED IO콜백 함수가 불립니다.
*) select와 overlapped i/o와 아무 상관이 없으며 SleepEx을 쓰는 이유도 타당해보이지 않는뎅....;; 리고 버퍼에 넣는 이유는 오류처리의 불편함도 있을수 있겟지만 tcp가 스트리밍io 여서이기도 하고 버퍼링(똑같은 말이네...-..-;모아서 보내면 자잘히 많이 보내는것보다 좋겠져...-,-;)과 동기화등 다른 이유가 더 큰거 같은뎅; --이규승() *)접속이 끊어진건 오류뿐 아니라 recv가 0을 리턴해도 종료된게 아닌지요? --조해진 ->MSDN *)몇몇 부분에서 수긍가지 않는것두있긴하지만 나름대로 각자의 스타일이라 생각하고 넘어갑니다 좋은글 감사합니다 --이규승 *)overlapped io에서 recv시 미리 사이즈를 주고 받아야 한다는 건 무슨 소리인지 이해가 안됩니다. 잘못아신듯 --최인호
답변 글을 달아 주셔서 감사합니다.
먼저 제가 글을 쓰는 상황에 대해서 밝히지 않은게 조금 문제가 된 듯 합니다. 어느날 웹서핑을 하다가 데브피아란 곳이 생각나서 ;;; 들어와보니 의외로 소켓에 관한 질문을 하시는 분들이 꽤 되더군요.. 전 지금까지 만 15년간 코딩을 해오고 있습니다. 그리고 그 중 소켓 코딩은 약 5년 동안 해왔고요... 물론 처음엔 CSocket/C AsyncSocket/BSD API 를 이용한 전형적인 순서를 밟아서 나름대로 경험을 쌓은 것이고요. 제가 글을 처음 쓸 때는 이것저것 생각하지 않고 글의 제목대로 소켓코딩(정확히 말하면 자기만의 Library)을 함에 있어서 실제 제가 겪고 남의 질문에 답하는 과정에서 쌓인 생각이 자연스럽게 표출된 것입니다. 아무 생각없이 뜬금없이 글을 써 올린 제게 좀 문제가 있네요 ㅋ * 위의 글에서 소켓 코딩은 그냥 TCP/IP(UDP 제외한...)를 이용한 코딩을 의미합니다. * 최소한 CSocket/C AsyncSocket은 써보고 대안을 찾는 사람을 대상으로 한 글입니다. * 소켓코딩을 해서 실제 프로그램을 제작했는데 문제가 생긴 경우 혹시 프로그램상의 문제가 있다면 오류를 찾는데 도움을 드리기 위한 겁니다. * 그래서 대부분의 글 내용이 BSD소켓 API를 이용한 async(non-blocked) call에 관한 내용입니다. 물론 win32 환경에서는 오버랩드 I/O를 사용하고 있다고 생각하시면 됩니다. * 그리고 개인적으로 온라인 게임 서버/클라이언트, 메신저의 다자간 채팅 서버/클라이언트, 웹 서비스 회사의 ActiveX/CGI/인증서버 등등 많은 소켓 응용 프로그램을 제작한 경험상, 대부분의 상황이 접속 요구가 굉장히 빈번하고 수많은 오류 처리를 해야하는 상황하의 코딩들만 해보았기 때문에 CSocket/CAsyncSocket을 이용하는 경우나, 구조적 안정성이나 프로그래밍적인 편리성 등이 필요없는 상황(그저 대충대충해도 뭐든지 가능한;;)은 솔직히 전혀 생각해 본 적이 없었더군요... --정창모
논점1: blocked call / timeout / 오류처리
특수한 목적, 주로 1 대 1 접속을 목표로 하는 경우엔 CSocket이 아주 괜찮은 선택이긴 합니다. 단 상용제품 등으로 내지 않는 상황에서요.... 왜 그런고 하니 실제 작성된 프로그램이 안정성 있게 돌아가려면 수많은 예외처리를 해야하는데 CSocket을 이용한 프로그램의 속성상 프로그램 여기/저기서 send/recv를 남발하게 됩니다. 더구나 Blocked call 인 CSocket을 이용하면서 오류에 대처하기 위해 모든 송/수신 호출부에서 타임아웃을 건다고 생각해보죠... 조금 코메디가 되죠 자기 혼자 쓰는 프로그램이 아닌 경우는 거의 모든 경우에 안정성과 매끄러운 오류 처리 등이 필수적입니다. 결국 웬만한 정도의 안정성을 확보하기 위해서 넣는 오류코드가 장난이 아니게 되며 Async콜 사용에 비해 무지 깔끔했던 코드들이 결국은 누더기가 될 가능성이 농후합니다. 특히 CSocket의 사용시의 문제점은 그걸 사용해 무지하게 오랜 시간을 코딩했다 해도 개발자의 경험이 늘지 않는다는 겁니다. 아마도 궁극적으로 CSocket을 마스터한 사람이 쓰는 코딩 방식은 모든CSocket API에 극히 적은 타임아웃을 주고 마치 CAsyncSocket처럼 사용하게될 겁니다. - 1:1접속이 아닌 1:다 의 경우 mmorpg가 아닌경우엔 순간전송량이 많고 접속자수가 그리 많지않은(ftp나 file 관리서버)같은경우엔 blocked call이 훨씬 좋을수 있습니다. ->MMORPG 개발 경험상 무조건 ASynchronizatio이고 IOCP를 사용해야 합니다. - 그리고 CSocket이 recv/send을 남발한다구 했는데 그건 Asyncsocket두 마찬가지아닌가요 이건 어떻게 소켓을가지고 프로그래밍 했느냐의 문제일듯... - timeout에 대해서.... 실제루 그렇게 사용되구 있구요(ftp서버나 telnetd, httpd) 그리소 송수신 호출부에 타임아웃을 거는게 아니고 소켓의 옵션을 말했던겁니다.
논점2: winsock2을 사용할것인지 bsd호환 api을 사용할것인지? 전적으로 동의합니다... 하지만 엄청난 성능 향상(오버랩드 I/O는 정말 쓸만하죠 )이 이루어지는 것이 아닌데 굳이 윈도우즈에서만 사용되는 API만을 사용하면 웬지 이상하지 않느냐는 정도로 이해하시면 될듯... 엄청난 성능향상은 없지만 제가 알기론 overlapped I/O을 사용하는 WSASend/Recv의 경우엔 30%의 성능향상이 있으며...(network programming for mcrosoft windows와 개인적인 테스트)
iocp나 기타 winsock2에서만 지원하는 각각의 객체들도 winsock1대에서는 사용할수 없기때문에 성능상으로 따지고 보면 많은 향상이 있다는것입니다. 실제로 예전 테스트에서 select을 사용하는것보다 winsock2에서 제일 비슷한 eventselect가 성능상 우위에 있었습니다. -> 생각 보다 WInsock2 APID Eventselect 성능이 뛰어나다 만약 극단적인 성능대신 개발에 용이성으로 Tradeoff를 한다면 나는 EventSelect로 개발 한다.
논점3: overlapped i/o 소켓 프로그램 구조적으로 write는 오버랩드 I/O에 아주 잘 맞습니다. 하지만 문제는 read쪽인데 프로그램에서 read할 데이터의 사이즈가 명백한 상황에서는 오버랩드 I/O를 이용한 코딩이 아주 도움이 되겠죠.. 하지만 대개의 경우 소켓코딩에서는 읽어야할 데이터의 수가 미리 고정되어 있지 않습니다.-> 프로토콜 설계를 잘 해야 한다. 그저 상대방이 보내주는 데이터를 읽어야 하는데 패킷의 크기도 가변이 태반이기 때문에 미리 사이즈를 주고 읽어야 하는 오버랩드 I/O와는 맞지 않는다는 겁니다. 고작 고정 사이즈의 작은 패킷 헤더를 읽기 위해 쓰기엔 아깝죠... 다만 파일처리쪽에선 많이 다르겠죠. 파일처리시엔 이미 데이터의 크기를 알고 있기 때문에 사용하면 무지 편하겠죠... 참고로 overlapped i/o는 제가 님의 말씀을 듣고 찾아본결과 현재 수면위로 올라온 버그는 없습니다. 그리고 커맨드가 가변일경우에 아깝다구 했는데....왜 그런지 이해하기 힘들구 제 생각에 님 기법이 데이타 사이즈을 recv 그후에 데이터를 recv해서 파씽하시나 본데 그렇게 하지 않고 그냥 일정 버퍼크기만큼을 받아서 파씽하는 방법을 보통 사용하죠 (overlapped i/o시) 논점4: socket가 스트리밍i/o라는 말이었는뎅... 스트리밍 I/O 처럼 속도까지 필요한 경우엔 독립적인 Read/Write 쓰레드 까지 사용될 수 있으므로 동기화 때문에라도 버퍼의 필요가 극대화 되겠죠.
다들 아시는것처럼 데이터량이 많으나 적으나 버퍼링으로 얻는 이점은 많답니다; --이규승 -> 윈도우 OS Kernel에서도 결국은 데이터를 Page or NonpagePool Memory management that This point is good at use Buffer for Winsock application! -aiwisdom@gmail.com |
[본문링크] 소켓 코딩시 실제적으로 주의해야할 사항
|
[1]
|
|
|
|
|
코멘트(이글의 트랙백 주소:/cafe/tb_receive.php?no=34764 |
|
|
|
|
|
|
|
|
|
Copyright byCopyright ⓒ2005, SSISO Community All Rights Reserved.
|
|
|