iOS iOS13 Deprecated scanHexInt32 수정하기

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


프로젝트의 최소 버전을 iOS13으로 올리면서 발견한 Deprecated가 있었다.

scanHexInt32 was deprecated in iOS13

이전코드는 아래와 같았다.

extension UIColor {

  convenience init(hex: String, alpha: CGFloat = 1.0) {
    var cString:String = hex.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).uppercased()

    if (cString.hasPrefix("#")) {
        cString = String(cString[cString.index(cString.startIndex, offsetBy: 1)...])
    }

    if (cString.count != 6) {
        return UIColor.gray
    }

    var rgbValue:UInt32 = 0

    Scanner(string: cString).scanHexInt32(&rgbValue)  // 해당 부분에서 위와 같은 경고 발생

    return UIColor(
        red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
        green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
        blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
        alpha: CGFloat(1.0)
    )
  }

}

이는 아래와 같이 수정해주면 된다.

convenience init(hex: String, alpha: CGFloat = 1.0) {
    var hexFormatted: String = hex.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).uppercased()

    if hexFormatted.hasPrefix("#") {
        hexFormatted = String(hexFormatted.dropFirst())
    }

    assert(hexFormatted.count == 6, "Invalid hex code used.")

    var rgbValue: UInt64 = 0
    Scanner(string: hexFormatted).scanHexInt64(&rgbValue)

    self.init(red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
              green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
              blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
              alpha: alpha)
}

이렇게 UInt64 및 scanHexInt64로 업데이트해주면 됨!

iOS 13,14,15에서 달라진 점은 무엇이 있을까?

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


UIButton

https://zeddios.tistory.com/1291

  • 버튼 내부적으로 Activity Indicator 지원

UITableView, UICollectionView DataSource: Diffable Data Source의 등장

https://velog.io/@ellyheetov/UI-Diffable-Data-Source

DataSource: 테이블뷰 혹은 컬렉션 뷰를 그리기 위한 데이터를 관리하고 UI를 업데이트 하는 역할을 함
DataSource와 달리 데이터가 달라진 부분을 추적하여 자연스럽게 UI 업데이트 함

UITableViewDataSource, UICollectionViewDataSource를 대체하는 클래스의 등장
기본적으로 두개가 하는 역할은 같다.

Diffable Data Source를 사용하면 테이블뷰나 컬렉션뷰의 달라진 부분을 자동으로 알아차리고 새로운 부분만 다시 그린다.

  • 추가적인 코드작업 없이도, 퀄리티 있는 에니메이션 적용이 가능하다.
  • 개선된 Data Source 매커니즘은 완벽하게 동기적인 버그나, 예외, 충돌 상황들을 피할 수 있게 해준다.
  • UI 데이터의 동기화 부분 대신 앱의 동적인 데이터와 내용에 집중할 수 있다.
  • identifier와 snapshot을 사용하는 간소화 된 Data 모델을 정의 하고, 이를 이용하여 UI를 업데이트 한다.

데이터가 업데이트 되기 전과 후의 section 수의 변화가 나타나는 경우 보통 에러가 나는데, 그럴때마다 우리는 reloadData()를 해줌.

사용자 경험(UX)에 있다.
reloadData()를 사용하는 경우 한번의 업데이트마다 뚝뚝 끊기는 UI가 보이는 반면 Diffable Data Source를 사용하면 변경된 데이터 부분에 대해 스스륵 사라지고 추가되는 UI 효과가 적용됨.


import UIKit

class ViewController: UIViewController {

    var dataSource: UITableViewDiffableDataSource<Int, UUID>!
    var color: [UIColor] = [.blue, .black, .brown, .cyan, .gray, .green, .magenta]


    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // tableView cell 등록
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "myCell")

        // cell 모양 정의 > cellForRowAt 델리게이트 메소드 역할
        dataSource = UITableViewDiffableDataSource<Int, UUID>(tableView: tableView, cellProvider: {tableView, indexPath, itemIdentifier in
            let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
            cell.backgroundColor = self.color[indexPath.row % self.color.count]

            return cell
        })

        // tableView dataSource 프로퍼티에 위 dataSource 프로퍼티 대입
        tableView.dataSource = dataSource

        // snapShop: 현재 UI 상태를 의미
        // appendSection 후 snapShot을 테이블 뷰에 대입되어잇는 dataSource객체에 apply > 자동으로 셀 업데이트
        var snapShot = NSDiffableDataSourceSnapshot<Int, UUID>()
        snapShot.appendSections([0])
//        snapShot.appendItems([UUID(), UUID(), UUID()])
        dataSource.apply(snapShot)
    }


    @IBAction func appendBtn(_ sender: Any) {
        var snapShot = dataSource.snapshot()
        snapShot.appendItems([UUID()])
        dataSource.apply(snapShot)
    }

    @IBAction func insetBtn(_ sender: Any) {
        var snapShot = dataSource.snapshot()
        // 첫번째 item에 삽입
        if let first = snapShot.itemIdentifiers.first {
            snapShot.insertItems([UUID()], afterItem: first)
        }

        dataSource.apply(snapShot)
    }

    @IBAction func deleteBtn(_ sender: Any) {
        var snapShot = dataSource.snapshot()
        // 마지막 item 삭제
        if let lastItem = snapShot.itemIdentifiers.last {
            snapShot.deleteItems([lastItem])
        }
        dataSource.apply(snapShot)
    }
}

UITableView, UICollectionView List Configuration

https://developer.apple.com/videos/play/wwdc2020/10097

https://shoveller.tistory.com/entry/WWDC20-Lists-in-UICollectionView

https://unnnyong.me/2020/07/02/wwdc-2020-advances-in-uicollectionview/

SheetPresentationController

https://beenii.tistory.com/189

Network Socket통신이란? (+HTTP통신이란?)

|

개인적인 연습 내용을 정리한 글입니다.
더 좋은 방법이 있거나, 잘못된 부분이 있으면 편하게 의견 주세요. :)


네트워크를 통해 서버로부터 데이터를 가져오기 위한 통신을 구현하기 위해서 크게 Http 프로그래밍과 Socket 프로그래밍 두가지가 있습니다. 각각의 특징에 대해 정리해보겠습니다.

Socket 프로그래밍

서버와 클라이언트가 특정 포트를 통해 실시간으로 양방향 통신 을 하는 방식

소켓 연결은 TCP/CP 프로토콜을 기반으로 맺어진 네트워크 연결 방식을 의미합니다. 이러한 소켓 연결방식으로 프로그래밍 하는 것을 소켓 프로그래밍이라고 하는데, 소켓 프로그래밍은 서버와 클라이언트가 특정 포트를 통해 연결을 유지하고 있고, 실시간으로 양방향 통신을 할 수 있는 방식을 의미합니다. 필요한 경우 클라이언트만 요청을 보낼 수 있는 HTTP 프로그래밍과 달리 소켓 프로그램이은 서버 역시 클라이언트에게 요청을 보낼 수 있는 것이 가장 큰 특징입니다.

뿐만 아니라, 계속 연결을 유지하는 연결지향형 방식을 지니고 있기 때문에 실시간 통신이 필요한 경우 자주 사용됩니다.

예로들어, 실시간 스트리밍 중계나 실시간 채팅과 같이 즉각적으로 정보를 주고받는 경우 사용하는 것이 특징입니다. 만약 실시간 동영상 스트리밍 방식을 HTTP 프로그래밍으로 구현했다고 가정하게 된다면, 사용자가 서버로 동영상을 ㅛ청하기 위해 동영상이 종료되는 순간까지 계속해서 Request를 보내야하고, 이러한 구조는 연결을 계속적으로 요청하기 때문에 부하가 걸리게 됩니다. 따라서 이러한 경우에는 소켓 프로그래밍을 통해 구현하는 것이 적합합니다.

  • 서버와 클라이언트가 계속 연결을 유지하는 양방향 프로그래밍 방식
  • 서버와 클라이언트가 실시간으로 데이터를 주고받는 상황이 플요한 경우 사용
  • 실시간 동영상 스트리밍이나 온라인 게임 등에 자주 사용

Http 프로그래밍

클라이언트의 요청이 있을때만 서버가 응답하여 해당 정보를 전송하고 곧바로 연결을 종료하는 방식

HTTP 프로그래밍의 가장 큰 특징은 클라이언트의 요청이 있을 때만 서버가 응답하여 처리한 후 바로 연결을 끊는 것입니다. 이러한 통신방식은 클라이언트가 요청을 보내는 경우에만 서버가 응답하는 단방향적 통신 으로 서버가 클라이언트로는 요청을 보낼 수 없는것이 큰 특징입니다.

클라이언트가 웹에서 어떤 링크를 클린한 순간에 클라이언트는 서버로 링크에 포함된 내용을 보내달라고 요청을 보내게 됩니다. 그리고 그 요청에 대한 응답을 받은 즉시 바로 둘 사이의 연결은 끊기게 됩니다. 이러한 Http 프로그래밍 방식은 실시간 연결이 아닌, 필요한 경우에만 서버로 접근하는 콘텐츠 위주의 데이터를 사용할 때 용이합니다. 만약 게시물에 대한 내용을 요청하기 위해 계속해서 실시간으로 연결을 유지하는 소켓 프로그래밍을 사용하게 된다면, 게시물을 받은 후에도 계속 통신을 위한 연결이 성립되어 부하가 걸리게 될 것입니다.

일반적으로 모바일 어플리케이션은 필요한 경우에만 서버로 정보를 요청하는 경우가 많아 서버로 Http 요청을 통해 필요한 경우에 짧게 연결을 유지함으로써 비용 및 유지 보수등 대부분의 방면에서 많은 장점을 얻을 수 있게 될 것입니다.

  • 클라이언트가 요청을 보내는 경우에만 서버가 응답하는 단방향 프로그래밍 방식
  • 서버로부터 소켓 연결을 하고 응답을 받은 후에 연결이 바로 종료
  • 실시간 연결이 아닌 응답이 필요한 경우에만 서버와 연결을 맺어 요청을 보내는 상황에 유용

Socket

소켓의 사전적 의미로는 ‘구멍’, ‘연결’, ‘콘센트’의 의미를 가집니다. 주로 전기 부품을 규격에 따라 연결할 수 있게 만들어진 ‘구멍 형태의 연결부’를 일컫는 단어인데, 가정에서 흔히 볼 수 있는 콘센트 구멍을 떠올리면 쉽게 이해할 수 있습니다. 네트워크 프로그래밍에서 소켓에 대한 의미도 사전적의미를 크게 벗어나지 않습니다. 프로그램이 네트워크에서 송수신할 수 있도록, 네트워크 환경에 연결할 수 있게 만들어진 연결부 를 바로 소켓이라고 합니다.

이러한 네트워크에 연결하기 위한 소켓은 통신을 위한 프로토콜에 맞게 만들어져야 합니다. 소켓으로 네트워크 통신 기능을 구현하기 위해서는 소켓을 만드는 것과, 만들어진 소켓을 통해 데이터를 주고 받는 절차에 대한 이해가 필요하고, 운영체제 및 프로그래밍 언어에 종속적으로 제공되는 소켓 API 사용법을 숙지해야합니다.

덤으로 케이블 분리로 인한 네트워크 단절, 트래픽 증가에 따른 데이터 전송 지연, 시스템 리소스 관리 문제로 인한 에러 등 네트워크 환경에서 발생할 수 있는 다양한 예외사항에 대해서도 처리가 필요하기 때문에 소켓 프로그래밍은 더욱 어렵게 느껴질 수도 있습니다.

클라이언트 소켓(Client Socket)과 서버 소켓(Server Socket)

두 개의 시스템(또는 프로세스)가 소켓을 통해 네트워크 연결을 만들어내기 위해서는 최초 어느 한곳에서 그 대상이 되는 곳으로 연결을 요청해야합니다. IP 주소와 포트 번호 를 통해 식별되는 대상에게 자신의 데이터 송수신을 위한 네트워크 연결을 수립할 의사가 있음을 알려야하죠. 그런데, 최초 한 곳에서 무작정 연결을 시도한다고 해서, 그 요청이 무조건 받아들여지고 연결이 되어 데이터를 주고받을 수 있는 것은 아닙니다. 한곳에서 요청을 보낸다고 하더라고 그 대상 시스템이 요청을 받아들일 준비가 되어있지 않다면 해당 요청은 무시되고 연결은 이루어지지 않습니다.

그렇기 때문에 요청을 받아들이는 곳에서도 어떤 연결 요청을 받아들일 것인지를 미리 시스템에 등록하는 절차가 필요하고, 그 이후 요청이 수신되었을 때 해당 요청을 처리할 수 있도록 준비해야 합니다.

따라서 두개의 시스템(또는 프로세스)이 소켓을 통해 데이터 통신을 위한 연결을 만들기 위해서는 연결 요청을 보내는지 또는 요청을 받아들이는 지에 따라 소켓의 역할이 나뉘게 뙤는데, 전자에 사용되는 소켓을 클라이언트 소켓이라 하고 후자에 사용되는 소켓을 서버 소켓이라고 합니다.

그런데 여기서 중요한 점은 앞서 클라이언트 소켓과 서버소켓이 전혀 별개의 소켓이라고 생각하면 안되는 것 입니다. 소켓의 역할과 구현 절차 구분을 위해 다르게 부르는 것일 뿐 전혀 다른 형태의 소켓이 아니고 단지 역할에 따라 호출되는 API 함수 종류와 순서만 다를뿐 동일한 소켓임 을 알아야합니다. 그리고 이 두 소켓은 절대로 직접 데이터를 주고 받지 않습니다. 서버 소켓은 클라이언트 소켓의 연결 요청을 받아들이는 역할만 수행할 뿐, 직접적인 데이터 송수신은 서버 소켓의 연결, 요청, 수락의 결과로 만들어지는 새로운 소켓을 통해 처리 됩니다.

소켓 API(Socket API)의 실행흐름

클라이언트 소켓(Client Socket)

클라이언트 소켓은 처음 소켓을 생성(create) 한 다음, 서버 측에 연결(connect) 을 요청합니다. 그리고 서버 소켓에서의 연결이 받아들여지면 데이터를 송수신(send/recv) 하고, 모든 처리가 완료되면 소켓을 닫습니다(close).

서버 소켓(server socket)

서버 소켓은 클라이언트보다는 조금 복잡한 과정을 거칩니다. 우선 클라이언트와 마찬가지로 처음 소켓을 생성(create) 합니다. 그리고 서버가 사용할 IP주소와 포트 번호를 생성해 소켓에 결합(bind) 합니다. 이후 클라이언트로부터 연결 요청이 수신되는 지 주시(listen) 하고, 요청이 수신되면 요청을 받아들어(accept) 데이터 통신을 위한 새로운 소켓을 생성합니다. 그렇게 새로운 소켓을 통해 연결이 수립(ESTABLISH)되면, 클라이언트와 마찬가지로 데이터를 송수신(send/recv) 할 수 있게 되고 데이터 송수신이 완료되면 소켓을 닫습니다(close).

클라이언트 소켓 프로그래밍(Client Socket Programming)

클라이언트 소켓생성(socket())

소켓 통신을 위해 가장 먼저 해야할 일은 소켓을 생성하는 것입니다. 이때 소켓의 종류를 지정할 수 있는데, TCP 소켓을 위해서는 스트림(stream)타입, UDP 소켓을 위해서는 데이터그램(Datagram) 타입을 지정할 수 있습니다.

이 최초 소켓이 만들어지는 시점에는 어떠한 연결 대상 에 대한 정보는 들어있지 않습니다. 그저 껍데기 뿐인 소켓 하나가 만들어진 것 뿐입니다.

연결 요청(connect())

connect() API는 IP주소포트번호 로 식별되는 대상에 연결 요청을 보냅니다.

이 API는 블럭방식으로 동작하는데, 블럭 방식이라는 것은 연결 요청에 대한 결과(성공, 거절 등)가 결정되기 전에는 connect()의 실행이 끝나지 않는 것을 의미합니다. 그렇기 때문에 connect API가 실행되지 마자 실행결과와 관계없이 무조건 결과가 리턴될 것이라고 가정해서는 안됩니다. 이 connect API 호출이 성공하면 이제부터 데이터의 송수신(send/recv) API를 통해 데이터를 주고받을 수 있게 됩니다.

데이터 송수신(send()/recv())

연결된 소켓을 통해 데이터를 보낼때는 send(), 데이터를 받을 때는 recv API를 사용합니다. 이 두 API 또한 블럭 방식으로 동작됩니다. 즉, 두 API 모두 실행결과가 결정되기 전까지는 API에 대한 결과가 리턴되지 않는 것을 의미하죠. 그중에서도 특히 recv() API는 데이터가 수신되지 않거나 에러가 발생하기 전에는 실행이 종료되지 않기 땜누에 데이터 수신 작업은 단순하게 처리하기가 쉽지는 않습니다.

send()의 경우 데이터를 보내는 주체가 자기 자신이기 때문에 얼마만큼의 데이터를 보낼지, 언제 보낼지를 알수 있지만, recv()의 경우에는 통신 대상이 언제, 언떤 데이터를 보낼 지 특정할 수 없기 때문에 해당 API는 한번 실행되면 언제 끝날지 모르는 상태가 됩니다.

따라서 데이터 수신을 위한 recv() API는 별도의 스레드에서 작업이 이루어집니다. 소켓의 생성과 연결이 완료된 후 새로운 스레드를 하나 더 만들어 그곳에서 recv()를 실행하고 데이터가 수신되길 기다리는 것이죠.

소켓닫기(close())

더 이상 데이터 송수신이 필요없게 되면 소켓을 닫기 위해 close() API를 호출합니다. 이렇게 close()에 의해 닫힌 소켓은 더 이상 유효한 소켓이 아니기 때문에 해당 소켓을 사용해 데이터를 송수신할 수 없게 됩니다. 만약 소켓 연결이 종료된 후 다시 데이터를 주고받고자 한다면 다시 한번 소켓의 생성, 연결 과정을 통해 소켓이 데이터를 송수신할 수 있는 상태가 되어야 합니다.

예시

import UIKit
import SocketIO

class SocketIOManager: NSObject {
  static let shared = SocketIOManager()
  var manager = SocketManager(socketURL: URL(string: "http://localhost:9000")!, config: [.log(true), .compress])
  var socket: SocketIOClient!

  override init() {
    super.init()
    socket = self.manager.socket(forNamespace: "/test")
    socket.on("test") { dataArray, ack in   // test로 송신된 이벤트 수신
      print(dataArray)
    }
  }

  func establishConnection() {
    socket.connect()  // 설정한 주소와 포트로 소켓 연결 시도
  }

  func closeConnection() {
    socket.disconnect()  // 소켓 연결 종료
  }

  func sendMessage(message: String, nickname: String) {
    socket.emit("event", ["message" : "This is a test message"])  // event라는 이름으로 뒤 데이터 송신
    socket.emit("event1", [["name" : "ns"], ["email" : "@naver.com"]])
    socket.emit("event2", ["name" : "ns", "email" : "@naver.com"])
    socket.emit("msg", ["nick": nickname, "msg" : message])
  }
}
  • socket.IO에서는 소켓을 룸으로 나누어 소켓을 룸단위로 구분
    • 클라이언트가 /test 룸에 속한 소켓이라면 서버에서도 /test룸으로 설정하고 처리해줘야 통신 가능

서버 소켓 프로그래밍(Server Socket Programming)

클라이언트 소켓을 처리하는 과정의 API는 비교적 간단하지만 서버 소켓의 경우 그 처리 과정이 조금 복잡합니다.

서버 소켓 생성(socket())

클라이언트 소켓과 마찬가지로 서버 소켓을 사용하려면 최초에 소켓을 생성해야 합니다.

서버 소켓 바인딩(bind())

bind의 사전적의미로 ‘결합하다’, ‘구속하다’, ‘묶다’등의 의미를 가지고 있습니다. bind() API에서 사용되는 인자는 두가지 소켓포트번호(또는 IP+포트번호) 입니다. 즉 사전적 의미로 바라보면 소켓과 포트번호를 결합한다는 의미입니다.

보통 시스템에는 많은 수의 프로세스가 동작합니다. 만약 어떤 프로세스가 TCP 또는 UDP 프로토콜을 사용한다면 각 표준에 따라 소켓은 시스템이 관리하는 포트 중 하나의 포트 번호를 사용하게 됩니다. 그런데 만약 소켓이 사용하는 포트 번호가 다른 소켓의 포트 번호와 중복된다면 어떻게 될까요?

모든 소켓이 1000이라는 동일한 포트번호를 사용하게 된다면, 네트워크를 통해 1000번 포트로 어떤 데이터가 수신될 때 어떤 소켓으로 이를 처리해야할 지 결정할 수 없는 문제가 발생하게 될 것 입니다.

그렇기 때문에 운영체제에서는 소켓들이 중복된 포트번호를 사용하지 않게 하기 위해 내부적으로 포트번호화 소켓 연결정보를 관리합니다.

그리고 bind()API에서는 해당 소켓이 지정된 포트 번호를 사용할 것이라는 것을 운영체제에 요청하는 것이 바로 해당 API의 역할입니다. 만약 지정된 포트 번호를 다른 소켓이 사용하고 있다면 bind() API는 에러를 리턴합니다. 즉 일반적으로 서버 소켓은 고정된 포트번호를 사용합니다. 그리고 그 포트 번호를 통해 클라이언트의 연결 요청을 받아들입니다. 그리고 운영체제가 특정 포트 번호를 서버 소켓이 사용하도록 만들기 위해 소켓과 포트 번호를 결합하는데 이를 결합하기 위해 사용하는 API 가 바로 bind()인 것입니다.

이를 소켓바인드, 소켓 바인딩이라고도 부릅니다.

클라이언트 연결 요청 대기(listen())

서버 소켓에 포트번호를 결합하고 나면 서버 소켓을 통해 클라이언트 연결 요청을 받아들일 준비가 되고, 이제는 클라이언트에 의한 연결요청이 수신될 때까지 기다리게 됩니다. listen()API가 그 역할을 수행합니다.

서버 소켓에 바인딩된 포트 번호를 통해 클라이언트의 연결 요청이 있는지 확인하며 대기상태에 머물게 되고, 클라이언트에서 호출된 connect() API에의해 연결요청이 수신되는지 귀 기울이고 있다가 요청이 수신되면 그 때 대기 상태를 종료하고 결과를 리턴합니다. 이렇게 listen() API가 대기 상태에서 빠져나오는 경우는 크게 두가지 입니다.

  1. 클라이언트 요청이 수신되는 경우
  2. 에러가 발생하는 경우

그런데 listen()API가 성공한 경우라도 리턴 값에는 클라이언트 요청에 대한 정보는 들어있지 않는 것이 특징입니다. 이때 반환되는 리턴값에서 판단할 수 있는 것은 단 두가지로 연결 요청이 수신되었는지(success), 그렇지 않고 에러가 발생했는지(fail) 뿐입니다.

그리고 이 클라이언트 연결 요청에 대한 정보는 시스템 내부적으로 관리되는 큐(queue)에 쌓이게 되는데, 이 시점은 클라이언트와의 연결은 아직 완전히 연결된 상태라고는 할 수없는 여전한 대기상태임을 놓치지 말아야 합니다. 이렇게 대기 중이 연결 요청을 큐로부터 꺼내와서 연결을 완료하기 위해서는 accept()API를 호출해야합니다.

클라이언트 연결 수립(accept())

다시 한번, listen() API가 클라이언트 연결 요청을 확인하고 문제없이 리턴(success)한다고 해서, 클라이언트와의 연결 과정이 모두 완료된 것은 아닙니다. 아직 실질적인 소켓 연결(connection)을 수립하는 절차가 남아있습니다. 즉 최정적으로 연결 요청을 받아들이는 역할을 수행하는 것은 accept() API입니다.

연결 요청을 받아들여 소켓 간 연결을 수립하는 것이 바로 이 API의 역할입니다. 그런데 여기서 가장 중요한 점은 최종적으로 데이터 통신을 위해 연결되는 이 소켓은 앞서 bind(), listen() API에서 사용한 서버 소켓이 아니라는 점입니다. 즉, 클라이언트 소켓과 연결이 만들어지는 소켓은 앞서 사용했던 서버 소켓이 아닌 accept()API 내부에서 만들어진 새로운 소켓이라는 점 입니다.

서버 소켓의 핵심역할은 클라이언트의 연결 요청을 수신!하는 것입니다. 이를 위해 bind() 및 listen()을 통해 소켓에 포트번호를 바인딩하고 요청 대기 큐를 생성해 클라이언트의 요청을 대기하였죠. 그리고 이후 accept() API에서 데이터 송수신을 위한 새로운 소켓을 만들고 서버 소켓의 대기 큐에 쌓여있는 첫번째 연결요청을 매핑 시킵니다. 이렇게 하나의 연결 요청을 처리하기 위한 서버 소켓의 역할은 끝나게 됩니다.

데이터 송수신(send()/recv())

이제 실질적인 데이터 송수신은 accept()API에서 생성된 연결이 수립된(Establiched)된 소켓을 통해 처리 됩니다.
데이터를 송수신하는 과정은 클라이언트 소켓 처리 과정 내용과 동일합니다.

소켓 연결 종료(close())

클라이언트 소켓 처리 과정과 마찬가지로 소켓을 닫기 위해 close() API를 호출합니다.

그런데 서버 소켓에서는 close()의 대상이 하나만 있는것이 아니란 것이 중요합니다. 최초 socket() API를 통해 생성한 서커 소켓에 더해 accept() API 호출에 의해 생성된 소켓 또한 관리해야하기 때문이죠.

예시

아래는 Socket.IO 문서에 나오는 Server를 구현하는 예시 코드입니다.

var app = require('http').createServer(handler)  // http 서버를 생성
var io = require('socket.io')(app);  // 소켓 생성
var fs = require('fs');

app.listen(80);  // 80번 포트를 연결해 클라이언트 요청을 대기

// 이제 클라이언트 소켓은 localhost:80으로 연결을 요청

io.on('connection', function (socket) {  // connection되면
  socket.emit('news', { hello: 'world' });  // 클라이언트로 news라는 키로 뒤 객체를 보냄
  socket.on('my other event', function (data) {  
    // 클라이언트에서 서버로 보낸 데이터중 'my other event'라는 키로 들어오는 값을 받아 console.log 출력
    console.log(data);
  });
});

iOS Xcode13에서 pod init 실패하는 경우 해결방법!

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


Pod init의 실패???

Xcode13으로 프로젝트를 생성 후 pod init을 하려하니 아래와 같은 이상한 에러가 발생한다.

――― MARKDOWN TEMPLATE ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

### Command

/opt/homebrew/Cellar/cocoapods/1.10.1_1/libexec/bin/pod init


### Report

* What did you do?

* What did you expect to happen?

* What happened instead?


### Stack

   CocoaPods : 1.10.1
        Ruby : ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [arm64-darwin20]
    RubyGems : 3.2.22
        Host : macOS 11.4 (20F71)
       Xcode : 13.0 (13A233)
         Git : git version 2.32.0
Ruby lib dir : /opt/homebrew/Cellar/ruby/3.0.2/lib
Repositories : trunk - CDN - https://cdn.cocoapods.org/


### Plugins

cocoapods-deintegrate : 1.0.4
cocoapods-plugins     : 1.0.0
cocoapods-search      : 1.0.0
cocoapods-trunk       : 1.5.0
cocoapods-try         : 1.2.0

### Error

RuntimeError - [Xcodeproj] Unknown object version.
/opt/homebrew/Cellar/cocoapods/1.10.1_1/libexec/gems/xcodeproj-1.19.0/lib/xcodeproj/project.rb:227:in `initialize_from_file'
/opt/homebrew/Cellar/cocoapods/1.10.1_1/libexec/gems/xcodeproj-1.19.0/lib/xcodeproj/project.rb:112:in `open'
/opt/homebrew/Cellar/cocoapods/1.10.1_1/libexec/gems/cocoapods-1.10.1/lib/cocoapods/command/init.rb:41:in `validate!'
/opt/homebrew/Cellar/cocoapods/1.10.1_1/libexec/gems/claide-1.0.3/lib/claide/command.rb:333:in `run'
/opt/homebrew/Cellar/cocoapods/1.10.1_1/libexec/gems/cocoapods-1.10.1/lib/cocoapods/command.rb:52:in `run'
/opt/homebrew/Cellar/cocoapods/1.10.1_1/libexec/gems/cocoapods-1.10.1/bin/pod:55:in `<top (required)>'
/opt/homebrew/Cellar/cocoapods/1.10.1_1/libexec/bin/pod:23:in `load'
/opt/homebrew/Cellar/cocoapods/1.10.1_1/libexec/bin/pod:23:in `<main>'

――― TEMPLATE END ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

[!] Oh no, an error occurred.

Search for existing GitHub issues similar to yours:
https://github.com/CocoaPods/CocoaPods/search?q=%5BXcodeproj%5D+Unknown+object+version.&type=Issues

If none exists, create a ticket, with the template displayed above, on:
https://github.com/CocoaPods/CocoaPods/issues/new

Be sure to first read the contributing guide for details on how to properly submit a ticket:
https://github.com/CocoaPods/CocoaPods/blob/master/CONTRIBUTING.md

Don't forget to anonymize any private data!

Looking for related issues on cocoapods/cocoapods...
 - RuntimeError - [Xcodeproj] Unknown object version.
   https://github.com/CocoaPods/CocoaPods/issues/10984 [closed] [12 comments]
   5 days ago

 - Unknown object version
   https://github.com/CocoaPods/CocoaPods/issues/10973 [closed] [10 comments]
   3 weeks ago

 - RuntimeError - [Xcodeproj] Unknown object version.
   https://github.com/CocoaPods/CocoaPods/issues/7458 [closed] [21 comments]
   2 weeks ago

and 70 more at:
https://github.com/cocoapods/cocoapods/search?q=%5BXcodeproj%5D%20Unknown%20object%20version.&type=Issues&utf8=

처음보는 엄청난 에러창에 조금 놀랐지만, 해당 에러는 Xcode13 버그인걸로 보인다.
이를 해결하는 방법은 간단하다. 두가지 방법이 있는데…

첫번째 해결방법

터미널 콘솔에 아래 코드를 입력해준다.

sudo xcode-select -s /Application/Xcode-beta.app/Contents/Developer

두번째 해결방법

위 사진처럼 Project DocumentProject Format을 변경해준다.

  • Xcode 12.0-Compatible로 변경해주면 됩니다.

위 두 방법 중 하나를 선택해 pod init을 진행하면 해결완료!

Network TCP와 UDP

|

개인적인 연습 내용을 정리한 글입니다.
더 좋은 방법이 있거나, 잘못된 부분이 있으면 편하게 의견 주세요. :)


소켓을 공부하려다 보니 TCP/UDP 개념이 필요하게 되었습니다.
그 이유는 위 두개의 프로토콜이 소켓에서 데이터를 보내기 위해 사용하는 프로토콜이기 때문이죠!

TCP(스트림-양방향/연결형)

인터넷 상에서 데이터를 메시지 형태로 보내기 위해 IP와 함께 사용하는 프로토콜

일반적으로 TCP와 IP는 같이 사용이 되는데, 이때 IP는 데이터의 배달을 처리한다면 TCP는 패킷을 추적 및 관리하게 됩니다.
이때 패킷이란 데이터 조각을 의미합니다. 인터넷 내에서 데이터를 전송하기 위해서는 데이터를 통쨰로 전송하지 않고 경로배정(라우팅)을 효율적으로 하기위해 데이터를 여러 조각으로 나누어 전송을 하게 됩니다. 이때 조각조각 나뉘어진 데이터를 패킷이라고 합니다. 이렇게 조각조각 나누어진 패킷에는 각각의 번호가 부여되고 해당 번호를 통해 데이터가 온전하게 전달되었는지를 확인할 수 있게 됩니다.

이러한 TCP의 특징은 아래와 같습니다.

  1. 3-Way Handshake
  2. 연결형 서비스로 가상회선 방식을 제공
  3. 흐림제어 및 혼잡제어
  4. 높은 신뢰성
  5. UDP보다 낮은 속도

1. 3-Way Handshake

첫번째 특징으로 3-Way Handshake는 목적지와 수신지를 정확히 하여 정확한 전송을 보장하기 위해 하는 방식을 의미합니다.
이러한 정확한 전송을 보장하기 위해 Ack와 재전송 을 이용한답니다.

TCP는 패킷을 받을 때마다 Ack라는 별도의 패킷을 만들어 보냅니다. 이때 Ack는 Acknoledge의 약자로 말 그래도 잘 받았다! 라는 의미로 사용합니다. 즉 송신자가 패킷을 보내면 수신자는 n번 패킷 잘 받았으니 n+1번 패킷 보내주세요 라는 Ack를 보내게 됩니다. 즉 이를 통해 데이터가 잘 오고 갔음을 체크할 수 있게 됩니다. 재 전송이라는 것은 말그대로 Ack가 오지않았을때 송신자가 다시 수신자에게 패킷을 보내는 것을 의미합니다. 이때 Ack가 오지않는 경우는 다음과 같은 두가지 경우입니다.

  1. 수신자가 패킷을 받지 못해 정말로 Ack를 보내지 못한 경우
  2. 수신자가 패킷을 잘 받아서 Ack를 보냈지만 중간에 Ack가 유실된 경우

둘중 어떤 경우로 인해 Ack가 안온지는 알 수 없지만, 송신자는 일정 시간동안 Ack가 오지않으면 패킷을 재전송합니다.
비효율적으로 보일지는 몰라고 데이터를 못받을 일은 없으니 확실히 안정적이게 됩니다.

2. 연결형 서비스로 가상회선 방식을 제공

가상회선을 제공한다는 것은 아래 사진과 같습니다.

이렇게 위 사진에서도 볼 수 있듯 패킷 1,2,3은 사이좋게 정해진 파란색(가상회선)으로 전달되어지고 있습니다.
바로 저 파랑색 선이 가상회선(논리적 경로)가 되는 것입니다.

TCP에서 가상회선을 배정해주기 때문에 패킷들은 이 회선을 따라 움직이기만 하면 됩니다.
이를 통해 알 수 있는것은 데이터들이 전달될때 패킷이라는 데이터 조각으로 조각조각 나누어 전달되는것처럼 보여도 사실상 가상회선을 따라 데이터들이 순차적으로 움직이고 있기 때문에 패킷의 순서가 뒤바뀔일도 없게 되는 것이죠.

3. 흐름제어 및 혼잡제어

TCP는 흐름제어와 혼잡제어가 가능합니다.

  • 혼잡제어: 데이터를 송신하는 곳과 수신하는 곳의 데이터 처리속도를 조절합니다. 이를 통해 수신자의 버퍼 오버플로우를 방지할 수 있게 되죠
    • 송신자가 미친듯이 데이터를 보내버리면 수신자에게 데이터 처리를 하는데 문제가 생길 수 있기 때문에 이를 조절해줍니다.
  • 혼잡제어: 네트워크 내 패킷 수가 넘치지 않게 방지합니다. 패킷의 수가 너무 많아지면 패킷을 조금만 전송하여 네트워크의 혼잡을 막는것이죠

이렇게 TCP는 기능은 많아보이지만 그만큼 CPU의 부담도 커지기때문에 속도가 느려진다는 단점이 존재합니다.

따라서 TCP는 연속적으로 데이터를 주고받을 때 보다는 신뢰성 있는 연결을 중시할 때 사용된다고 합니다.
연속적으로 데이터를 주고받는 다는 것은 쉽게 말해 영상같은 실시간 스트리밍 데이터를 주고받을 때를 생각하면 쉽게 이해할 수 있습니다. 영상 스트리밍의 경우 1초만에도 수많은 데이터 즉 엄청나게 많은 프레임 패킷을 받게 됩니다. 이럴때 프레임 패킷 하나가 손실되었다고 해서 다시 손실된 데이터를 요청한다면, 실시간이라는 것이 결국 의미가 없어지게 될 것입니다. 이때는 프레임패킷 하나 손실되었다 해서 재요청을 하는것보다는 무시하고 계속 재생되는 것이 중요할 테니까요.

UDP(데이터그램-비연결형)

UDP에서는 데이터를 데이터그램 단위로 처리하는 것이 특징입니다.

이 데이터그램은 독립적인 데이터 단계를 지니는 패킷을 의미하는데, UDP도 데이터를 받기 위해 TCP와 같이 IP를 사용합니다. 다만 소켓을 만들어 서로 연결을 맺는 구조가 아닌 소켓을 만들어 그냥 UDP서버 IP, Port로 데이터를 보내버리는 개념입니다. 따라서 UDP 서버 하나에 여러 클라이언트들이 붙어 데이터를 받을 수 있게 되는 것을 의미합니다. 즉 명시적으로 연결을 맺지 않는 비 연결형 소캣이라고도 부릅니다.

일반적으로 TCP를 전화에, UDP를 편지로 비교한다고 합니다.

  • TCP의 경우 상대방의 번호(IP, Port)를 통해 전화를 걸어 연결이 되면 서로 의사소통을 주고 받죠
    • 들었니? > 아니! 다시말해줘
  • UDP의 경우 상대방의 번호(IP, Port)만 알면 그냥 편지에 데이터를 써서 우체통에 넣어버립니다.
    • 수신자는 자신의 편지통에 편지가 왔는지 직접 확인할때까지는 알수가 없고, 송신자 또한 편지를 보내긴 했지만 실제 그 편지를 받았는지도 읽었는지도 중간에 사라져버렸는지도 확인할 수도 관심도 없습니다.

이러한 UDP는 TCP와 달리 비연결형 소켓이기 때문에 연결을 위해 할당되는 논리적 경로가 없는것이 특징입니다.

TCP와 달리 파란색 가상선이 존재하지 않는 것을 볼 수 있습니다.

그렇기 때문에 UDP는 데이터들이 서로 다른 경로로 전송이 되고 각각의 패킷은 독립적인 관계이면서 실제 보낸 순서대로 도착하지 않을 수도 있습니다.
이런 패킷들을 데이터그램이라 하고 이러한 방식은 UDP라고 합니다.

이러한 UDP의 특징은 아래와 같습니다.

  1. 비연결형 서비스로 데이터 그램방식을 제공
  2. 정보를 주고받을 때 Handshake와 같은 신호절차를 거치지 않음
  3. 최소한의 에러만 검출
  4. 데이터가 제대로 도착할수도, 중간에 유실될 수도, 순서가 바뀌어 도착할수도 있음
  5. TCP보다 빠른 속도
  6. 낮은 신뢰성

UDP의 경우 연결을 맺고 끊는 과정이 존재하지 않고, Ack를 보내지도 않으며, 패킷 순서를 부여하여 추적 관리하거나 혼잡, 흐름 제어도 하지 않기 때문에 속도가 빠르고 네트워크 부하도 적습니다. 그러나 중간에 패킷 손실과 같은 것을 체크할 수도 없기 때문에 데이터 전송면에서 신뢰성이 떨어지는 것도 사실입니다.

따라서 TCP와 반대로 UDP는 신뢰성보다 연속성이 중요한 서비스에서 사용되는것이 옳습니다.