CocoaAsyncSocket (一): GCDAsyncUdpSocketDelegate

CocoaAsyncSocket 是 OC 下的 socket 框架, 引入 Swift 下有点麻烦. 之前本来想用一个 swift 的类似框架: SwiftSocket 但后来发现发不了 UDP 广播, google 一下还是选择了CocoaAsyncSocket.

CocoaAsyncSocket provides easy-to-use and powerful asynchronous socket libraries for Mac and iOS.

1. swift 下引入CocoaAsyncSocket

到 github 上下载 zip 包解压后, 将其中的以下四个文件拖入到你的 Swift 工程中

在header 文件(xxxx-Bridging-Header)中引入:

// import oc frames
#import "GCDAsyncSocket.h"
#import "GCDAsyncUdpSocket.h"

2. 创建一个udpSocket 的 delegate

import Foundation

// udp delegate
class GCDAsynUdpSocketDelegate : NSObject, GCDAsyncUdpSocketDelegate {
    var udpSocket: GCDAsyncUdpSocket?
    // 服务端 IP
    var serverHost: String!
    // udp 监听的本地端口号, 这里设置为和服务端一样
    var udpPort: UInt16 = 50000

    override init() {
        super.init()
        setupConnection()
    }

    func initialize(serverHost: String, tcpPort: UInt16, udpPort: UInt16) {
        self.serverHost = serverHost
        self.udpPort = udpPort
    }

    // 初始化 udp 对象
    func setupConnection() {
        var error: NSError?
        udpSocket = GCDAsyncUdpSocket(delegate: self, delegateQueue: dispatch_get_main_queue())
        udpSocket?.bindToPort(udpPort, error: &error)
        // 注释掉, 解决同一个 socket 对象不能同时接收和发送数据的问题, 改为 ip 和端口号在 sendData 中指定
        //udpSocket?.connectToHost("192.168.1.224", onPort: 40001, error: &error)
        udpSocket?.beginReceiving(&error)
        // 允许接收广播数据
        udpSocket?.enableBroadcast(true, error: &error)
        udpSocket?.joinMulticastGroup("192.168.1.255", error: &error)
    }

    // 发送数据
    func send(message: String) {
        let messageData = message.dataUsingEncoding(NSUTF8StringEncoding)
        //udpSocket?.sendData(data, withTimeout: -1, tag: 0)
        udpSocket?.sendData(messageData, toHost: serverHost, port: udpPort, withTimeout: -1, tag: 0)
    }

    ////////需要实现GCDAsyncUdpSocketDelegate的方法
    // 发送数据
    func udpSocket(sock: GCDAsyncUdpSocket!, didConnectToAddress address: NSData!) {
        println("didConnectToAddress");
    }

    func udpSocket(sock: GCDAsyncUdpSocket!, didNotConnect error: NSError!) {
        println("didNotConnect \(error)")
    }

    func udpSocket(sock: GCDAsyncUdpSocket!, didSendDataWithTag tag: Int) {
        println("didSendDataWithTag")
    }

    func udpSocket(sock: GCDAsyncUdpSocket!, didNotSendDataWithTag tag: Int, dueToError error: NSError!) {
        println("didNotSendDataWithTag")
    }

    // 接收数据
    func udpSocket(sock: GCDAsyncUdpSocket!, didReceiveData data: NSData!, fromAddress address: NSData!,      withFilterContext filterContext: AnyObject!) {
    // 在这里可以接收数据
        println("incoming message: \(data)");
    }


}

在 AppDelegate.swift 中使用这个 udpSocketDelegate, 部分代码如下:

var udpSocketDelegate = GCDAsynUdpSocketDelegate()
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GCDAsyncSocketDelegate {

    var window: UIWindow?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        // 初始化 udpSocketDelegate
        udpSocketDelegate.initialize(serverHost, tcpPort: tcpServerPort, udpPort: udpServerPort)

        return true
    }

然后在 viewController 中就可以使用它来发送数据了,

    @IBAction func udpClientBtnCliecked(sender: AnyObject) {
        udpSocketDelegate.send("udp test from client")
    }

而接收数据的功能在 delegate 中的 udpSocket(sock: GCDAsyncUdpSocket!, didReceiveData data: NSData!, fromAddress address: NSData!, withFilterContext filterContext: AnyObject!) 来实现


问题记录:

1. 为什么不能使用同一个 socket 对象同时接收和发送数据?
使用一个 socket 对象 接收一个socket 对象发送的例子: 参考[这里](http://stackoverflow.com/questions/26790129/swift-receive-udp-with-gcdasyncudpsocket)
import Foundation

//Receive
class InSocket: NSObject, GCDAsyncUdpSocketDelegate {

    let IP = "255.255.255.255"
    let PORT:UInt16 = 8081
    var socket:GCDAsyncUdpSocket!

    override init(){
        super.init()
        setupConnection()
    }

    func setupConnection(){
        var error : NSError?
        socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: dispatch_get_main_queue())
        socket.bindToPort(PORT, error: &error)
        socket.enableBroadcast(true, error: &error)
        socket.joinMulticastGroup(IP, error: &error)
        socket.beginReceiving(&error)
    }

    func udpSocket(sock: GCDAsyncUdpSocket!, didReceiveData data: NSData!, fromAddress address: NSData!,      withFilterContext filterContext: AnyObject!) {

        let dataString = NSString(data: data, encoding: NSUTF8StringEncoding)
        println("incoming message: \(data)");
    }
}

//Send
class OutSocket: NSObject, GCDAsyncUdpSocketDelegate {

    let IP = "192.168.1.224"
    let PORT:UInt16 = 40001
    var socket:GCDAsyncUdpSocket!

    override init(){
        super.init()
        setupConnection()
    }

    func send(message:String){
        let data = message.dataUsingEncoding(NSUTF8StringEncoding)
        socket.sendData(data, withTimeout: 2, tag: 0)
        // 接收回复的数据
        socket.receiveOnce(&error)
    }

    func setupConnection(){
        var error : NSError?
        socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: dispatch_get_main_queue())
        socket.bindToPort(PORT, error: &error)
        socket.connectToHost(IP, onPort: PORT, error: &error)
        socket.beginReceiving(&error)
        send("ping")
    }


    func udpSocket(sock: GCDAsyncUdpSocket!, didConnectToAddress address: NSData!) {
        println("didConnectToAddress");
    }

    func udpSocket(sock: GCDAsyncUdpSocket!, didNotConnect error: NSError!) {
        println("didNotConnect \(error)")
    }

    func udpSocket(sock: GCDAsyncUdpSocket!, didSendDataWithTag tag: Int) {
        println("didSendDataWithTag")
    }

    func udpSocket(sock: GCDAsyncUdpSocket!, didNotSendDataWithTag tag: Int, dueToError error: NSError!) {
        println("didNotSendDataWithTag")
    }
}

在 viewController 中调用

class SocketTestViewController: UIViewController {
    var inSocket : InSocket!
    var outSocket : OutSocket!

    override func viewDidLoad() {
        super.viewDidLoad()
        inSocket = InSocket()
        outSocket = OutSocket()
    }

    @IBAction func udpClientBtnCliecked(sender: AnyObject) {
        self.outSocket.send("udp test from 152")

        //udpSocketDelegate.send("udp test from 152")
    }

}

这个例子中的 inSocket 和 outSocket 分别执行接收和发送数据的功能. 总觉得应该是可以的, google, 在这里
找到了解决方法.

2. socket.receiveOnce(&error)socket.beginReceiving(&error) 的区别

上面的代码在接收报文的时候监听的是本地的40001端口, 客户端发报文的时候使用的是随机生成的一个端口号, 如果对方往这个端口号回复, 那就收不到了. 这种情况下就要使用前者.

func send(message:String){
    let data = message.dataUsingEncoding(NSUTF8StringEncoding)
    socket.sendData(data, withTimeout: 2, tag: 0)
    // 接收回复的数据
    socket.receiveOnce(&error)
}

附上一个 socket开发的测试工具, 下载地址

2015-06-28 15:51676