본문 바로가기

C++/Socket 통신

WSAEWOULDBLOCK(10035), WSAEALREADY(10037) 오류 및 RST 패킷 분석과 해결 방법

SMALL

포스팅 계기

 네트워크 프로그래밍을 하다 보면, 소켓 통신에서 예상치 못한 오류가 발생하는 경우가 많다. 특히, 논블로킹 소켓을 사용할 때 자주 마주치는 WSAEWOULDBLOCK(10035)WSAEALREADY(10037) 오류는 많은 개발자들에게 혼란을 준다.

 최근 현업에서 TCP 클라이언트-서버 환경을 구축하는 과정에서 이 오류를 해결해야 하는 상황이 발생했다. 여기에 더해, 와이어샤크(Wireshark) 패킷 분석을 통해 서버 측에서 RST (Reset) 패킷이 발생하는 문제도 발견하였다. 그래서 해당 포스팅을 작성하게 되었다. 이번 포스팅에서는 WSAEWOULDBLOCK(10035)WSAEALREADY(10037) 오류의 개념과 해결 방법을 정리하고, RST 패킷이 발생하는 원인과 해결책까지 설명해 보겠다. 네트워크 프로그래밍을 하는 분들에게 도움이 되었으면 한다.

출처 : ChatGPT


WSAEWOULDBLOCK(10035)와 WSAEALREADY(10037) 오류 개념

  •  WSAEWOULDBLOCK (10035)란?
    • 논블로킹 모드의 소켓에서 send() 또는 recv()를 호출할 때, 소켓이 아직 준비되지 않았을 경우 발생하는 오류다.
    • 즉, 데이터를 보낼 준비가 안 되었거나 받을 데이터가 아직 없을 때 발생하는 오류다.
    • 해결 방법은 select() 또는 WSAPoll()을 사용하여 소켓이 준비될 때까지 대기하는 것이다.
  • WSAEALREADY (10037)란?
    • 논블로킹 모드에서 connect()를 호출했을 때, 이미 연결이 진행 중인 상태에서 다시 connect()를 호출하면 발생하는 오류다.
    • WSAEWOULDBLOCK과 비슷하지만, 이미 connect()가 실행 중인 상태에서 추가 호출이 발생할 때 나타난다.
    • 해결 방법은 select()를 사용하여 연결 완료 여부를 확인한 후, getsockopt(SO_ERROR)를 호출하여 연결 상태를 점검하는 것이다.

해결 방법 (예제 코드 포함)

  • WSAEWOULDBLOCK(10035) 해결 방법

아래 코드는 select()를 사용하여 소켓이 데이터 송수신이 가능할 때까지 대기하는 예제다.

d_set writeSet;
FD_ZERO(&writeSet);
FD_SET(socket, &writeSet);

timeval timeout;
timeout.tv_sec = 5; // Close하기 까지의 대기 시간을 기본적으로 5초로 설정
timeout.tv_usec = 0;

int result = select(0, NULL, &writeSet, NULL, &timeout);
if (result > 0 && FD_ISSET(socket, &writeSet)) {
    send(socket, buffer, length, 0);
} else {
    printf("Socket is not ready for writing. WSAEWOULDBLOCK occurred.\n");
}
  • WSAEALREADY(10037) 해결 방법

아래 코드는 connect() 호출 후 select()를 사용하여 연결 상태를 확인하는 방법이다.

int result = connect(socket, (sockaddr*)&serverAddr, sizeof(serverAddr));
if (result == SOCKET_ERROR) {
    int error = WSAGetLastError();
    if (error == WSAEWOULDBLOCK || error == WSAEALREADY) {
        fd_set writeSet;
        FD_ZERO(&writeSet);
        FD_SET(socket, &writeSet);
        timeval timeout;
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;
        
        result = select(0, NULL, &writeSet, NULL, &timeout);
        if (result > 0) {
            int so_error;
            socklen_t len = sizeof(so_error);
            getsockopt(socket, SOL_SOCKET, SO_ERROR, reinterpret_cast<int*>(&so_error), &len);
            if (so_error == 0) {
                printf("Connection successful!\n");
            } else {
                printf("Connection failed with error: %d\n", so_error);
            }
        }
    }
}

RST 패킷이란? (Reset 패킷)

  • RST 패킷이란?
    • RST (Reset) 패킷은 TCP 연결이 비정상적으로 종료될 때 발생한다. 주로 아래와 같은 경우에 나타난다:
      • 서버가 accept() 후 즉시 closesocket()을 호출하는 경우
      • 클라이언트가 연결을 유지하려 하는데 서버가 closesocket()을 호출한 경우
      • 서버의 방화벽이 특정 시간 동안 연결을 유지하지 못하도록 설정된 경우
      • 서버가 SO_LINGER 옵션을 설정하여 소켓을 즉시 종료하는 경우

RST 패킷 문제 해결 방법

  • accept() 후 일정 시간 대기
SOCKET clientSock = accept(serverSock, NULL, NULL);
if (clientSock != INVALID_SOCKET) {
    printf("Client connected!\n");
    Sleep(5000);  // 일정 시간 대기
    closesocket(clientSock);
}
  • SO_LINGER 비활성화
linger so_linger;
so_linger.l_onoff = 0;
so_linger.l_linger = 0;
setsockopt(serverSock, SOL_SOCKET, SO_LINGER, (char*)&so_linger, sizeof(so_linger));

포스팅 마무리

 이번 글에서는 WSAEWOULDBLOCK(10035), WSAEALREADY(10037) 오류와 RST 패킷 발생 원인 및 해결 방법을 정리해 보았다. 이 오류들은 실제 네트워크 프로그래밍에서 흔히 발생하는 문제이며, 문제를 정확히 이해하고 적절한 방법으로 처리하면 쉽게 해결할 수 있다.

 이번 포스팅이 현업에서 같은 문제를 겪고 있는 개발자들에게 도움이 되었으면 좋겠다. 네트워크 프로그래밍은 쉽지 않지만, 하나씩 해결해 나가면 점점 익숙해질 것이라고 기대한다.