Edited at

SwiftでTCPサーバーを作ってみる

More than 3 years have passed since last update.

Swiftでは,Cの構造体でさえもExtensionでどんどん拡張できてしまうのは愉快痛快ですね.

C言語でのソケット関連のお約束をSwiftで拡張して使いやすくしてみようという試みです.

Cの,というかソケットAPIでは,

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))
...

という感じでsockaddr_inからsockaddrへキャストするのがお約束.swiftではこれをストレートに表現できないので,Extensionを使って直感的キャスト(コピー)できるようにしてみる.

swiftでは,Cのsockaddr_in, sockaddr構造体はそれぞれ次のようにコンバートされて定義されている.

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)
}

これらを眺めつつ,Extension使ってsockaddr_in/sockaddrの初期化処理を追加.引数にsockaddr_in/sockaddrタイプ変数を与えて初期化,つまり相互変換できるようにする.

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)
}
}

   (unsafeBitCastを使ってしまえば一発で可能みたいだけど,ここでは生々しくやっていきたい.)

struct sockaddr_inも同様な感じにExtensionして,

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)
}
}

ところで,copyAsSockAddrInメソッド内のhi, lo(Int8タイプ)変数のところのasInt8とかasUInt16なんていうプロパティは整数タイプにありましたっけ?

これも次のようにExtensionしちゃってます.これ,符号なし16ビット整数を符号付8ビット整数に強引に変換するのに必要なんですね.UInt16(Int8(-128))なんてことをやるとコンパイラに怒られてしまうので.

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を呼んでみましょう.(長ったらしいのでエラー処理は省略.)

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)
}

 

ほとんどC言語のお約束と同様にいけますね.listenした後は,仕事をGCDに丸投げします.

ひとつ残念なのが,現状(XCode6 Beta5)では,swiftからGCDのdispatch_source_create()の直接呼び出しがうまく機能せず,正しく機能させるためにC言語のブリッジを使わざるを得ないことです.その関数がmy_create_disp_src()でこんなふうに定義しています.

dispatch_source_t my_create_disp_src(int sock) {

return dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
sock, 0, dispatch_get_global_queue(0, 0));
}

 

次は,dispatch_source_set_event_handlerに渡すハンドラ(acceptClient)の定義です.

    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)
}

acceptもほとんどお約束通りいけますね.

あと,inet_ntoa()なんか使わず,sockaddr_inにExtensionしてaddressStringプロパティを追加しました.addressStringでIPアドレスの文字列表現を取れるようにしてます.なるべくピュアswiftで行きたいものでして.

お次は,接続してきたクライアントに対するイベント処理です.イベントハンドラ登録関数(dispatch_source_set_event_handler)に渡すハンドラ関数(クロージャ)はタイプが無引数無戻値な'() -> ()'でないとダメです.ですが,クライアントからのread/へのwriteや,切断時の後始末のためにクライアントのソケットが必要で,どうしても整数引数を渡さないといけません.

そこで関数の部分適用の出番です.

これがサラッと書ける言語はほんと楽でいいですねー.

つまり,"(Int) -> () -> ()"な関数を作ってしまえばいいのでした.

上記コードのreadAndEchoBack(sock)に注目.これ関数コールしてますが戻り値は"() -> ()"です.

その実装は,

    func readAndEchoBack(sock: Int32) -> () -> () {

let client = sock
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(self.semaphore, DISPATCH_TIME_FOREVER)

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

dispatch_semaphore_signal(self.semaphore)
} else {/* size == -1 somethig wrong happned...*/}
}
}
}

現状(XCode6 Beta5),"[unowned self]"を付けると何故かリンク時にエラーになってしまいます. (循環参照していないのでキャプチャリストは必要ありません.)

  

以上で,swiftでもそこそこ簡単に生ソケットAPI使ってTCPサーバーを実装できることが分かりました.SwiftでC言語用APIをサラッと使えるのはとても便利ですね.

(一部残念ですが,swift環境はまだベータ版,というかむしろアルファ版ですからね.我慢しましょう.)

Gistにソースを置きました.

Extensions.swift

EchoServer.swift