Skip to content

Conversation

@taipaise
Copy link
Collaborator

@taipaise taipaise commented Jan 8, 2025

🌁 Background

  • MPC 프레임워크를 Network 프레임 워크로 교체하며 참여 요청 및 수락 기능을 리팩터링 하였습니다.

👩‍💻 Contents

  • 커스텀 근거리 통신 프로토콜 구현
  • 참여 요청 기능 리팩터링
  • 참여 요청 수락 기능 리팩터링
  • 기타 린트 에러 수정

📝 Review Note

  • 통신에 사용할 커스텀 프로토콜을 정의하였습니다. 해당 프로토콜은 전송 계층 위에 존재하는 애플리케이션 계층에서 동작합니다.
  • 전송 계층의 프로토콜로는 안정적인 통신을 위해 tcp를 사용하였습니다. NWBrowser, NWListener, NWConnection을 생성할 때 아래와 같이 NWParameter를 설정합니다.
let option = NWProtocolFramer.Options(definition: NearbyNetworkProtocol.definition)
let parameter = NWParameters.tcp
parameter
    .defaultProtocolStack
    .applicationProtocols
    .insert(option, at: 0)
  • 프로토콜 스택에 프로토콜을 추가할 때, application 계층에 가까울 수록 앞에, transport 계층에 가까울 수록 뒤에 배치해야합니다. 저희는 TLS나 다른 커스텀 프로토콜을 사용하지 않기 때문에 구현한 프로토콜을 제일 앞에 추가하였습니다.
  • 또한 기존 MPC와는 다르게, 연결 요청과 동시에 참여자의 정보를 보낼 수 없었습니다. 따라서 연결 수립 이후, 데이터를 전송하기 전에 자신의 정보를 호스트에게 알려주어야 합니다. 따라서 수신 측에서는 데이터를 수신할 때 현재 수신한 데이터가 peer의 정보인지, 아니면 데이터인지 구분해야 합니다.
  • 커스텀 프로토콜을 사용하면 송수신할 데이터의 메타 데이터를 설정할 수 있습니다. 현재 송수신할 데이터에 whiteboardObject같은 Data가 담겨있는지, 또는 peerInfo인지를 표시할 수 있습니다. 이를 통해 수신 시 더 원활하게 데이터를 확인할 수 있습니다.

📣 Related Issue

📬 Reference

Copy link
Collaborator

@ekrud99 ekrud99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

띵쪼!!!
생소한 프레임워크로 리팩토링 하시느라 고생 많으셨습니다!!!
RESPECT!!!!!


private func convertMetadata(metadata: NWBrowser.Result.Metadata) -> RefactoredNetworkConnection? {
switch metadata {
case .bonjour(let foundedPeerData):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

metadata.bonjour인 경우는 어떤 경우인가요?

Copy link
Collaborator Author

@taipaise taipaise Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사용자(client)가 host와 connection을 맺기 위해서는 호스트의 ip와 port 번호를 알고 있어야 합니다. 하지만 저희가 구현하는 서비스에서는 당연히 그걸 알기 쉽지 않죠. 따라서 MPC의 advertiser처럼, host는 자신의 정보를 주변에 알려야 합니다.

MPC의 advertiser의 역할을 Network 프레임워크에서는 NWListener가 수행합니다.
Listener는 크게 두 가지 역할을 수행합니다.

  1. 들어오는 connection에 대한 처리
  2. Bonjour 서비스를 이용하여 주변에게 본인을 advertising.

Listener가 봉주르 서비스를 이용하여 주변에게 advertising을 할 때, 본인의 정보를 NWTXTRecord라는 타입에 담아서 알릴 수 있습니다.

한편, MPC의 browser의 역할은 Network framework에서 NWBrowser가 수행합니다. 브라우저가 특정 host의 정보를 찾으면. result라는 객체를 통해 host의 endpoint를 알 수 있습니다. 또한 만약 host가 위에서 말씀드린 TXTRecord에 host의 정보를 담아 광고했다면, 이를 Result.Metadata에서 확인할 수 있습니다.
이 Metadata는 enum으로 총 두가지 case가 있습니다.

  1. .bonjour(NWTXTRecord)
  2. .none

metadata가 .bonjour인 경우는 어떤 경우인가요?

따라서 metadata가 bonjour인 경우는 listner가 본인의 정보를 추가적으로 담아 광고하고 있는 경우라고 보시면 될 것 같습니다!
저희는 화이트보드 방의 이름과 참여자의 이모티콘 정보를 담아 광고하고 있습니다.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

설명 감사합니다!!
Metadata에 대한 이해도 필요했는데 함께 설명이 된 것 같아요!
단번에 이해가 됐습니다 :)

}

// Create a class that implements a framing protocol.
class NearbyNetworkProtocol: NWProtocolFramerImplementation {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

커넥션 연결 역할을 수행하는 커스텀 클래스라고 이해해도 될까요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Browsing, Advertising, 커넥션 연결 수행 등 모든 근거리 통신에 사용되는 파라미터 헤더의 커스텀 프로토콜이라고 이해하는게 조금 더 맞는 것 같아요 !!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아직 저도 이 커스텀한 프로토콜에 대한 이해가 부족한 것 같습니다..

어떤 코드들이 필요하고 왜 커스텀했는지에 대한 설명을 조금 더 해주실 수 있을까요?

개인적으로 refactoredNearbyConnection과 함께 이해하기에는 들어오는 데이터를 peerInfo와 data를 구분하여 전달받기 위한 것인가? 라는 생각을 하고 보았습니다..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 ! 결과적으로 현재는 딴이 말한 peerInfo와 data를 구분하는데 사용하고 있습니다.

기존 커스텀 프로토콜을 사용하려고 했던 이유는
참여 요청을 보낼 때 자신의 정보를 보낼 수 있는 용도로 사용하려고 했습니다.

프로토콜을 구현하면서 연결되기 전까지는 데이터를 보낼 수 없고, 참여 요청을 보낼 때에도 자신의 정보를 같이 보내줄 수 없다는 것을 알았습니다.

그래서 사실 커스텀 프로토콜 없이도 근거리통신이 가능하긴 하지만 그래도 Low-Level한 작업을 처리하고 있고
커스텀 프로토콜을 없애기 보다는 참여 요청 후 자신의 정보 데이터를 보내는 것과 화이트보드 오브젝트를 구분하는 용도로 사용해보자 !
라고 결론지어 사용하고 있습니다 !!

Copy link
Collaborator Author

@taipaise taipaise Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

커스텀 프로토콜을 사용하면 여러가지 이점이 있다고 생각하여 사용하였습니다.
첫번째로는 딴이 말씀하신 것처럼, 데이터를 쉽게 구분할 수 있기 때문입니다. MPC와는 다르게 Network framework를 이용하여 연결을 수립하는 시점에, 호스트쪽은 클라이언트의 정보 (닉네임이나 이모티콘)을 알 수 없습니다. 따라서 클라이언트는 호스트에 연결 후 본인의 정보를 서버에게 데이터를 전송하는 방식으로 알려야 합니다.
또한 Network framework를 사용하면서, 데이터를 송신하는 방식이 Data 타입 자체를 송신하는 방법으로 변경되었습니다. 이에 따라 사용자의 정보와 airplain의 데이터를 분리 할 방법이 필요하다고 생각했습니다. 물론 DataInformationDTO에 Data 형식의 프로퍼티를 추가하고, dataType에 .profile 을 추가하여 Repository 단에서 파싱 후 사용하는 방법도 있을 것입니다. 하지만 기존에 있는 reciptDataPublisher를 활용하는 편이 좀 더 수월하게 리팩토링 할 수 있을 것이라 생각했습니다. 추가로, 커스텀 프로토콜을 사용하면 좀 더 깔끔하고 명확하게 현재 전송하는 데이터의 형식이 무엇인지 구분할 수 있다는 장점이 있어 프로토콜을 구현하는 방식을 채택하기로 했습니다.

또한, listener와 browser가 해당 프로토콜을 사용해서 동작하고 있습니다. 따라서 browser는 해당 프로토콜을 사용하는 network service만 검색하게 됩니다. 우선 아래와 같은 상황을 가정하겠습니다.

  1. airplain이 커스텀 프로토콜을 사용하지 않음
  2. 다른 아이폰 유저가 network framework로 구현된 A앱을 사용하고, 본인의 네트워크 서비스를 advertising, browsing하고 있음 (A앱도 특별한 프로토콜 사용 x)

이 상황에서 airplain을 사용하는 유저는 A앱에서 광고하고 있는 network service를 검색할 것이고, A앱을 사용하고 있는 사용자는 airplain에서 광고하는 서비스를 검색할 수 있습니다. 하지만 커스텀 프로토콜을 사용하면, airplain을 사용하는 유저끼리만 network 연결을 구축하고 사용할 수 있다는 장점이 있어 채택하였습니다.

  • 커스텀 프로토콜을 사용한다면, 저희가 DataInformationDTO에서 사용하고 있는 dataType 프로퍼티를 없앨 수도 있습니다. 프로토콜의 헤더에 데이터 타입을 명시해서 보낼 수 있기 때문입니다! 하지만 근거리 통신 모듈 자체를 뚝 떼서 다른 곳에서 원활하게 사용할 수 있게 하기 위해 '사용자 정보’와 '데이터’ 타입 두 개만 구분할 수 있도록 구현했습니다.

connection: RefactoredNetworkConnection,
myConnectionInfo: RequestedContext) -> Result<Bool, Never> {
return .success(false)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

항상 성공하는 메서드인가요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요기는 NearbyNetworkService라 리팩터링 될 refactoredNearbyNetworkService 를 확인해주시면 됩니다 !!!!!

Copy link
Collaborator Author

@taipaise taipaise Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우선 Result를 반환하도록 한 의도는 추후 가능하다면 커스텀 에러 타입을 반환하기 위해서 입니다.
Bool 값으로 성공/실패 를 알리기 보다, error 타입 정의로 어떤 문제로 인해 실패했는지 파악하기 쉽게 하기 위해서죠

하지만 지금은 커스텀으로 정의한 에러가 없기 때문에, .success 안에 false가 들어가면 실패라고 봐주시면 감사하겠습니다!
조이 말씀대로

요기는 NearbyNetworkService라 리팩터링 될 refactoredNearbyNetworkService 를 확인해주시면 됩니다 !!!!!

참고해주시면 감사하겠습니다~

Copy link
Member

@eemdeeks eemdeeks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생 많으셨습니다..

아직 해당 pr에 대한(특히 NearbyNetworkProtocol) 이해가 완벽히 되지 않아서 코멘트만 남깁니다..!
또한 중간중간 영어인 주석들이 있는데, 큰 문제가 되진 않겠지만 이미 있는 코드에서 복붙해왔다는 느낌이 들기도 합니다. 그러다 보니 어느부분까지 커스텀하고, 왜 해야 했는지에 대한 이해가 스스로 리뷰하는 과정에서 알기 힘들었습니다ㅠㅠ

열심히 잘 해주실것이라 생각합니다.. 시간이 많이 소모되고 힘들어 보이네요ㅠㅠ 고생하셨습니다 :)

Comment on lines +131 to +134
/// 연결됐던 기기와 연결이 끊어졌을 때 실행됩니다.
/// - Parameters:
/// - connection: 연결이 끊긴 기기
func nearbyNetwork(_ sender: NearbyNetworkInterface, didDisconnect connection: RefactoredNetworkConnection)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이젠 연결 끊겼을 때도 관리 해주겠군요..! 좋습니다 점점 앱다워지는 것 같아요!

Comment on lines +35 to +42
let option = NWProtocolFramer.Options(definition: NearbyNetworkProtocol.definition)
let parameter = NWParameters.tcp
parameter.defaultProtocolStack
.applicationProtocols
.insert(option, at: 0)
nwBrowser = NWBrowser(
for: .bonjourWithTXTRecord(type: serviceType, domain: nil),
using: .tcp)
using: parameter)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존에 .tcp로 넣던거를 parameter를 통해 옵션을 추가 해주는 방식으로 변경한 것으로 이해가 됩니다!
그렇게 해줄 경우 달라지는 이유? 달라지는 것이 무엇인지 알 수 있을까요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

구현한 커스텀 프로토콜이 적용된 파라미터를 사용할 수 있게 됩니다. !

그럴 경우 이제 데이터를 주고 받을 때 헤더를 포함하여 데이터를 송수신할 수 있고
헤더에 포함된 정보를 갖고 어떤 데이터인지 확인할 수 있게 됩니다.

바뀐 코드도 tcp를 사용중이지만 커스텀 프로토콜을 추가해 헤더를 사용할 수 있게 한 것이 다른 부분이라고 이해하시면 될 것 같습니다 !

let parameter = NWParameters.tcp


private func convertMetadata(metadata: NWBrowser.Result.Metadata) -> RefactoredNetworkConnection? {
switch metadata {
case .bonjour(let foundedPeerData):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

설명 감사합니다!!
Metadata에 대한 이해도 필요했는데 함께 설명이 된 것 같아요!
단번에 이해가 됐습니다 :)

Comment on lines +76 to +77
let hostName = dictionary[NearbyNetworkKey.host.rawValue],
let connectedPeerInfo = dictionary[NearbyNetworkKey.connectedPeerInfo.rawValue]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앞 pr에서 정의 했던 내용들을 이런 방식으로 사용되는 군요..

Comment on lines +74 to +79
// error가 아니라면, 계속해서 데이터 수신
if error == nil {
configureConnection(connection: connection)
} else {
// connection을 다시 establish 해야 함!
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

만약 error가 nil이라면, 이후에 오는 데이터도 계속해서 수신해야되기 때문에 다시 호출 해 주는 건가요?
클로저 내부에서 해당 메소드를 또 호출하고 있는 형태로 보입니다... 반복적인 행동이 지속 된다거나 클로저 메모리 해제에 대한 문제가 발생하진 않을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

데이터를 send할 때, parameter로 isCompleted라는 Bool 타입이 있습니다. 따로 명시하지 않을 경우에는 true값이 들어갑니다. 이 값을 통해 현재 전송중인 데이터가 끝났는가?, 다시 말해 단일 메시지 하나가 끝났는가를 표시할 수 있습니다.
한편, connection에서 수신한 data를 처리하는 방법은 두가지가 있습니다.

  1. 데이터를 수신할 때마다 처리 (수신할 최소, 최대 바이트 지정). 이경우에는 isComplete가 true가 아니어도 호출됩니다. (receive)
  2. 단일 메시지가 끝날 때마다 호출되어 처리. 이 경우에는 isComplete가 true이어야만 호출됩니다. (receiveMessage)

처음에 구현할 때는 이 receiveMessage를 한 번만 정의해주는 방법을 사용했는데요, 데이터를 딱 한 번 수신할 수 있었습니다. 이에 정의를
저희는 두 번째 방식을 사용하고 있습니다. receiveMessage의 정의는 아래와 같이 나와있습니다.
Schedules a single receive completion handler for a complete message, as opposed to a range of bytes.
단발성으로 한 번 호출되어 실행되면 자동으로 재설정되지 않기 때문에, 다음 수신 작업을 진행하기 위해서 다시 receiveMessage를 설정해주어야 합니다. 이에 재귀적으로 구현하였습니다.

  • 메모리 해제에 대한 문제는 발생하지 않을 것이라 생각됩니다. 연결이 끊긴 connection의 경우 nil로 바꿔주면서 RC를 감소시켜주고 있기 때문입니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

connection의 receiveMessage를 통해 받은 데이터를 처리하면,
다시 해당 connection에서 데이터를 받았을 때의 핸들러를 (receiveMessage에 대한) 정의해주어야 추후 받은 데이터들을 처리할 수 있었습니다 !!
한번 처리한 받은 데이터를 처리하고 또 이후에 받을 데이터를 처리하기 위해 connection에 대한 receiveMessage함수를 정의한 것이라고 이해하면 좋을 것 같아요 !!

에러가 아니라면 connection이 데이터를 수신하였을 때 처리할 핸들러를 등록하는 것이고,
메시지를 받았을 때에는 receiveMessage 함수를 통해 데이터 처리가 진행되니 해당 행동은 반복적으로 설정해줘야 한다고 생각합니다.
(메시지를 받기 전까지는 핸들러 등록만 한 것이니 괜찮다고 생각합니다.)

따라서 반복 호출 구조는 데이터 수신을 계속 처리하기 위해 필요한 부분이라고 생각하고 클로저는 매번 호출 이후 메모리에서 해제되니
괜찮다고 생각합니다 .. ...

Comment on lines +22 to +27
// 가장 기본적인 framing protocol 형식
required init(framer: NWProtocolFramer.Instance) { }
func start(framer: NWProtocolFramer.Instance) -> NWProtocolFramer.StartResult { return .ready }
func wakeup(framer: NWProtocolFramer.Instance) { }
func stop(framer: NWProtocolFramer.Instance) -> Bool { return true }
func cleanup(framer: NWProtocolFramer.Instance) { }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이부분은 사용되지 않는 걸까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 사용하고 있는 프로토콜의 동작 방식이 고도화 될 경우 이 부분에 구현해주면 됩니다!
너무 깊은 부분으로 들어가는 부분이기도 하고, 다른 기능 개발이 우선이라 기본 값으로 남겨두었습니다!

}

// Create a class that implements a framing protocol.
class NearbyNetworkProtocol: NWProtocolFramerImplementation {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아직 저도 이 커스텀한 프로토콜에 대한 이해가 부족한 것 같습니다..

어떤 코드들이 필요하고 왜 커스텀했는지에 대한 설명을 조금 더 해주실 수 있을까요?

개인적으로 refactoredNearbyConnection과 함께 이해하기에는 들어오는 데이터를 peerInfo와 data를 구분하여 전달받기 위한 것인가? 라는 생각을 하고 보았습니다..

@taipaise taipaise merged commit ec93040 into develop Jan 10, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Refactor] Network framework로 참여 기능 리팩터링

5 participants