본문 바로가기

Programming Language/C&C++

소켓 통신 #3 - ICMP 클라이언트 구현하기(2)

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

소켓 통신 #3 - ICMP 클라이언트 구현하기(2)

- by Tapito


 ICMP로 통신하기 위해서는 저수준 소켓을 만듭니다. 만일 Windows 운영체제에서 이 단계에 INVALID_SOCKET이 반환되고 GetLastError 함수 등을 통해 10013 오류가 확인되었다면 해당 프로그램을 관리자모드로 실행하면 됩니다.

SOCKET icmpSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

 struct sockaddr_in icmpDestination에 ping을 보낼 대상이 미리 저장되어있다고 하면 sendto 소켓함수를 통해 상대방의 수신 여부와 무관하게 일방적으로 데이터(ICMP를 포함한 패킷 전체)를 쏩니다. 전송 성공 시 전송된 바이트 수를 반환하고 그렇지 않으면 SOCKET_ERROR를 반환합니다. 4번째 매개변수는 전송 옵션으로 보통 0을 넣습니다.

sendto(icmpSocket, (char *)패킷, sizeof(패킷), 0, (struct sockaddr *)icmpDestination, sizeof(struct sockaddr_in));

 에코가 되돌아올 때까지 잠시 대기합니다. select 소켓 함수가 그 역할을 합니다. Windows 운영체제에서 첫 번째 매개변수는 신경쓰지 않아도 됩니다. 반환 값으로서 -1은 소켓 오류, 0은 타임아웃, 양수는 수신된 바이트 수입니다.

select(1, 읽기소켓, 쓰기소켓, 오류출력소켓, 제한시간);

 select 함수로 에코가 되돌아왔음이 확인되면 recvfrom으로 데이터를 가져옵니다. 가져오는 패킷의 형식은 IP 헤더-ICMP 헤더-전송시 프로그램 작성자가 부가적으로 보낸 데이터의 순서입니다. 소켓 오류 시 SOCKET_ERROR, 성공 시 수신한 패킷의 바이트 수가 반환됩니다.

recvfrom(icmpSocket, (char *)&icmpResponse, sizeof(icmpResponse), 0, pSocketAddress, &nSocketAddress);

 구글 서버로 ping을 1회 보내보겠습니다. 분량상 오류 발생 시 처리 사항은 생략하겠습니다.

/* 예제 */
SOCKET icmpSocket; // 저수준 소켓
struct sockaddr_in icmpDestination; // 수신지 주소 (서버)
struct sockaddr_in icmpSource; // 발신지 주소 (여러분 PC)

/* 1 단계. 저수준 소켓 생성 */
icmpSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); // Windows 운영체제에서 여기가 10013 오류나면 관리자 모드로 실행

memset(&icmpDestination, sizeof(icmpDestination), 0);
memset(&icmpSource, sizeof(icmpSource), 0);

icmpDestination.sin_family = AF_INET; // 인터넷 망에 접속
icmpDestination.sin_port = 0; // 포트 번호: 0번
icmpDestination.sin_addr.s_addr = inet_addr("173.194.127.212"); // 구글 www.google.com의 IP 주소

/* 2 단계. 시험 데이터 일방적 전송 */
ICMPREQUEST icmpRequest; // 전송할 패킷
ZeroMemory(&icmpRequest, sizeof(icmpRequest));

icmpRequest.icmpHeader.bType = 8;
icmpRequest.icmpHeader.bCode = 0;
icmpRequest.icmpHeader.wChecksum = 0;
icmpRequest.icmpHeader.wIdentification = (WORD)(_getpid() & 0xFFFF); // ID와 Sequence 값은 임의로 설정 가능
icmpRequest.icmpHeader.wSequence = 1;

icmpRequest.icmpHeader.wChecksum = GetChecksumWord((LPBYTE)&icmpRequest, sizeof(icmpRequest)); // 전송할 패킷 모두 구성한 후에 체크섬 구하기
 	
sendto(icmpSocket, (LPSTR)&icmpRequest, sizeof(ICMPREQUEST), 0, (struct sockaddr *)pDestination, sizeof(struct sockaddr_in)); // 전송

/* 3 단계. 에코가 돌아올 때까지 대기 */
struct timeval timeOut; // 제한 시간
fd_set fdset; // fd_set 구조체를 통해 여러개의 소켓을 동시에 감지할 수 있습니다. 여기서는 1개만

FD_ZERO(&fdset); // fd_set 구조체 초기화
FD_SET(icmpSocket, &fdset); // fd_set 구조체에 감시할 소켓 하나씩 추가

timeOut.tv_sec = 1; // 제한 시간은 1.5sec로 설정
timeOut.tv_usec = 500;

select(1, &fdset, NULL, NULL, &timeOut); // -1: 오류 발생, 0: 타임아웃, 양수: 수신된 데이터 크기 반환

/* 3 단계. 되돌아온 패킷 수신 */
ICMPRESPONSE icmpResponse;
struct sockaddr * pSocketAddress = (struct sockaddr *)pSource; // 에코를 보낸 서버의 주소 (sendto로 지정한 주소와 다를 수 있음)
int nSocketAddress = sizeof(struct sockaddr_in);
int iReturn;

memset(&icmpResponse, sizeof(icmpResponse), 0); // 받을 버퍼를 0으로 초기화

recvfrom(icmpSocket, (char *)&icmpResponse, sizeof(icmpResponse), 0, pSocketAddress, &nSocketAddress); // 받기

/* 4 단계. 소켓 닫기 */
closesocket(icmpSocket);