Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,23 @@ public protocol NearbyNetworkInterface {
/// 주변에 내 기기 알리는 것을 중지합니다.
func stopPublishing()

/// 연결된 모든 피어와 연결을 끊습니다.
/// 연결된 모든 피어와 연결을 끊습니다.
func disconnectAll()

/// 주변 기기와 연결을 시도합니다.
/// - Parameter connection: 연결할 기기
@available(*, deprecated, message: "이 메서드는 network framework로 리팩터링 하면서 사용되지 않을 예정입니다.")
func joinConnection(connection: NetworkConnection, context: RequestedContext) throws

/// 주변 기기와 연결을 시도합니다.
/// - Parameters:
/// - connection: 연결할 기기
/// - myConnectionInfo: 내 정보
/// - Returns: 연결 요청 성공 여부
func joinConnection(
connection: RefactoredNetworkConnection,
myConnectionInfo: RequestedContext) -> Result<Bool, Never>

/// 연결된 기기들에게 데이터를 송신합니다.
/// - Parameter data: 송신할 데이터
func send(data: Data)
Expand All @@ -59,7 +69,7 @@ public protocol NearbyNetworkInterface {
@available(*, deprecated, message: "이 메서드는 network framework로 리팩터링 하면서 사용되지 않을 예정입니다.")
func send(
fileURL: URL,
info: DataSource.DataInformationDTO,
info: DataInformationDTO,
to connection: NetworkConnection) async
}

Expand All @@ -86,27 +96,40 @@ public protocol NearbyNetworkConnectionDelegate: AnyObject {
/// - Parameters:
/// - connection: 연결된 기기
/// - info: 기존 Session 정보
@available(*, deprecated, message: "이 메서드는 network framework로 리팩터링 하면서 사용되지 않을 예정입니다.")
func nearbyNetwork(
_ sender: NearbyNetworkInterface,
didConnect connection: NetworkConnection,
with info: [String: String])

/// 주변 기기와 연결에 성공했을 때 실행됩니다.
/// 주변 기기와 연결에 성공하였을 때 실행됩니다.
/// - Parameters:
/// - connection: 연결된 기기
/// - context: 참여자가 보낸 정보
/// - isHost: 호스트 여부
@available(*, deprecated, message: "이 메서드는 network framework로 리팩터링 하면서 사용되지 않을 예정입니다.")
func nearbyNetwork(
_ sender: NearbyNetworkInterface,
didConnect connection: NetworkConnection,
with context: Data?,
isHost: Bool)

/// 주변 기기와 연결에 성공했을 때 실행됩니다.
/// - Parameters:
/// - didConnect: 연결된 peer
func nearbyNetwork(_ sender: NearbyNetworkInterface, didConnect connection: RefactoredNetworkConnection)

/// 연결됐던 기기와 연결이 끊어졌을 때 실행됩니다.
/// - Parameters:
/// - connection: 연결이 끊긴 기기
/// - isHost: 호스트 여부
@available(*, deprecated, message: "이 메서드는 network framework로 리팩터링 하면서 사용되지 않을 예정입니다.")
func nearbyNetwork(
_ sender: NearbyNetworkInterface,
didDisconnect connection: NetworkConnection,
isHost: Bool)

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

Choose a reason for hiding this comment

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

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

}
8 changes: 7 additions & 1 deletion DataSource/DataSource/Sources/Model/NetworkConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,14 @@ public struct RefactoredNetworkConnection: Codable {
}
}

extension RefactoredNetworkConnection: Equatable {
extension RefactoredNetworkConnection: Hashable {
public static func == (lhs: RefactoredNetworkConnection, rhs: RefactoredNetworkConnection) -> Bool {
return lhs.id == rhs.id
}
}

extension RefactoredNetworkConnection: CustomStringConvertible {
public var description: String {
return "ID: \(id), Name: \(name)"
}
}
12 changes: 9 additions & 3 deletions DataSource/DataSource/Sources/Model/RequestedContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@
import Foundation

public struct RequestedContext: Codable {
let nickname: String
let participant: String
public let peerID: UUID
public let nickname: String
public let participant: String

public init(nickname: String, participant: String) {
public init(
peerID: UUID,
nickname: String,
participant: String
) {
self.peerID = peerID
self.nickname = nickname
self.participant = participant
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ public final class WhiteboardRepository: WhiteboardRepositoryInterface {
name: whiteboard.name,
info: participantsInfo)

let context = RequestedContext(nickname: myProfile.nickname, participant: myProfile.profileIcon.emoji)
let context = RequestedContext(
peerID: myProfile.id,
nickname: myProfile.nickname,
participant: myProfile.profileIcon.emoji)

try nearbyNetwork.joinConnection(connection: connection, context: context)
}
Expand Down Expand Up @@ -170,6 +173,13 @@ extension WhiteboardRepository: NearbyNetworkConnectionDelegate {
}
}

public func nearbyNetwork(
_ sender: any NearbyNetworkInterface,
didConnect connection: RefactoredNetworkConnection
) {
// TODO: - 연결 되었을 때 화이트보드에 프로필 정보 넘기기
}

public func nearbyNetwork(
_ sender: any NearbyNetworkInterface,
didDisconnect connection: NetworkConnection,
Expand All @@ -181,4 +191,11 @@ extension WhiteboardRepository: NearbyNetworkConnectionDelegate {
updatePublishingInfo(myProfile: myProfile)
nearbyNetwork.startPublishing(with: self.participantsInfo)
}

public func nearbyNetwork(
_ sender: any NearbyNetworkInterface,
didDisconnect connection: RefactoredNetworkConnection
) {
// TODO: - 연결 끊겼을 때 화이트보드에 프로필 정보 넘기기
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,6 @@ final class MockWhiteObjectRepository: WhiteboardObjectRepositoryInterface {
}

final class MockWhiteboardRepository: WhiteboardRepositoryInterface {

var delegate: (any WhiteboardRepositoryDelegate)?
var recentPeerPublisher: AnyPublisher<Domain.Profile, Never>
var connectionResultPublisher: AnyPublisher<Bool, Never>
Expand Down
4 changes: 4 additions & 0 deletions NearbyNetwork/NearbyNetwork.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
00549AD62CEDDDB700DF8F6C /* MCSessionState+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00549AD52CEDDDB100DF8F6C /* MCSessionState+.swift */; };
00549AD82CEDDDCF00DF8F6C /* MCSession+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00549AD72CEDDDCF00DF8F6C /* MCSession+.swift */; };
0080E86A2CE19EC40095B958 /* NearbyNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0080E8672CE19EC40095B958 /* NearbyNetworkService.swift */; };
00CA7A102D2E08DA00116F9E /* NearbyNetworkProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00CA7A0F2D2E08DA00116F9E /* NearbyNetworkProtocol.swift */; };
5B7C6EBF2CDB6C6E0024704A /* DataSource.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B7C6EBE2CDB6C6E0024704A /* DataSource.framework */; };
5B7C6EC02CDB6C6E0024704A /* DataSource.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5B7C6EBE2CDB6C6E0024704A /* DataSource.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
A83D2AB02D23BAFC007EC41F /* NearbyNetworkListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83D2AAF2D23BAFC007EC41F /* NearbyNetworkListener.swift */; };
Expand Down Expand Up @@ -51,6 +52,7 @@
00549AD52CEDDDB100DF8F6C /* MCSessionState+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MCSessionState+.swift"; sourceTree = "<group>"; };
00549AD72CEDDDCF00DF8F6C /* MCSession+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MCSession+.swift"; sourceTree = "<group>"; };
0080E8672CE19EC40095B958 /* NearbyNetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearbyNetworkService.swift; sourceTree = "<group>"; };
00CA7A0F2D2E08DA00116F9E /* NearbyNetworkProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearbyNetworkProtocol.swift; sourceTree = "<group>"; };
5B7C6E652CDB6A560024704A /* NearbyNetwork.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NearbyNetwork.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5B7C6EBE2CDB6C6E0024704A /* DataSource.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DataSource.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A83D2AAF2D23BAFC007EC41F /* NearbyNetworkListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearbyNetworkListener.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -107,6 +109,7 @@
isa = PBXGroup;
children = (
00549AD32CEDDDA300DF8F6C /* Common */,
00CA7A0F2D2E08DA00116F9E /* NearbyNetworkProtocol.swift */,
0080E8672CE19EC40095B958 /* NearbyNetworkService.swift */,
002DEC942D22A99F00ED7EE3 /* RefactoredNearbyNetworkService.swift */,
A83D2AAF2D23BAFC007EC41F /* NearbyNetworkListener.swift */,
Expand Down Expand Up @@ -297,6 +300,7 @@
A83D2AB22D23BE88007EC41F /* NearbyNetworkBrowser.swift in Sources */,
00549AD62CEDDDB700DF8F6C /* MCSessionState+.swift in Sources */,
A83D2AB02D23BAFC007EC41F /* NearbyNetworkListener.swift in Sources */,
00CA7A102D2E08DA00116F9E /* NearbyNetworkProtocol.swift in Sources */,
00549AD82CEDDDCF00DF8F6C /* MCSession+.swift in Sources */,
002DEC952D22A99F00ED7EE3 /* RefactoredNearbyNetworkService.swift in Sources */,
0080E86A2CE19EC40095B958 /* NearbyNetworkService.swift in Sources */,
Expand Down
79 changes: 57 additions & 22 deletions NearbyNetwork/NearbyNetwork/Sources/NearbyNetworkBrowser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,45 @@
// Created by 최정인 on 12/31/24.
//

import DataSource
import Foundation
import Network
import OSLog

public protocol NearbyNetworkBrowserDelegate: AnyObject {
func nearbyNetworkBrowserDidFindPeer(
_ sender: NearbyNetworkBrowser,
hostName: String,
connectedPeerInfo: [String])
foundPeers: [RefactoredNetworkConnection])
}

public final class NearbyNetworkBrowser {
private let nwBrowser: NWBrowser
private let browserQueue: DispatchQueue
private let serviceType: String
private let logger: Logger
private var foundPeers: [RefactoredNetworkConnection: NWEndpoint] {
didSet {
let foundPeers = foundPeers
.keys
.sorted(by: { $0.name < $1.name })
delegate?.nearbyNetworkBrowserDidFindPeer(self, foundPeers: Array(foundPeers))
}
}
weak var delegate: NearbyNetworkBrowserDelegate?

init(serviceType: String) {
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)
Comment on lines +35 to +42
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

self.browserQueue = DispatchQueue.global()
self.serviceType = serviceType
self.logger = Logger()
self.foundPeers = [:]
configure()
}

Expand All @@ -38,30 +52,51 @@ public final class NearbyNetworkBrowser {
}

private func browserHandler(results: Set<NWBrowser.Result>, changes: Set<NWBrowser.Result.Change>) {
for result in results {
switch result.metadata {
case .bonjour(let foundedPeerData):
let dictionary = foundedPeerData.dictionary
guard
let hostName = dictionary[NearbyNetworkKey.host.rawValue],
let connectedPeerInfo = dictionary[NearbyNetworkKey.connectedPeerInfo.rawValue]
else {
logger.log(level: .error, "connection의 데이터 값이 유효하지 않습니다.")
return
}

delegate?.nearbyNetworkBrowserDidFindPeer(
self,
hostName: hostName,
connectedPeerInfo: connectedPeerInfo
.split(separator: ",")
.map { String($0) })
for change in changes {
switch change {
case .added(let result):
guard let foundPeer = convertMetadata(metadata: result.metadata) else { return }
foundPeers[foundPeer] = result.endpoint
case .removed(let result):
guard let foundPeer = convertMetadata(metadata: result.metadata) else { return }
foundPeers[foundPeer] = nil
default:
logger.log(level: .error, "알 수 없는 피어가 발견되었습니다.")
break
}
}
}

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에 대한 이해도 필요했는데 함께 설명이 된 것 같아요!
단번에 이해가 됐습니다 :)

let dictionary = foundedPeerData.dictionary
guard
let peerIDString = dictionary[NearbyNetworkKey.peerID.rawValue],
let peerID = UUID(uuidString: peerIDString),
let hostName = dictionary[NearbyNetworkKey.host.rawValue],
let connectedPeerInfo = dictionary[NearbyNetworkKey.connectedPeerInfo.rawValue]
Comment on lines +76 to +77
Copy link
Member

Choose a reason for hiding this comment

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

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

else {
logger.log(level: .error, "connection의 데이터 값이 유효하지 않습니다.")
return nil
}

let foundPeer = RefactoredNetworkConnection(
id: peerID,
name: hostName,
connectedPeerInfo: connectedPeerInfo
.split(separator: ",")
.map { String($0) })
return foundPeer
default:
logger.log(level: .error, "알 수 없는 피어가 발견되었습니다.")
return nil
}
}

func fetchFoundConnection(networkConnection: RefactoredNetworkConnection) -> NWEndpoint? {
return foundPeers[networkConnection]
}

func startSearching() {
nwBrowser.start(queue: browserQueue)
}
Expand Down
1 change: 1 addition & 0 deletions NearbyNetwork/NearbyNetwork/Sources/NearbyNetworkKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

public enum NearbyNetworkKey: String {
case peerID
case host
case connectedPeerInfo
}
51 changes: 48 additions & 3 deletions NearbyNetwork/NearbyNetwork/Sources/NearbyNetworkListener.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,45 @@ import Foundation
import Network
import OSLog

final class NearbyNetworkListener {
public protocol NearbyNetworkListenerDelegate: AnyObject {
/// 주변 기기와 연결에 성공하였을 때 실행됩니다.
/// - Parameters:
/// - didConnect: 연결된 기기
func nearbyNetworkListener(_ sender: NearbyNetworkListener, didConnect connection: NWConnection)

/// 주변 기기와 연결이 끊겼을 때 실행됩니다.
/// - Parameters:
/// - didDisconnect: 연결이 끊긴 기기
func nearbyNetworkListener(_ sender: NearbyNetworkListener, didDisconnect connection: NWConnection)

/// 주변 기기와 연결에 실패했을 때 실행됩니다.
/// - Parameters:
/// - connection: 연결에 실패한 기기
func nearbyNetworkListenerCannotConnect(_ sender: NearbyNetworkListener, connection: NWConnection)
}

public final class NearbyNetworkListener {
weak var delegate: NearbyNetworkListenerDelegate?
private var nwListener: NWListener?
private let listenerQueue: DispatchQueue
private let peerID: UUID
private let serviceName: String
private let serviceType: String
private let logger: Logger

init(serviceName: String, serviceType: String) {
nwListener = try? NWListener(using: .tcp)
init(
peerID: UUID,
serviceName: String,
serviceType: String
) {
let option = NWProtocolFramer.Options(definition: NearbyNetworkProtocol.definition)
let parameter = NWParameters.tcp
parameter.defaultProtocolStack
.applicationProtocols
.insert(option, at: 0)
nwListener = try? NWListener(using: parameter)
listenerQueue = DispatchQueue.global()
self.peerID = peerID
self.serviceName = serviceName
self.serviceType = serviceType
self.logger = Logger()
Expand All @@ -27,12 +56,28 @@ final class NearbyNetworkListener {

private func configure() {
nwListener?.newConnectionHandler = { connection in
connection.stateUpdateHandler = { state in
switch state {
case .ready:
self.logger.log(level: .debug, "\(connection.debugDescription)와 연결되었습니다.")
self.delegate?.nearbyNetworkListener(self, didConnect: connection)
case .failed:
self.logger.log(level: .debug, "\(connection.debugDescription)와 연결에 실패했습니다")
self.delegate?.nearbyNetworkListenerCannotConnect(self, connection: connection)
case .cancelled:
self.logger.log(level: .debug, "\(connection.debugDescription)와 연결이 끊어졌습니다.")
self.delegate?.nearbyNetworkListener(self, didDisconnect: connection)
default:
self.logger.log(level: .debug, "\(connection.debugDescription)와 연결 설정 중입니다.")
}
}
connection.start(queue: self.listenerQueue)
}
}

func startPublishing(hostName: String, connectedPeerInfo: [String]) {
let connectionData = [
NearbyNetworkKey.peerID.rawValue: peerID.uuidString,
NearbyNetworkKey.host.rawValue: hostName,
NearbyNetworkKey.connectedPeerInfo.rawValue: connectedPeerInfo.joined(separator: ",")]
let txtRecord = NWTXTRecord(connectionData)
Expand Down
Loading