Swift는 C구조체조차 Extension에서 확장할 수 있어 C언어기반으로 배웠던 소켓처리를 Swift로 확장해보려고 합니다.

C언어 기반의 소켓 API

[c]

struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_len = sizeof(sin);
sin.sin_family = AF_INET;
sin.sin_port = htons(0);
sin.sin_addr.s_addr= INADDR_ANY;

bind(sock, (struct sockaddr *)&sin, sizeof(sin))

[/c]

이런 형태에서 sockaddr_in에서 sockaddr에 캐스팅하는 것입니다. swift는 이를 직관적으로 처리할 수 없기 때문에 Extension을 사용하여 직관적으로 복사할 수 있게 구현해보았습니다. Swift는 C언어의 sockaddr_in, sockaddr 구조는 아래와같이 변환해보았습니다.

[swift]

struct sockaddr_in {
var sin_len: __uint8_t
var sin_family: sa_family_t
var sin_port: in_port_t
var sin_addr: in_addr
var sin_zero: (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)
}

struct sockaddr {
var sa_len: __uint8_t
var sa_family: sa_family_t
var sa_data: (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)
}

[/swift]

Extension을 사용하여 sockaddr_in, sockaddr의 초기화작업을 추가합니다. 인수 sockaddr_in, sockaddr타입 변수를 주고 초기화하는데 이를 상호변환할 수 있도록 합니다.

[swift]

extension sockaddr {
init(_ saddr_in: sockaddr_in) { // cast(copy) from sockaddr_in
self = saddr_in.copyAsSockAddr()
}

func copyAsSockAddrIn() -> sockaddr_in {
let len = UInt8(sizeof(sockaddr_in))
let family = self.sa_family
let (hi, lo) = (self.sa_data.0, self.sa_data.1)
let port = (hi.asUInt16 << 8) | lo.asUInt16       // what’s asUInt16?
let b = (UInt32(self.sa_data.2), UInt32(self.sa_data.3),
UInt32(self.sa_data.4), UInt32(self.sa_data.5))
let sadr =  b.0 << 24 | b.1 << 16 | b.2 << 8 | b.3
let addr = in_addr(s_addr: sadr)

return sockaddr_in(sin_len: len, sin_family: family, sin_port: port,
sin_addr: addr, sin_zero: (0,0,0,0,0,0,0,0))
}

func unsafeCopyAsSockAddrIn() -> sockaddr_in {
return unsafeBitCast(self, sockaddr_in.self)
}
}

[/swift]

unsafeBitCast를 사용하면 바로 될 것 같은데, 일단 그대로 구현해 보면, struct sockaddr_in도 같은 형태의 Extension으로 처리하여 아래와 같이 처리합니다.

[swift]

extension sockaddr_in {
init(_ saddr: sockaddr) {
self = saddr.copyAsSockAddrIn()
}

func copyAsSockAddr() -> sockaddr {
let len = self.sin_len
let family = self.sin_family
let port = self.sin_port.bigEndian
let (hi, lo) = ((port >> 8).asInt8, (port & 0x00ff).asInt8)
let sadr: UInt32 = self.sin_addr.s_addr.bigEndian
let b = (Int8( sadr >> 24        ),
Int8((sadr >> 16) & 0xff),
Int8((sadr >>  8) & 0xff),
Int8( sadr        & 0xff))
let z: Int8 = 0
let data = (hi, lo, b.0, b.1, b.2, b.3, z,z,z,z,z,z,z,z)
return sockaddr(sa_len: len, sa_family: family, sa_data: data)
}
}

[/swift]

그런데, copyAsSockAddrin메소드의 hi,lo(Int8타입) 변수에  asInt8이라든지, asUInt16이라는 속성은 정수형에 있었던 것입니다. 이것도 다음과 같이 Extension해 버립니다. 부호없는 16비트 정수를 부호가 있는 8비트정수로 강제 변환하는데 필요합니다.

[swift]

extension Int8 {
var asUInt16: UInt16 {
return UInt16(Int16(self) & 0xff)
}
}

extension UInt16 {
var asInt8: Int8 {
let v = self & 0xff
return Int8(v >> 4) << 4 | Int8(v & 0xf)
}
}

[/swift]

이렇게 준비가 끝나면 소켓API를 호출해 봅시다.

[swift]

class EchoServer {

func start(port: UInt16) {
serverSocket = socket(AF_INET, SOCK_STREAM, 0)

var addrIn = sockaddr_in(port: port)
var len = socklen_t(addrIn.sin_len)
var addr = sockaddr(addrIn)

bind(serverSocket, &addr, len)
listen(serverSocket, 4)

let src = my_create_disp_src(serverSocket)
dispatch_source_set_event_handler(src, acceptClient)
dispatch_resume(src)
}

[/swift]

C언어처럼 하게 되는 listen을 한 후 GCD에서 처리하도록 합니다. 한가지 아쉬운점은 GCD의 dispatch_source_create()를 직접 호출이 잘 실행하지 않고 제대로 실행시키기 위해서는 C언어 브릿지를 사용하지 않을 수 없을 것 같습니다. 함수가 my_create_disp_src()에서 이런 형태로 정의합니다.

[swift]

class EchoServer {

func start(port: UInt16) {
serverSocket = socket(AF_INET, SOCK_STREAM, 0)

var addrIn = sockaddr_in(port: port)
var len = socklen_t(addrIn.sin_len)
var addr = sockaddr(addrIn)

bind(serverSocket, &addr, len)
listen(serverSocket, 4)

let src = my_create_disp_src(serverSocket)
dispatch_source_set_event_handler(src, acceptClient)
dispatch_resume(src)
}

[/swift]

다음은 dispatch_source_set_event_handler에 전달핸들러(acceptClient)의 정의입니다.

[swift]

func acceptClient() {
var addrIn = sockaddr_in()
var addr = sockaddr(addrIn)
var len = socklen_t(addrIn.sin_len)

let sock = accept(serverSocket, &addr, &len)

addrIn = sockaddr_in(addr) // copy back
let ipStr = addrIn.addressString
let port = addrIn.sin_port
println(“Accepted client(=(ipStr):(port))”)

var csrc = my_create_disp_src(sock)
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
clientsAccepted[sock] = csrc
dispatch_semaphore_signal(semaphore)

dispatch_source_set_event_handler(csrc, readAndEchoBack(sock))
dispatch_resume(csrc)
}

[/swift]

accept도 약속대로 잘 안됩니다. 그리고 inet_ntoa()도 사용하지 않고, sockaddr_in에 Extension으로 하여 addressString속성을 추가했습니다. addressString에 IP주소 문자열을 표현을 얻을 수 있습니다. 가급적 순수한 Swift로 가려고 합니다.

다음은 연결하려는 클라이언트에 대한 이벤트처리입니다. 이벤트핸들러 등록함수(dispatch_source_set_event_handler)에 전달핸들러함수(클로저)는 유형이 인수없는 것을 리턴한값인 ()->()이 아니면 안됩니다. 하지만 클라이언트에서 read/write 및 자르면 뒷처리를 위해 클라이언트 소켓이 필요하고 아무래도 정수인수를 전달하지 않으면 안됩니다.

readAndEchoBack(sock)에 주목해서 이를 함수호출하고 있으면 리턴은 ()->()입니다.

[swift]

func readAndEchoBack(sock: Int32) -> () -> () {
let client = sock
var cDict = self.clientsAccepted
var sem = self.semaphore
return {
() -> () in dispatch_async(GlobalQueue) {
//                [unowned self] in    // <=== Linkage error in Xcode6 Beta5
let bufSize = 64
var buf = [UInt8](count: bufSize, repeatedValue: 0)
let size = read(client, &buf, UInt(bufSize))

if size > 0 {
write(client, &buf, UInt(size))
} else if size == 0 {
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER)

var csrc = cDict.removeValueForKey(client)
if let src = csrc {
dispatch_source_cancel(src)
dispatch_release(src)
close(client)
}

dispatch_semaphore_signal(sem)
} else {/* size == -1 somethig wrong happned…*/}
}
}
}

[/swift]

이렇게 해서 Swift언어도 쉽개 소켓API를 사용하여 TCP서버를 구현할 수 있다는 것을 확인했습니다. C언어에 비해 API를 깔끔하게 사용할 수 있어 편리합니다.

전체소스코드 – EchoServer.swift

Extensions.swift

Facebook Comments

You may be interested

Xcode 기능 확장(Extension) 제거하기
Xcode
shares3 views
Xcode
shares3 views

Xcode 기능 확장(Extension) 제거하기

MJ Kim - 3월 18, 2017

Mac에서 Xcode Source Editor Extension등의 기능확장을 사용하다보면 디버깅시 시스템 환경 설정의 확장이 앱에 등록되는 경우가 있다. 계속해서 목록이 남아 있기…

iOS App Store Review(앱 심사약관) 번역
Swift 3.0
shares112 views
Swift 3.0
shares112 views

iOS App Store Review(앱 심사약관) 번역

MJ Kim - 3월 15, 2017

App Store Review를 번역했다. 사실 이번에 좀 애매한 리젝을 당해서 그걸 이해하고자 정리해본다. 원문링크: https://developer.apple.com/app-store/review/guidelines/ 1. 이약관은? 1.1 앱 개발자로서 프로그램의…

Raspberry Pi 타이머 On/Off 전원제어모듈 RPi1114-Raspberry Pi
IoT by Raspberry Pi
shares7 views
IoT by Raspberry Pi
shares7 views

Raspberry Pi 타이머 On/Off 전원제어모듈 RPi1114-Raspberry Pi

MJ Kim - 3월 04, 2017

RPi1114-Raspberry Pi전원제어 모듈이 있다. 이 제품은 40Pin GPIO핀헤더에 연결하여 사용하는 모듈로 Cortax-M0마이크로컨트롤러 LPC1114를 내장하고 Raspberry Pi의 시작과 정지 순서등을 프로그래밍할…