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编程资料
大神.我用这段代码,链接不上服务器