Swift - 使用socket进行通信(附聊天室样例)
(本文代码已升级至Swift4)
下面通过一个聊天室的样例来演示socket通信,这里我们使用了一个封装好的socket库(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) } }源码下载:

为什么我在模拟器上可以连接上服务器,而在真机测试始终提示socketerror:connectionTimeout 超时
本来用楼主的demo已经做出来了,但是公司有要求SSL加密,无奈换成CocoaAsyncSocket。关于28楼的问题,以CocoaAsyncSocket的使用经验,只要不彻底中断app,socket就会一直保持连接,但是从待机状态变成活跃状态之后,登录状态没了,所以每次app重新变成活跃状态时,我都是重新调一次登录接口(首次登录,服务器返回一个token参数,之后用token重新登录),登录成功就说明还连着。
大神好 我只找着 tvOS 和 mac 版本的 SwiftSocket.framework
没看见 iphone 版本的 不知道是否有哪里出错了
您好。想请问您是否有做过判断swiftsocket中判断套接字是否还连接的功能? 最近在搞这个,需要检测重连,但是找了很多资料也不知道如何判断socket是否还与服务端连接着 哭哭
大神能加个qq不
大神,看了您的DEMO,socket已经连接成功了,但是项目又做了SSL加密,研究一天好无头绪,请问有没有什么思路能指点一下,谢谢。
楼主您好,请问这个socket编程收到服务端新消息有回调方法吗
这个好像不能发送大文件
while (len - byteswrite > 0) {
int writelen = (int)write(socketfd, data + byteswrite, len - byteswrite);
if (writelen < 0) {
return -1;
}
sendsize == Int32(data.count)
sendSize = -1, 所以总是发送失败
刚下载运行了一下,这个代码报错,运行不了。
int retval = select(sockfd + 1,NULL, &fdwrite, NULL, &tvSelect);
怎么能实现局域网的聊天呢,这个好像只能实现一台电脑开多个模拟器然后相互聊天
请问 ?ysocket.c 中的fd 是代表什么来着
航哥你好! 我服务器端是一次性发过来的,客户端收到以0309开头\n结尾的数据之后就跳出循环,不在接收服务器的数据了,直到再次请求服务器在接收。我发现服务器发过来的速度比客户端接收的要快,我在接收的时候sleep了一下,是不是因为客户端处理的太慢数据丢了,就死在循环里了呢? 就算是数据丢了,能不能有什么办法跳出循环在从新请求服务器呢?谢谢!!
//初始化客户端,并连接服务器
func processClientSocket(){
//用于读取并解析服务端发来的消息-----字符串
func readmsg()->String?{
sleep(sleepTime)
if let buff=socketClient!.readAll(){
let msgi:String = String(bytes: buff, encoding: String.Encoding.utf8)!
//print(msgi)
return msgi
}
return nil
}
if isConnect
{
//发送数据
let msgtosend = "newshouji££|03\(sjkdm)|\(shidm)\(qudm)\(dwdm1)\(dwdm2)|0309|\(count)"
let (success, errmsg) = socketClient!.send(str: msgtosend)
if success
{
//读取数据
//不断接收服务器发来的消息
while true{
if let msg=readmsg(){
//是否有后缀
if msg.hasPrefix("0309") && msg.hasSuffix("\n") {
//print(msg)
self.processMessage(msg)
break
}
}else{
print("接收失败")
break
}
}
}else {
let alert = UIAlertController(title: "提示", message: errmsg, preferredStyle: UIAlertControllerStyle.alert)
let yes = UIAlertAction(title: "确定", style: UIAlertActionStyle.default, handler: nil)
alert.addAction(yes)
self.present(alert, animated: true, completion: nil)
print(errmsg)
}
}
else {
let alert = UIAlertController(title: "提示", message: "网络连接异常!", preferredStyle: UIAlertControllerStyle.alert)
let yes = UIAlertAction(title: "确定", style: UIAlertActionStyle.default, handler: nil)
alert.addAction(yes)
self.present(alert, animated: true, completion: nil)
}
}
航哥,这个是我在你的基础上改,没用线程,我就是在登录页连的服务器, 程序要有好多个页面,我是用个while true不断接收服务器的消息,直到头尾都对,退出循环,现在就有个问题,有时候收的不对,就死在循环里面了,就是收的不对要怎么跳出循环呢,我不知道了,求教!谢谢!!
航哥,你这个不支持ipv6,我能直接在上面修改吗,然后支持ipv6。
是否支持ipv6吗?
进入你的链接里 咋没找到那个封装库
我这个是和仪器的WIFI模块通讯,我想改成直接send(0xAA,0xAA,0x00,0x12)给仪器wifi模块的c服务端发送这样的指令,并收到回复。这个该怎么改啊?
站长,想咨询一个问题,我用GCDAsyncSocket写的tcp,但是发送数据怎么也看不明白,贴一下代码您看看怎么回事
func Cilent(host: String) {
do {
var test = [ "title": "data" ]
clientSocket = GCDAsyncSocket()
clientSocket.delegate = self
clientSocket.delegateQueue = dispatch_get_global_queue(0,0)
try clientSocket.connectToHost (host, onPort: 50000, withTimeout: 5000)
clientSocket.writeData(test["title"], withTimeout: 20, tag: Int)
clientSocket.readDataToData(data, withTimeout: 20, tag: Int)
}
catch {
print("error")
}
}
站长,后面服务端的代码能直接放在服务器上么,不是应该用PHP、Node.js之类的么?还有我要做一个验证用户账号登录注册的服务器该怎么写?谢谢~
改了改还是不行,代码一开始是少4个字节,我改了改还是不行,少开头一个字节
链接服务器成功-->ELCOME 192.168.0.131:627
链接服务器成功-->K
链接服务器成功-->SG:1470136693458
链接服务器成功-->SG:1470136694459
链接服务器成功-->SG:1470136695459
链接服务器成功-->SG:1470136696459
应该是::WELCOME
MSG
OK
航哥,请教一下,我现在要用Cordova开发一个聊天功能的插件,但是这个聊天功能我可以使用Socket实现吗?谢谢!
站长,如果用os x做的话,会不会有些不同?
请问要让两个使用者连结该从哪修改呢?
航哥,能不能稍微改改这个代码,直接收发String类型。多谢航哥 :D
您好 幫了很大的忙
想請問要連socket server是其他語言的 ip 跟port要改哪裡呢謝謝
我的服务端socket是其它语言的,按照传输格式,先发需要读区的长度,然后按长度读取,可是swift self.tcpClient!.read(Int(len)) (稍微多一点数据的时候)读取出来并不是我发送的长度,有时成功,有时失败。请问是为什么?
我用了,如果是本地的服务器 addr: "127.0.0.1", port: 5222,//服务器端口var serverport = 5222 这三个地方改一下就能成功!!谢谢楼主!
你好,我用的是xcode 7.2.1 我编译之后发现错误:
Unknown attribute '_silgen_name' Expected declaration;错误,请问如何解决?
为什么swiftsocket 报错
请问这个用到了滑动窗口协议吗? 能不能提供一些关于滑动窗口协议的swift编程资料
大神.我用这段代码,链接不上服务器