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