[iOS, macOS layer] libSystem / CFNetwork / URLSession / 네트워크로 보는 Core OS, Core Services

2024. 5. 4. 04:15🍏/OS

Cocoa Fundamentals를 정독하던 도중 CoresOS와 CoreFoundation에 대한 궁금증이 생겨서 사용해보고 이해를 돕기위해 이 글을 작성합니다. 아래 링크는 해당 내용을 번역, 알아보기 쉽게 작성한 블로그 글입니다.

 

[MacOS, iOS] Cocoa Fundamentals Guide / 코코아 기본 사항

 

chanhhh.tistory.com

 

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/WhatIsCocoa/WhatIsCocoa.html


Cocoa framework의 계층은 위와같이 나뉠 수 있습니다.

그 중 Core OS 레벨에는 커널, 파일 시스템, 네트워킹 인프라, 보안, 전원 관리 및 여러 장치 드라이버가 포함되어 있습니다. 또한 POSIX/BSD 4.4/C99 API 사양을 지원하고 많은 서비스에 대한 시스템 수준 API를 포함하는 libSystem 라이브러리도 있습니다.

libSystem의 주요 기능과 특징

  1. POSIX API: POSIX (Portable Operating System Interface) 표준을 준수하는 API를 제공합니다. 이는 파일 시스템, 스레드, 프로세스 관리 등의 기본적인 운영 체제 기능을 제공합니다.
  2. 시스템 호출 및 라이브러리: libSystem은 C언어를 기반으로 한 다양한 시스템 호출과 라이브러리 함수를 제공합니다. 이는 운영 체제의 핵심적인 서비스와 리소스에 접근할 수 있는 방법을 제공합니다.
  3. 동적 링킹: 이 라이브러리는 macOS 및 iOS의 모든 실행 파일이 기본적으로 링크하는 동적 라이브러리로, 다양한 기능과 서비스를 통합적으로 제공합니다.
  4. 스레드 및 프로세스 관리: 스레드 생성, 동기화, 프로세스 생성 및 관리 등의 기능을 통해 멀티태스킹과 병렬 컴퓨팅을 지원합니다.
  5. 네트워킹과 통신: 소켓 API와 같은 네트워킹 기능을 제공하여 서버와 클라이언트 프로그램의 구현을 지원합니다.
  6. 메모리 관리: 메모리 할당, 해제 등의 기능을 제공하여 시스템 리소스를 효율적으로 관리할 수 있도록 돕습니다.

POSIX 같은 경우에는 Darwin을 직접적으로 import 해서 사용할 수 있다는 것은 인지하고 있었습니다. 더보기를 통해서 간단한 예시를 확인할 수 있습니다.

더보기
import Darwin

// 파일을 열고 쓰기
func manipulateFile() {
    let path = "/path/to/your/file.txt"
    let fd = open(path, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)
    if fd == -1 {
        perror("Could not open file")
        return
    }

    let text = "Hello, world!\n"
    write(fd, text, strlen(text))
    close(fd)
}

// 현재 process의 pid를 출력
func printProcessInfo() {
    let pid = getpid()
    print("Current process ID: \(pid)")
}

// Signal 핸들링
func handleSignal() {
    signal(SIGINT) { _ in
        print("SIGINT received")
        exit(0)
    }
}

// 시스템 시간을 출력
func printSystemTime() {
    var tv = timeval()
    gettimeofday(&tv, nil)
    print("Seconds: \(tv.tv_sec), Microseconds: \(tv.tv_usec)")
}

// 디렉토리 생성
func createDirectory() {
    let path = "~/new"
    mkdir(path, S_IRWXU)
}

// ls와 유사한 동작 디렉토리를 열고 파일이름을 읽으며, 디렉토리 스트림을 정상적으로 닫는 것.
func safeListFilesInDirectory(directory: String) {
    guard let dp = opendir(directory) else {
        print("Cannot open directory")
        return
    }
    defer { closedir(dp) }

    while let dir = readdir(dp) {
        let dNameArray = withUnsafeBytes(of: dir.pointee.d_name) { rawBufPtr -> [CChar] in
            let bufPtr = rawBufPtr.baseAddress!.assumingMemoryBound(to: CChar.self)
            return Array(UnsafeBufferPointer(start: bufPtr, count: 256))  // 256 is typical size for d_name
        }
        if let filename = String(validatingUTF8: dNameArray) {
            print("Found file: \(filename)")
        }
    }
}

 

Core OS layer

libSystem으로 할 수 있는 네트워크 통신 관련을 생각해보다가 이전에 C++98로 웹서버를 만들어본 경험을 살려서 Core OS로 네트워크 통신을 받을 수 있는 서버를 아래와 같은 코드로 구현해 볼 수 있었습니다. 

libSystem은 macOS와 iOS에서 가장 기본적인 시스템 라이브러리로, 저수준의 시스템 호출과 서비스를 제공합니다. 이는 POSIX API, 스레드 관리, 파일 시스템 작업 등을 포함합니다. 네트워킹에 관련하여, 소켓과 같은 기본적인 네트워크 통신 기능을 아래와 같이 직접적으로 제어할 수 있습니다.

import Darwin

func startServer() {
    let port: UInt16 = 8080
    var serverAddr = sockaddr_in()
    var serverSocket: Int32 = socket(AF_INET, SOCK_STREAM, 0)
    
    if serverSocket == -1 {
        perror("Socket creation failed")
        return
    }
    
    serverAddr.sin_family = sa_family_t(AF_INET)
    serverAddr.sin_addr.s_addr = inet_addr("0.0.0.0")
    serverAddr.sin_port = htons(value: port)
    
    withUnsafePointer(to: &serverAddr) {
        $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
            if bind(serverSocket, $0, socklen_t(MemoryLayout<sockaddr_in>.size)) == -1 {
                perror("Socket bind failed")
                Darwin.close(serverSocket)
                return
            }
        }
    }
    
    if listen(serverSocket, 10) == -1 {
        perror("Listening failed")
        Darwin.close(serverSocket)
        return
    }
    
    print("Server is running on port \(port)")
    
    var clientAddr = sockaddr_in()
    var addrLen = socklen_t(MemoryLayout<sockaddr_in>.stride)
    
    let clientSocket = withUnsafeMutablePointer(to: &clientAddr) { pointer -> Int32 in
        let addrPointer = UnsafeMutableRawPointer(pointer).assumingMemoryBound(to: sockaddr.self)
        return accept(serverSocket, addrPointer, &addrLen)
    }
    
    if clientSocket == -1 {
        perror("Accept failed")
        Darwin.close(serverSocket)
        return
    }
    
    let message = "Hello from chanhihi server!\n"
    message.withCString { cstr -> Void in
        send(clientSocket, cstr, strlen(cstr), 0)
    }
    
    Darwin.close(clientSocket)
    Darwin.close(serverSocket)
    print("Server shutdown")
}

//nc 0.0.0.0 8080로 접속 가능
startServer()

 

Core Services layer

CoreFoundation를 담고있는 CFNetwork로 네트워크 통신에 대해서 예시를 들어보려고합니다. Core OS보다 한 단계 더 추상화된 레벨로 CFNetwork 는 HTTP 프로토콜의 구현을 포함하며, 네트워크 세션관리, 연결 지속성(persistent connections), 리소스 캐싱 등을 제공합니다. 

import CFNetwork

func sendSecureHTTPRequestToApple() {
        let url = "https://www.apple.com"
        guard let urlCF = URL(string: url) as CFURL? else {
            print("Invalid URL")
            return
        }

        let request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET" as CFString, urlCF, kCFHTTPVersion1_1).takeRetainedValue()
        let readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request).takeRetainedValue()
        let sslOptions = [
            kCFStreamSSLValidatesCertificateChain: kCFBooleanTrue
        ] as CFDictionary
        CFReadStreamSetProperty(readStream, CFStreamPropertyKey(kCFStreamPropertySSLSettings), sslOptions)

        if !CFReadStreamOpen(readStream) {
            print("Could not open stream")
            return
        }

        var totalData = Data()
        var streamError = CFStreamError()
        var bytesRead = 0
        let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: 1024)
        defer { buffer.deallocate() }

        var streamStatus = CFReadStreamGetStatus(readStream)
        while streamStatus != .atEnd {
            if CFReadStreamHasBytesAvailable(readStream) {
                bytesRead = CFReadStreamRead(readStream, buffer, 1024)
                if bytesRead > 0 {
                    totalData.append(buffer, count: bytesRead)
                } else if bytesRead < 0 {
                    streamError = CFReadStreamGetError(readStream)
                    print("Stream read error: \(streamError)")
                    break
                }
            }
            streamStatus = CFReadStreamGetStatus(readStream)  // 상태 업데이트
        }

        CFReadStreamClose(readStream)
        
        if let responseString = String(data: totalData, encoding: .utf8) {
            print("Received chanhihi response: \(responseString)")
        } else {
            print("Failed to decode response")
        }
    }

 

Application Services

URLSessionCFNetwork를 더욱 추상화하여 제공하는 고수준 API이며, Foundation 프레임워크의 일부로 분류됩니다. 이 API는 CFNetworklibSystem의 기능을 사용하지만, 사용자에게 보다 간단하고 강력한 인터페이스를 제공합니다. URLSession은 네트워크 요청을 보다 쉽게 만들고, 응답을 처리하며, 에러 핸들링과 세션 관리를 자동으로 수행합니다. 또한, 멀티태스킹과 백그라운드 다운로드를 지원하는 등 iOS와 macOS의 다양한 시스템 기능과도 잘 통합되어 있습니다.

import Foundation

func sendSecureHTTPRequestToApple() {
    let url = "https://www.apple.com"
    guard let url = URL(string: url) else {
        print("Invalid URL")
        return
    }

    let request = URLRequest(url: url)
    let session = URLSession(configuration: .default)

    let task = session.dataTask(with: request) { data, response, error in
        if let error = error {
            print("Client error: \(error.localizedDescription)")
            return
        }

        guard let httpResponse = response as? HTTPURLResponse,
              (200...299).contains(httpResponse.statusCode),
              let mimeType = httpResponse.mimeType, mimeType == "text/html",
              let data = data else {
            print("Server error")
            return
        }

        if let responseString = String(data: data, encoding: .utf8) {
            print("Received response: \(responseString)")
        } else {
            print("Failed to decode response")
        }
    }

    task.resume()
}

sendSecureHTTPRequestToApple()

결론 

  이 글을 통해 API 레이어를 정리하며, macOS와 iOS에서 네트워킹을 처리하는 데 사용되는 다양한 API 레이어에 대해 고찰해볼 수 있었습니다. libSystem부터 시작해 보다 추상화된 URLSession에 이르기까지, 각 레벨의 특성과 기능을 알게 되었고, 이러한 다층적 접근 방식을 통해 어떻게 적합한 도구를 선택할 수 있는지 깨달았습니다.

  네트워크 통신의 다양한 측면을 이해하고 최적의 솔루션을 구현하는 데 필요한 지식을 제공함으로써, 이 글을 보시는 분들이 보다 효율적이고 효과적인 네트워킹 전략을 사용하거나 레이어의 깊이에 대해서 알아가는 시간이었으면 좋겠습니다. 틀리거나 지적할 점이 있으시다면 피드백을 부탁드립니다.

감사합니다.