특정 장비와의 통신 문제가 국지화 되도록 비동기 I/O 로 전환하기

주싱·2021년 9월 6일
0

Trouble Shooting

목록 보기
2/21

01. 문제 현상

위성과의 교신을 위해 지상국 시스템을 초기화 하는 과정에서 모뎀 장비에서 응답 신호가 수신되지 않아 전체 교신 프로세스가 실패하는 문제가 발생하였습니다.

02. 원인 분석

  1. 로그를 분석한 결과 모뎀 장비를 초기화하는 과정에서 장비로부터 응답이 수신되지 않아 이어지는 모든 프로세스가 지연되고 있었습니다.
  2. 모뎀 장비와의 통신에는 Blocking 소켓이 사용되고 있어서 recv()를 호출하고 응답이 수신되지 않는 경우 20초씩 실행이 지연되고 있음을 확인했습니다.
  3. 전체 모뎀 장비가 2대이고 장비 1대 당 12개의 서브 모듈이 있어서 약 8분의 지연(2대 X 12개 서브모듈 X 20초)이 발생하여 초기화에 할당된 3분 시간을 초과하고 있었습니다.
12021/03/19  04:52:57     <RAU INITIALIZATION
22021/03/19  04:52:57     [CONTROL] Command Success - ACK 0
32021/03/19  04:52:57     <TMS INITIALIZATION
42021/03/19  04:52:57     [CONTROL] Command Success - ACK 0
52021/03/19  04:52:57     <IFR-1 INITIALIZATION
62021/03/19  04:52:58     [CONTROL] Command Success - ACK 0
72021/03/19  04:52:58     <TMU-A INITIALIZIATION
...
12021/03/19  04:47:03     <RAU INITIALIZATION
22021/03/19  04:47:23     <TMS INITIALIZATION
32021/03/19  04:47:43     <IFR-1 INITIALIZATION
42021/03/19  04:48:03     <TMU-A INITIALIZIATION
...

03.처리 결과

  • 모뎀 장비와의 통신에 Non-blocking 소켓을 적용하도록 설계를 변경하였고 최대 2초간 응답 수신을 대기하도록 수정하였습니다.
  • 모뎀과 통신 문제가 발생하여도 최대 48초 ( 모뎀 2대 x 서브 모듈 12개 x 2초 )가 지연되어 해당 모뎀의 문제가 전체 미션 실패로 이어지지 않도록 문제를 국지화 할 수 있었습니다.

04. 주요 코드

// Non-Blocking 소켓 생성
BOOL CreateNonBlockingSocket(SOCKET *pSocket)
{
	*pSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); 
	if (*pSocket == INVALID_SOCKET)
	{
		return FALSE;
	}

	u_long iMode = 1; // 1 : non-blocking mode, 0 : blocking mode

	int iResult = ioctlsocket(*pSocket, FIONBIO, &iMode);
	if (iResult != NO_ERROR)
	{
		return FALSE;
	}

	return TRUE;
}

// Non-Blocking 소켓 연결 
int ConnectWithTimeout(SOCKET socket, sockaddr_in addr, int timeoutSeconds)
{
	if (connect(socket, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
		int error = WSAGetLastError();

		if (error != WSAEWOULDBLOCK) {
			return -error;
		}
	}

	fd_set writefds;
	FD_ZERO(&writefds);
	FD_SET(socket, &writefds);

	struct timeval tv;             
	memset(&tv, 0, sizeof(tv));
	tv.tv_sec = timeoutSeconds;     
	tv.tv_usec = 0;                 

	int result = select(0 /*ignored in windows*/, NULL, &writefds, NULL, &tv);
	if (result == 0 || result == -1)
	{
		return SOCKET_ERROR;
	}

	return 0;
}

// Non-Blocking 수신
int ReceiveWithTimeout(SOCKET socket, char *buf, int len, int timeoutSeconds)
{
	fd_set readfds;
	FD_ZERO(&readfds);
	FD_SET(socket, &readfds);

	struct timeval tv; 
	tv.tv_sec = timeoutSeconds;    
	tv.tv_usec = 0; 

	int result = select(0 /*ignored in windows*/, &readfds, NULL, NULL, &tv);
	if (result == 0 || result == SOCKET_ERROR)
	{
		return SOCKET_ERROR;
	}

	return recv(socket, buf, len, 0);
}

...
profile
소프트웨어 엔지니어, 일상

0개의 댓글