Trobles (7)

박근수·2024년 2월 20일
0

WebSocket과 RTCConnection Event

원래 WebRTC연결을 WebSocket이 연결되면 바로 시작하도록 동기적으로 만들었다. 그런데 한 번 연결이 맺어지고 나서는 WebSocket을 거의 사용하지 않는데 이 때 WebSocket은 데이터를 계속 주고 받지 않으면 연결 상태를 확인하고 다시 연결한다. 그 과정에서 Event 기반으로 만들지 않고 Socket 연결되면 바로 동기적으로 WebRTC연결을 맺도록 만든 코드가 버그를 일으켰다.

client.onConnect = (frame) => { // client는 WebSocket 연결 Client임.
  pc.createOffer({
    offerToReceiveAudio:true,
    offerToReceiveVideo:true
  })
    .then((offer) => {
    console.log("sdp offer created") // sdp status
    pc.setLocalDescription(offer)
      .then((r) => {
      client.publish({
        destination: `/app/api/audience/${userId}/offer`,
        body: JSON.stringify({
          buskerId,
          audienceId: userId,
          offer,
        })
      })
      new RTCPeerConnectionIceEvent("onicecandidate")
    })
  })
    .catch((error) => {
    console.log(error)
  })

WebSocket이 1분 간격으로 다시 연결되는데 이 때마다 클라이언트가 다시 연결을 시도했다. 클라이언트는 연결을 SDPOffer를 만들어서 Signaling Server로 보내는데 Server는 Offer를 받아서 Ice Candidate까지 진행을 한다. 그런데 브라우저는 Offer만 보내고 Server의 메세지에 응답을 하지 않아서 서버와 Kurento에 data가 쌓이다가 죽어버렸다. Event 중심으로 코딩을 하지 않으면 이러한 일들이 발생할 수 있다. 다양한 노드와 정해진 프로토콜로 메세지를 주고 받으며 연결을 해야하기 때문에 되도록 EventListner를 사용하는 것이 좋다.

Kurento

아직 이유를 제대로 알지 못하고 있으나 어쩌다보니 해결해버린 문제임.

@SpringBootApplication
@EnableWebSocket
public class ChocolateApplication {

    @Bean
    public KurentoClient kurentoClient() {

        String kurentoValue;
        return KurentoClient.create();
    }

Coturn은 EC2에 있는데 개발할 때 Kurento는 로컬환경에서 Docker로 작동시켰다. KurentoClient()에 파라미터를 주지 않으면 로컬 Kurento를 사용한다.
Coturn도 문제가 없고 Signaling과 Broswer, Coturn은 문제가 없는 것을 확인했다. 모든 Event에 log를 달고 wireshark로 coturn의 응답도 확인을 했는데 막상 영상이 Kurento로 전달이 안됐다. 모든 상태가 다 좋은 것 같은데 안되서 Kurento가 문제라는 것은 확인했는데 막상 그걸 어떻게 해야할지 몰라서 Kurento의 Logging을 최대로 만들고 모니터링을 하려다가 무슨 생각이었는지 모르겠는데 EC2에 올려놓은 Kurento로 수정하니까 WebRTC연결이 되어버렸다.

    @Bean
    public KurentoClient kurentoClient() {

        String kurentoValue;
        return KurentoClient.create("여기에 EC2 Kurento url:port를 입력해주세요."
        );
    }

이유는 잘 모르겠다. SSAFY의 port block때문이었을까? 나머지 개발에 쫒겨서 더 이상 디버깅은 어려웠고 이 상태로 나머지 기능을 빨리 개발했다.

Coturn setting

realm 설정 안했더니 401 UnAuthrizaion Error가 발생했다. 유의... realm이 뭔지 모르겠어서 안했더니 문제가 있었다. 나중에 Kurento doc보니까 설정값을 다 알려줬었다. 그 때 realm도 설정하라고 했다. 임의의 문자를 입력하면 된다.

PC.close, Stomp.release()

소켓이나 PC를 잘 정리하는 것의 중요성
처음 개발할 때 연결을 끊는 것을 생각하지 않고 연결하는 기능만 생각해서 개발을 했다. WebSocket이나 Http나 제대로 연결을 끊는 로직을 별로 짜지 않았다. 프레임워크에서 알아서 연결을 끊어버렸기 때문이다. 그런데 Kurento를 사용할때는 개발자가 연결을 끊어버리지 않으면 그 세션 데이터 상태가 계속 남게된다.

@Getter
@Setter
public class Busking extends UserSession implements Closeable {
   private final Logger log = LoggerFactory.getLogger(Busking.class);
   private final ConcurrentHashMap<String, UserSession> buskingSession = new ConcurrentHashMap<>();
   private final KurentoClient kurentoClient;
   private final IceMessageSendService iceMessageSendService;
   private final String buskerEmail;
   private final String buskingTitle;
   private final String buskingReport;
   private final String buskingHashtag;
   private final String buskingInfo;
   private GeoLocation geoLocation;
   private int audienceCount = 0;
   private WebRtcEndpoint buskerWebRtcEndpoint;
   private MediaPipeline buskerPipeline;
	... ( 생략 )
	   String sdpAnswer = audienceWebRtcEndpoint.processOffer(sdpOffer.getOffer().getSdp());
       JsonObject sdpResponse = new JsonObject();
       sdpResponse.addProperty("id", "audienceSdpAnswer");
       sdpResponse.addProperty("response", "accepted");
       sdpResponse.addProperty("sdpAnswer", sdpAnswer);

       iceMessageSendService.audienceSendSdpAnswer(audienceId, sdpResponse);
       audienceWebRtcEndpoint.gatherCandidates();

       buskingSession.put(sdpOffer.getAudienceId(), audienceSession);

이렇게 HashMap에 세션 정보를 관리하는데 이 세션에서 WebRtcEndPoint를 저장한다. 이 때 세션을 remove하는 기능이 없다면 브라우저에서는 웹 페이지를 꺼버리거나 비정상적으로 연결을 끊었을 때 세션 정보가 남아있어서 서비스를 사용할 때 충돌이 발생하거나 정보가 입력되지 않는 등 여러가지 에러가 발생했다. 뿐만 아니라 한번 방송에 들어가서 WebRTC 연결이 맺어지면 해제가 되지 않아서 다른 방송방에 들어가도 볼 수 없는 등 예측하기 어려운 문제들이 발생한다. 그래서 네트워크 연결과 관련된, 정상적인 종료, 비정상적인 연결 끊김 등 다양한 경우를 고려해줘야한다.
TCP에서 3-HandShake,4-HandShake로 연결 상태를 관리하는 과정을 생각해보면 1번의 데이터 req,res를 위해 7번의 데이터 송수신이 낭비인 것 같으나 좋은 품질의 서비스를 위해 필수적인 요소라는 것을 배울 수 있었다.

profile
개성이 확실한편

0개의 댓글