(本文代碼已升級至Swift3)
在Swift開發(fā)中,如果我們需要保持客服端和服務器的長連接進行雙向的數(shù)據(jù)通信寄锐,使用socket是一種很好的解決方案。
下面通過一個聊天室的樣例來演示socket通信尖啡,這里我們使用了一個封裝好的socket庫(SwiftSocket)。
SwiftSocket配置:
將下載下來的ysocket文件夾拖進項目中即可剩膘。
功能如下:
1衅斩,程序包含服務端和客服端,這里為便于調(diào)試把服務端和客服端都做到一個應用中
2怠褐,程序啟動時畏梆,自動初始化啟動服務端,并在后臺開啟一個線程等待客服端連接
3奈懒,同時奠涌,客戶端初始化完畢后會與服務端進行連接,同時也在后臺開啟一個線程等待接收服務端發(fā)送的消息
4磷杏,連接成功后溜畅,自動生成一個隨機的用戶名(如“游客232”)并發(fā)送消息告訴服務器這個用戶信息
5,點擊界面的“發(fā)送消息”按鈕极祸,則會發(fā)送聊天消息到服務端慈格,服務端收到后會把聊天消息發(fā)給所有的客服端怠晴。客服端收到后顯示在對話列表中
注意1:消息傳遞過程使用的json格式字符串數(shù)據(jù)浴捆。
目前這個demo里消息種類有用戶登錄消息蒜田,聊天消息,后面還可以加上用戶退出消息等选泻。為了在接收到消息以后冲粤,能判斷是要執(zhí)行什么命令。我們創(chuàng)建消息體的時候使用字典NSDictionary(其中cmd表示命令類型页眯,content表示內(nèi)容體色解,nickname表示用戶名,等等)餐茵,發(fā)送消息時把字典轉(zhuǎn)成json格式的字符串傳遞科阎,當另一端接收的時候,又把json串還原成字典再執(zhí)行響應的動作忿族。
注意2:可變長度消息的發(fā)送
由于我們發(fā)送的消息長度是不固定的锣笨。所以看下面代碼可以發(fā)現(xiàn),為了讓接受方知道我們這條消息的長度道批。每次我們要發(fā)送一條消息错英。其實是發(fā)兩個消息包出去。
第1個包長度固定為4個字節(jié)隆豹,里邊記錄的是接下來的實際消息包的長度椭岩。
第2個包才是實際的消息包,接受方通過第一個包知道了數(shù)據(jù)長度璃赡,從而進行讀取判哥。
效果圖如下:
代碼如下:
--- ViewController.swift 主頁面 ---
importUIKit
classViewController:UIViewController{
//消息輸入框
@IBOutletweakvartextFiled:UITextField!
//消息輸出列表
@IBOutletweakvartextView:UITextView!
//socket服務端封裝類對象
varsocketServer:MyTcpSocketServer?
//socket客戶端類對象
varsocketClient:TCPClient?
overridefuncviewDidLoad() {
super.viewDidLoad()
//啟動服務器
socketServer =MyTcpSocketServer()
socketServer?.start()
//初始化客戶端,并連接服務器
processClientSocket()
}
//初始化客戶端碉考,并連接服務器
funcprocessClientSocket(){
socketClient=TCPClient(addr:"localhost", port: 8080)
DispatchQueue.global(qos: .background).async {
//用于讀取并解析服務端發(fā)來的消息
funcreadmsg()->[String:Any]?{
//read 4 byte int as type
ifletdata=self.socketClient!.read(4){
ifdata.count==4{
letndata=NSData(bytes: data, length: data.count)
varlen:Int32=0
ndata.getBytes(&len, length: data.count)
ifletbuff=self.socketClient!.read(Int(len)){
letmsgd =Data(bytes: buff, count: buff.count)
letmsgi = (try!JSONSerialization.jsonObject(with: msgd,
options: .mutableContainers))as! [String:Any]
returnmsgi
}
}
}
returnnil
}
//連接服務器
let(success,msg)=self.socketClient!.connect(timeout: 5)
ifsuccess{
DispatchQueue.main.async {
self.alert(msg:"connect success", after: {
})
}
//發(fā)送用戶名給服務器(這里使用隨機生成的)
letmsgtosend=["cmd":"nickname","nickname":"游客\(Int(arc4random()%1000))"]
self.sendMessage(msgtosend: msgtosend)
//不斷接收服務器發(fā)來的消息
whiletrue{
ifletmsg=readmsg(){
DispatchQueue.main.async {
self.processMessage(msg: msg)
}
}else{
DispatchQueue.main.async {
//self.disconnect()
}
break
}
}
}else{
DispatchQueue.main.async {
self.alert(msg: msg,after: {
})
}
}
}
}
//“發(fā)送消息”按鈕點擊
@IBActionfuncsendMsg(_ sender:AnyObject) {
letcontent=textFiled.text!
letmessage=["cmd":"msg","content":content]
self.sendMessage(msgtosend: message)
textFiled.text=nil
}
//發(fā)送消息
funcsendMessage(msgtosend:[String:String]){
letmsgdata=try?JSONSerialization.data(withJSONObject: msgtosend,
options: .prettyPrinted)
varlen:Int32=Int32(msgdata!.count)
letdata =Data(bytes: &len, count: 4)
_ =self.socketClient!.send(data: data)
_ =self.socketClient!.send(data:msgdata!)
}
//處理服務器返回的消息
funcprocessMessage(msg:[String:Any]){
letcmd: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)
}
}
//彈出消息框
funcalert(msg:String,after:()->(Void)){
letalertController =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)
}
}
overridefuncdidReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
--- MyTcpSocketServer.swift 服務端 ---
importUIKit
//服務器端口
varserverport = 8080
//客戶端管理類(便于服務端管理所有連接的客戶端)
classChatUser:NSObject{
vartcpClient:TCPClient?
varusername:String=""
varsocketServer:MyTcpSocketServer?
//解析收到的消息
funcreadMsg()->[String:Any]?{
//read 4 byte int as type
ifletdata=self.tcpClient!.read(4){
ifdata.count==4{
letndata=NSData(bytes: data, length: data.count)
varlen:Int32=0
ndata.getBytes(&len, length: data.count)
ifletbuff=self.tcpClient!.read(Int(len)){
letmsgd =Data(bytes: buff, count: buff.count)
letmsgi = (try!JSONSerialization.jsonObject(with: msgd,
options: .mutableContainers))as! [String:Any]
returnmsgi
}
}
}
returnnil
}
//循環(huán)接收消息
funcmessageloop(){
whiletrue{
ifletmsg=self.readMsg(){
self.processMsg(msg: msg)
}else{
self.removeme()
break
}
}
}
//處理收到的消息
funcprocessMsg(msg:[String:Any]){
ifmsg["cmd"]as!String=="nickname"{
self.username=msg["nickname"]as!String
}
self.socketServer!.processUserMsg(user:self, msg: msg)
}
//發(fā)送消息
funcsendMsg(msg:[String:Any]){
letjsondata=try?JSONSerialization.data(withJSONObject: msg, options:
JSONSerialization.WritingOptions.prettyPrinted)
varlen:Int32=Int32(jsondata!.count)
letdata =Data(bytes: &len, count: 4)
_ =self.tcpClient!.send(data: data)
_ =self.tcpClient!.send(data: jsondata!)
}
//移除該客戶端
funcremoveme(){
self.socketServer!.removeUser(u:self)
}
//關閉連接
funckill(){
_ =self.tcpClient!.close()
}
}
//服務端類
classMyTcpSocketServer:NSObject{
varclients:[ChatUser]=[]
varserver:TCPServer=TCPServer(addr:"127.0.0.1", port: serverport)
varserverRuning:Bool=false
//啟動服務
funcstart() {
_ = server.listen()
self.serverRuning=true
DispatchQueue.global(qos: .background).async {
whileself.serverRuning{
letclient=self.server.accept()
ifletc=client{
DispatchQueue.global(qos: .background).async {
self.handleClient(c: c)
}
}
}
}
self.log(msg:"server started...")
}
//停止服務
funcstop() {
self.serverRuning=false
_ =self.server.close()
//forth close all client socket
forc:ChatUserinself.clients{
c.kill()
}
self.log(msg:"server stoped...")
}
//處理連接的客戶端
funchandleClient(c:TCPClient){
self.log(msg:"new client from:"+c.addr)
letu=ChatUser()
u.tcpClient=c
clients.append(u)
u.socketServer=self
u.messageloop()
}
//處理各消息命令
funcprocessUserMsg(user:ChatUser, msg:[String:Any]){
self.log(msg:"\(user.username)[\(user.tcpClient!.addr)]cmd:"+(msg["cmd"]as!String))
//boardcast message
varmsgtosend=[String:String]()
letcmd = msg["cmd"]as!String
ifcmd=="nickname"{
msgtosend["cmd"]="join"
msgtosend["nickname"]=user.username
msgtosend["addr"]=user.tcpClient!.addr
}elseif(cmd=="msg"){
msgtosend["cmd"]="msg"
msgtosend["from"]=user.username
msgtosend["content"]=(msg["content"]as!String)
}elseif(cmd=="leave"){
msgtosend["cmd"]="leave"
msgtosend["nickname"]=user.username
msgtosend["addr"]=user.tcpClient!.addr
}
foruser:ChatUserinself.clients{
//if u~=user{
user.sendMsg(msg: msgtosend)
//}
}
}
//移除用戶
funcremoveUser(u:ChatUser){
self.log(msg:"remove user\(u.tcpClient!.addr)")
ifletpossibleIndex=self.clients.index(of: u){
self.clients.remove(at: possibleIndex)
self.processUserMsg(user: u, msg: ["cmd":"leave"])
}
}
//日志打印
funclog(msg:String){
print(msg)
}
}
源碼下載: