返回 导航

Swift

hangge.com

Swift - 使用socket进行通信(附聊天室样例)

作者:hangge | 2015-06-08 16:37
(本文代码已升级至Swift4)

在Swift开发中,如果我们需要保持客服端和服务器的长连接进行双向的数据通信,使用socket是一种很好的解决方案。
下面通过一个聊天室的样例来演示socket通信,这里我们使用了一个封装好的socket库(SwiftSocket)。

SwiftSocket配置: 
(1)将下载下来的源码包中 SwiftSocket.xcodeproj 拖拽至我们的工程中。

(2)工程 -> General -> Embedded Binaries 项,把iOS版的framework添加进来: SwiftSocket.framework

功能如下:
1,程序包含服务端和客服端,这里为便于调试把服务端和客服端都做到一个应用中
2,程序启动时,自动初始化启动服务端,并在后台开启一个线程等待客服端连接
3,同时,客户端初始化完毕后会与服务端进行连接,同时也在后台开启一个线程等待接收服务端发送的消息
4,连接成功后,自动生成一个随机的用户名(如“游客232”)并发送消息告诉服务器这个用户信息
5,点击界面的“发送消息”按钮,则会发送聊天消息到服务端,服务端收到后会把聊天消息发给所有的客服端。客服端收到后显示在对话列表中

注意1:消息传递过程使用的json格式字符串数据。
目前这个demo里消息种类有用户登录消息,聊天消息,后面还可以加上用户退出消息等。为了在接收到消息以后,能判断是要执行什么命令。我们创建消息体的时候使用字典NSDictionary(其中cmd表示命令类型,content表示内容体,nickname表示用户名,等等),发送消息时把字典转成json格式的字符串传递,当另一端接收的时候,又把json串还原成字典再执行响应的动作。

注意2:可变长度消息的发送
由于我们发送的消息长度是不固定的。所以看下面代码可以发现,为了让接受方知道我们这条消息的长度。每次我们要发送一条消息。其实是发两个消息包出去。
第1个包长度固定为4个字节,里边记录的是接下来的实际消息包的长度。
第2个包才是实际的消息包,接受方通过第一个包知道了数据长度,从而进行读取。

效果图如下:
   

代码如下:
--- ViewController.swift 主页面 ---
import UIKit
import SwiftSocket

class ViewController: UIViewController {
    
    //消息输入框
    @IBOutlet weak var textFiled: UITextField!
    //消息输出列表
    @IBOutlet weak var textView: UITextView!
    
    //socket服务端封装类对象
    var socketServer:MyTcpSocketServer?
    //socket客户端类对象
    var socketClient:TCPClient?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //启动服务器
        socketServer = MyTcpSocketServer()
        socketServer?.start()
        
        //初始化客户端,并连接服务器
        processClientSocket()
    }
    
    //初始化客户端,并连接服务器
    func processClientSocket(){
        socketClient=TCPClient(address: "localhost", port: 8080)
        
        DispatchQueue.global(qos: .background).async {
            //用于读取并解析服务端发来的消息
            func readmsg()->[String:Any]?{
                //read 4 byte int as type
                if let data=self.socketClient!.read(4){
                    if data.count==4{
                        let ndata=NSData(bytes: data, length: data.count)
                        var len:Int32=0
                        ndata.getBytes(&len, length: data.count)
                        if let buff=self.socketClient!.read(Int(len)){
                            let msgd = Data(bytes: buff, count: buff.count)
                            if let msgi = try? JSONSerialization.jsonObject(with: msgd,
                                                        options: .mutableContainers) {
                                return msgi as? [String:Any]
                            }
                        }
                    }
                }
                return nil
            }
            
            //连接服务器
            switch self.socketClient!.connect(timeout: 5) {
            case .success:
                DispatchQueue.main.async {
                    self.alert(msg: "connect success", after: {
                    })
                }
                
                //发送用户名给服务器(这里使用随机生成的)
                let msgtosend=["cmd":"nickname","nickname":"游客\(Int(arc4random()%1000))"]
                self.sendMessage(msgtosend: msgtosend)
                
                //不断接收服务器发来的消息
                while true{
                    if let msg=readmsg(){
                        DispatchQueue.main.async {
                            self.processMessage(msg: msg)
                        }
                    }else{
                        DispatchQueue.main.async {
                            //self.disconnect()
                        }
                        //break
                    }
                }
            case .failure(let error):
                DispatchQueue.main.async {
                    self.alert(msg: error.localizedDescription,after: {
                    })
                }
            }
        }
    }
    
    //“发送消息”按钮点击
    @IBAction func sendMsg(_ sender: AnyObject) {
        let content=textFiled.text!
        let message=["cmd":"msg","content":content]
        self.sendMessage(msgtosend: message)
        textFiled.text=nil
    }
    
    //发送消息
    func sendMessage(msgtosend:[String:String]){
        let msgdata=try? JSONSerialization.data(withJSONObject: msgtosend,
                                                options: .prettyPrinted)
        var len:Int32=Int32(msgdata!.count)
        let data = Data(bytes: &len, count: 4)
        _ = self.socketClient!.send(data: data)
        _ = self.socketClient!.send(data:msgdata!)
    }
    
    //处理服务器返回的消息
    func processMessage(msg:[String:Any]){
        let cmd:String=msg["cmd"] as! String
        switch(cmd){
        case "msg":
            self.textView.text = self.textView.text +
                (msg["from"] as! String) + ": " + (msg["content"] as! String) + "\n"
        default:
            print(msg)
        }
    }
    
    //弹出消息框
    func alert(msg:String,after:()->(Void)){
        let alertController = UIAlertController(title: "",
                                                message: msg,
                                                preferredStyle: .alert)
        self.present(alertController, animated: true, completion: nil)
        
        //1.5秒后自动消失
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.5) {
            alertController.dismiss(animated: false, completion: nil)
        }
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
--- MyTcpSocketServer.swift 服务端 ---
import UIKit
import SwiftSocket

//服务器端口
var serverport:Int32 = 8080

//客户端管理类(便于服务端管理所有连接的客户端)
class ChatUser:NSObject{
    var tcpClient:TCPClient?
    var username:String=""
    var socketServer:MyTcpSocketServer?
    
    //解析收到的消息
    func readMsg()->[String:Any]?{
        //read 4 byte int as type
        if let data=self.tcpClient!.read(4){
            if data.count==4{
                let ndata = NSData(bytes: data, length: data.count)
                var len:Int32=0
                ndata.getBytes(&len, length: data.count)
                if let buff=self.tcpClient!.read(Int(len)){
                    let msgd = Data(bytes: buff, count: buff.count)
                    let msgi = (try! JSONSerialization.jsonObject(with: msgd,
                                options: .mutableContainers)) as! [String:Any]
                    return msgi
                }
            }
        }
        return nil
    }
    
    //循环接收消息
    func messageloop(){
        while true{
            if let msg=self.readMsg(){
                self.processMsg(msg: msg)
            }else{
                self.removeme()
                break
            }
        }
    }
    
    //处理收到的消息
    func processMsg(msg:[String:Any]){
        if msg["cmd"] as! String=="nickname"{
            self.username=msg["nickname"] as! String
        }
        self.socketServer!.processUserMsg(user: self, msg: msg)
    }
    
    //发送消息
    func sendMsg(msg:[String:Any]){
        let jsondata=try? JSONSerialization.data(withJSONObject: msg,
                                                 options:.prettyPrinted)
        var len:Int32=Int32(jsondata!.count)
        
        let data = Data(bytes: &len, count: 4)
        _ = self.tcpClient!.send(data: data)
        _ = self.tcpClient!.send(data: jsondata!)
    }
    
    //移除该客户端
    func removeme(){
        self.socketServer!.removeUser(u: self)
    }
    
    //关闭连接
    func kill(){
        _ = self.tcpClient!.close()
    }
}

//服务端类
class MyTcpSocketServer: NSObject {
    var clients:[ChatUser]=[]
    var server:TCPServer=TCPServer(address: "127.0.0.1", port: serverport)
    var serverRuning:Bool=false
    
    //启动服务
    func start() {
        _ = server.listen()
        self.serverRuning=true
        
        DispatchQueue.global(qos: .background).async {
            while self.serverRuning{
                let client=self.server.accept()
                if let c=client{
                    DispatchQueue.global(qos: .background).async {
                        self.handleClient(c: c)
                    }
                }
            }
        }
        
        self.log(msg: "server started...")
    }
    
    //停止服务
    func stop() {
        self.serverRuning=false
        _ = self.server.close()
        //forth close all client socket
        for c:ChatUser in self.clients{
            c.kill()
        }
        self.log(msg: "server stoped...")
    }
    
    //处理连接的客户端
    func handleClient(c:TCPClient){
        self.log(msg: "new client from:"+c.address)
        let u=ChatUser()
        u.tcpClient=c
        clients.append(u)
        u.socketServer=self
        u.messageloop()
    }
    
    //处理各消息命令
    func processUserMsg(user:ChatUser, msg:[String:Any]){
        self.log(msg: "\(user.username)[\(user.tcpClient!.address)]cmd:"+(msg["cmd"] as! String))
        //boardcast message
        var msgtosend=[String:String]()
        let cmd = msg["cmd"] as! String
        if cmd=="nickname"{
            msgtosend["cmd"]="join"
            msgtosend["nickname"]=user.username
            msgtosend["addr"]=user.tcpClient!.address
        }else if(cmd=="msg"){
            msgtosend["cmd"]="msg"
            msgtosend["from"]=user.username
            msgtosend["content"]=(msg["content"] as! String)
        }else if(cmd=="leave"){
            msgtosend["cmd"]="leave"
            msgtosend["nickname"]=user.username
            msgtosend["addr"]=user.tcpClient!.address
        }
        for user:ChatUser in self.clients{
            //if u~=user{
            user.sendMsg(msg: msgtosend)
            //}
        }
    }
    
    //移除用户
    func removeUser(u:ChatUser){
        self.log(msg: "remove user\(u.tcpClient!.address)")
        if let possibleIndex=self.clients.index(of: u){
            self.clients.remove(at: possibleIndex)
            self.processUserMsg(user: u, msg: ["cmd":"leave"])
        }
    }
    
    //日志打印
    func log(msg:String){
        print(msg)
    }
}
源码下载:hangge_756.zip
评论

全部评论(31)

回到顶部