Swift - 使用vapor socks库进行socket通信(基于TCP、UDP协议)
作者:hangge | 2017-04-26 08:10
本文介绍另一个 socket 库:vapor 的 socks 库。个人感觉这个用起来更加简单方便,而且原生就有 receiveAll 方法,方便我们发送和接收不定长消息。
一、安装配置
(1)从 Github 主页下载代码:https://github.com/vapor/socks
(2)将下载下来的 Socks 文件夹添加到项目中。

二、基于TCP协议的socket通信
1,效果图
(1)程序运行后会启动一个 tcp 服务器,监听 8080 端口。
(2)同时还会初始化一个 tcp 客户端。在输入框中填写内容后,点击“发送”按钮即可将消息发送到服务端(本文样例就是自己发给自己)。
(3)服务端接收到消息后会显示在界面上,同时接收到的消息又返回客户端。
(4)客户端收到反馈消息后同样会将其显示在界面上。

2,样例代码
import UIKit
class ViewController: UIViewController {
//发送消息输入框
@IBOutlet weak var textField: UITextField!
//用于显示接收到的消息
@IBOutlet weak var textView: UITextView!
//TCP服务端
var server:SynchronousTCPServer!
//TCP客户端
lazy var client:TCPClient? = {
//初始化客户端
let address = InternetAddress(hostname: "127.0.0.1", port: 8080)
do {
return try TCPClient(address: address)
} catch {
print("Error \(error)")
return nil
}
}()
override func viewDidLoad() {
super.viewDidLoad()
//启动服务器
startServer()
}
//启动服务器
func startServer() {
//在后台线程中启动服务器
DispatchQueue.global(qos: .background).async {
do {
//初始化服务器
self.server = try SynchronousTCPServer(port: 8080)
//在界面上显示启动信息
DispatchQueue.main.async {
let hostname = self.server.address.hostname
let address = self.server.address.addressFamily
let port = self.server.address.port
self.textView.text = "服务器启动,监听:"
+ "\"\(hostname)\" (\(address)) \(port)\n"
}
//接收并处理客户端连接
try self.server.startWithHandler { (client) in
self.handleClient(client: client)
}
} catch {
print("Error \(error)")
}
}
}
//处理连接的客户端
func handleClient(client:TCPClient){
do {
while true{
//获取客户端发送过来的消息:[UInt8]类型
let data = try client.receiveAll()
//将接收到的消息转成String类型
let str = try data.toString()
//将这个String消息显示到界面上
DispatchQueue.main.async {
self.textView.text = self.textView.text + "服务端接收到消息: \(str)\n"
}
//将接收到的消息又发回客户端
try client.send(bytes: data)
//try client.close() //关闭与客户端链接
}
} catch {
print("Error \(error)")
}
}
//发送消息
@IBAction func sendMessage(_ sender: Any) {
do {
let message = self.textField.text
if message != nil && message != "" {
try client?.send(bytes: message!.toBytes())
let str = try client!.receiveAll().toString()
//将服务端返回的消息显示在界面上
self.textView.text = self.textView.text + "客户端接收到反馈: \(str)\n"
//try client.close() //关闭客户端与服务端链接
//清空输入框
self.textField.text = ""
}
} catch {
print("Error \(error)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
源码下载:三、基于UDP协议的socket通信
由于 UDP 不像 TCP 那样需要三次握手,所以不需要建立连接通道,也没有断开连接之说,发送完了也就完了。因此代码简单许多。1,效果图
(1)功能和上面的大体一样。程序运行后会启动一个 udp 服务器,监听 8080 端口。(2)同时还会初始化一个 udp 客户端。在输入框中填写内容后,点击“发送”按钮即可将消息发送到服务端(本文样例就是自己发给自己)。
(3)服务端接收到消息后会显示在界面上,同时接收到的消息又返回客户端。
(4)客户端收到反馈消息后同样会将其显示在界面上。

2,样例代码
import UIKit
class ViewController: UIViewController {
//发送消息输入框
@IBOutlet weak var textField: UITextField!
//用于显示接收到的消息
@IBOutlet weak var textView: UITextView!
//UDP服务端
var server:SynchronousUDPServer!
//UDP客户端
lazy var client:UDPClient? = {
//初始化客户端
let address = InternetAddress(hostname: "127.0.0.1", port: 8080)
do {
return try UDPClient(address: address)
} catch {
print("Error \(error)")
return nil
}
}()
override func viewDidLoad() {
super.viewDidLoad()
//启动服务器
startServer()
}
//启动服务器
func startServer() {
//在后台线程中启动服务器
DispatchQueue.global(qos: .background).async {
do {
//初始化服务器
self.server = try SynchronousUDPServer(port: 8080)
//在界面上显示启动信息
DispatchQueue.main.async {
let hostname = self.server.address.hostname
let address = self.server.address.addressFamily
let port = self.server.address.port
self.textView.text = "服务器启动,监听:"
+ "\"\(hostname)\" (\(address)) \(port)\n"
}
//接收并处理客户端消息
try self.server.startWithHandler(handler: {
(received:[UInt8], client: UDPClient) in
self.handleClient(received: received, client: client)
})
} catch {
print("Error \(error)")
}
}
}
//处理接收到的客户端消息
func handleClient(received:[UInt8], client:UDPClient){
do {
//将接收到的消息转成String类型
let str = try received.toString()
//将这个String消息显示到界面上
DispatchQueue.main.async {
self.textView.text = self.textView.text + "服务端接收到消息: \(str)\n"
}
//将接收到的消息又发回客户端
try client.send(bytes: received)
} catch {
print("Error \(error)")
}
}
//发送消息
@IBAction func sendMessage(_ sender: Any) {
do {
let message = self.textField.text
if message != nil && message != "" {
try client?.send(bytes: message!.toBytes())
//获取服务端返回的消息
let str = try client!.receive().data.toString()
//将服务端返回的消息显示在界面上
self.textView.text = self.textView.text + "客户端接收到反馈: \(str)\n"
//清空输入框
self.textField.text = ""
}
} catch {
print("Error \(error)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
全部评论(4)
航哥,请问接收到服务端发来的消息后是不是应该有个响应的代理方法呢,不然当有了新消息之后怎么及时接收消息更新UI呢
站长回复:socket是长连接,你看我文章里的代码while循环部分,会一直等待接受服务器发过来的消息。不是说收到一次就退出循环了,而会一直执行下去。
航哥您好 ,我想问一下类似微信的即时通讯对话框,对方发消息我的客户端怎么知道有没有新消息呢,通过轮询可以吗,后台每秒刷新一次看有没有新消息,有的话再更新UI?轮询的话是不是很不合理?
站长回复:轮询这种方式不合适,不仅消息接受不及时,也十分浪费资源。即时通讯一般是如下两种方式:
航哥,你好!我的程序有一个要往服务器发图片的功能,格式是:头+图片+尾
let startData = msgtosend.data(using: String.Encoding.utf8, allowLossyConversion: false)!//头部字符串转data
let endData = end.data(using: String.Encoding.utf8, allowLossyConversion: false)!//尾部字符串转data
let mutableData = NSMutableData()
mutableData.append(startData)//头
mutableData.append(imageData)//图片
mutableData.append(endData)//尾
try client?.send(bytes: message!.toBytes())你的这个发送方法的参数是[UInt8]的,怎么把mutableData转成[UInt8]呢,
我试过先把mutableData转成data在转字符串在转[UInt8],但是发到服务器的时候已经错了,是不是转的太多转坏了
求航哥帮忙解答,谢谢
站长回复:这个send方法的参数是字节数组,所以把先把头、尾、还有图片分别转换为[UInt8],最后再拼接起来发送即可:
航哥,这个支持IPV6吗?谢谢!
站长回复:支持IPV6的。