(本文代码已升级至Swift3)
在Swift开发中,如果我们需要保持客服端和服务器的长连接进行双向的数据通信,使用socket是一种很好的解决方案。
下面通过一个聊天室的样例来演示socket通信,这里我们使用了一个封装好的socket库(SwiftSocket)。
SwiftSocket配置:
将下载下来的ysocket文件夹拖进项目中即可。
功能如下:
1,程序包含服务端和客服端,这里为便于调试把服务端和客服端都做到一个应用中
2,程序启动时,自动初始化启动服务端,并在后台开启一个线程等待客服端连接
3,同时,客户端初始化完毕后会与服务端进行连接,同时也在后台开启一个线程等待接收服务端发送的消息
4,连接成功后,自动生成一个随机的用户名(如“游客232”)并发送消息告诉服务器这个用户信息
5,点击界面的“发送消息”按钮,则会发送聊天消息到服务端,服务端收到后会把聊天消息发给所有的客服端。客服端收到后显示在对话列表中
注意1:消息传递过程使用的json格式字符串数据。
目前这个demo里消息种类有用户登录消息,聊天消息,后面还可以加上用户退出消息等。为了在接收到消息以后,能判断是要执行什么命令。我们创建消息体的时候使用字典NSDictionary(其中cmd表示命令类型,content表示内容体,nickname表示用户名,等等),发送消息时把字典转成json格式的字符串传递,当另一端接收的时候,又把json串还原成字典再执行响应的动作。
注意2:可变长度消息的发送
由于我们发送的消息长度是不固定的。所以看下面代码可以发现,为了让接受方知道我们这条消息的长度。每次我们要发送一条消息。其实是发两个消息包出去。
第1个包长度固定为4个字节,里边记录的是接下来的实际消息包的长度。
第2个包才是实际的消息包,接受方通过第一个包知道了数据长度,从而进行读取。
效果图如下:
代码如下:
--- 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 {
//用于读取并解析服务端发来的消息
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: {
})
}
//发送用户名给服务器(这里使用随机生成的)
letmsgtosend=["cmd":"nickname","nickname":"游客\(Int(arc4random()%1000))"]
self.sendMessage(msgtosend: msgtosend)
//不断接收服务器发来的消息
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: {
})
}
}
}
}
//“发送消息”按钮点击
@IBActionfuncsendMsg(_ sender:AnyObject) {
letcontent=textFiled.text!
letmessage=["cmd":"msg","content":content]
self.sendMessage(msgtosend: message)
textFiled.text=nil
}
//发送消息
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
}
//循环接收消息
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)
}
//发送消息
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)
}
}
源码下载: