Swifter {Swift Developer}

메뉴

Swift로 TCP서버 만들기

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

카테고리:   Swift

댓글

죄송하지만 댓글은 닫혀 있습니다.