网络由下往上分为:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。

IP协议对应于网络层,TCP协议对应于传输层,HTTP协议对应于应用层,三者从本质上来说没有可比性,Socket则是对TCP/IP协议的封装和应用。也可以说,TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。

CocoaAsyncSocket是一个十分好用的异步Socket库,本文将以CocoaAsyncSocket作为工具进行网络通信的操作,Demo源码链接在文章末尾。

TCP的三次握手与四次挥手

连接过程:三次握手

  1. 客户端发送SYN包(SYN=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
  2. 服务器收到SYN包,必须确认客户的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
  3. 客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ACK=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

关闭过程:四次挥手

  1. 客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送(报文段4)。
  2. 服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。
  3. 服务器B关闭与客户端A的连接,发送一个FIN给客户端A(报文段6)。
  4. 客户端A发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。

HTTP连接的特点

HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。
HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

TCP和UDP的区别

  • TCP协议是有连接而UDP是无连接的。有连接的意思是开始传输实际数据之前TCP的客户端和服务器端必须通过三次握手建立连接,会话结束之后也要结束连接。
  • TCP协议保证数据按序发送,按序到达,提供超时重传来保证可靠性,但是UDP不保证按序到达,甚至不保证到达,只是努力交付,即便是按序发送的序列,也不保证按序送到。
  • TCP协议所需资源多,TCP首部需20个字节(不算可选项),UDP首部字段只需8个字节。
  • TCP有流量控制和拥塞控制,UDP没有,网络拥堵不会影响发送端的发送速率。
  • TCP是一对一的连接,而UDP则可以支持一对一,多对多,一对多的通信。
  • TCP面向的是字节流的服务,UDP面向的是报文的服务。

各自的应用场景

  • 简短信息适合用UDP实现,因为UDP是基于报文段的,它直接对上层应用对数据封装成报文段,然后丢在网络中,如果信息量太大,会在链路层中被分片,影响传输效率。
  • 注重性能高于完整性和安全性,适合用UDP,例如多媒体应用,缺一两帧不影响用户体验,但是需要流媒体到达的速度快。
  • 要求快速响应的场景,如即时通讯聊天。
  • TCP适合于注重安全性的场景。

Demo实战

UDPSocket

创建一个 UDPSocket 用于管理 UDP 连接。

class UDPSocket: NSObject, GCDAsyncUdpSocketDelegate {

public static let shared = UDPSocket()

lazy var asyncUdpSocket: GCDAsyncUdpSocket = {
return GCDAsyncUdpSocket(delegate: self, delegateQueue: DispatchQueue(label: "com.xaoxuu.socket"))
}()

}

开始或者关闭广播

var broadcasting = false
func broadcast(_ flag: Bool?){
if let f = flag {
broadcasting = f
broadcast(nil)
} else if broadcasting == true {
if let host = broadcastHost() {
asyncUdpSocket.send(broadcastData, toHost: host, port: kPort, withTimeout: 1000, tag: 1)
DispatchQueue.main.asyncAfter(deadline: .now()+3) {
self.broadcast(nil)
}
}
}
}

监听收到的数据

func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress address: Data, withFilterContext filterContext: Any?) {
if data == broadcastData {
if let host = GCDAsyncUdpSocket.host(fromAddress: address) {
if let h = safeHost(host) {
if hosts.contains(h) == false {
hosts.append(h)
}
}
}
NotificationCenter.default.post(name: didUpdate, object: hosts)
}
}

TCPSocket

let kPort = UInt16(5528)
class TCPSocket: NSObject, GCDAsyncSocketDelegate {

public static let shared = TCPSocket()

lazy var asyncSocket: GCDAsyncSocket = {
return GCDAsyncSocket(delegate: self, delegateQueue: DispatchQueue(label: "com.xaoxuu.socket"))
}()

var clientSockets = [GCDAsyncSocket]()
var host = ""
var port = kPort

private var connectedHosts = [String:String]()

typealias ConnectCallback = (SocketCharacter, String, Error?) -> Void
typealias DisconnectCallback = (SocketCharacter, String?) -> Void
typealias ReceiveMessageCallback = (String?, String?) -> Void

var character = SocketCharacter.server

var block_onConnect: ConnectCallback?
var block_onDisconnect: DisconnectCallback?
var block_onReceiveMessage: ReceiveMessageCallback?


func onConnect(_ callback: @escaping ConnectCallback) {
block_onConnect = callback
}

func onDisconnect(_ callback: @escaping DisconnectCallback) {
block_onDisconnect = callback
}
func onReceiveMessage(_ callback: @escaping ReceiveMessageCallback) {
block_onReceiveMessage = callback
}


func startServer(host: String) {
character = SocketCharacter.server
self.host = host
if asyncSocket.isConnected {
asyncSocket.disconnect()
}
do {
try asyncSocket.accept(onPort: port)

if let f = self.block_onConnect {
f(self.character, host, nil)
}
UDPSocket.shared.broadcast(true)
} catch {
debugPrint(error)
if let f = self.block_onConnect {
f(self.character, host, error)
}
}

}
func endServer(){
let h = self.host
TCPSocket.shared.asyncSocket.disconnect()
UDPSocket.shared.broadcast(false)
if let idx = UDPSocket.shared.hosts.index(of: h) {
UDPSocket.shared.hosts.remove(at: idx)
}


}
func connectServer(host: String) {
character = SocketCharacter.client
self.host = host
if asyncSocket.isConnected {
asyncSocket.disconnect()
}
do {
try asyncSocket.connect(toHost: host, onPort: port)
} catch {
debugPrint(error)
if let f = self.block_onConnect {
f(self.character, host, error)
}
}
}
func sendData(data: Data) {
asyncSocket.write(data, withTimeout: -1, tag: 0)
}



// MARK: - delegate
// 在读取数据之前 服务端还需要监听 客户端有没有写入数据
func socket(_ sock: GCDAsyncSocket, didAcceptNewSocket newSocket: GCDAsyncSocket) {
debugPrint("sock: \(sock), newSocket: \(newSocket)")
clientSockets.append(newSocket)
if let h = newSocket.connectedHost {
connectedHosts[NSString.pointerDescription()(newSocket)] = h
}
// let key = NSString.pointerDescription()(newSocket)
// if let ip = newSocket.connectedHost {
// ips[key] = ip
// }
DispatchQueue.main.async {
if let f = self.block_onConnect {
if let ip = newSocket.connectedHost {
f(self.character, ip, nil)
} else {
f(self.character, "", nil)
}
}
}
// 监听客户端是否写入数据
// timeOut: -1 暂时不需要 超时时间 tag暂时不需要 传0
newSocket.readData(withTimeout: -1, tag: 0)
}

// 服务器读取客户端发送数据
func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) {
let dataStr = String.init(data: data, encoding: String.Encoding.utf8)
DispatchQueue.main.async {
if let f = self.block_onReceiveMessage {
f(sock.connectedHost, dataStr)
}
}
sock.readData(withTimeout: -1, tag: 0)
}

func socket(_ sock: GCDAsyncSocket, didConnectToHost host: String, port: UInt16) {
debugPrint("sock: \(sock) connect to: \(host) port: \(port)")
DispatchQueue.main.async {
if let f = self.block_onConnect {
f(self.character, host, nil)
}
}
sock.readData(withTimeout: -1, tag: 0)
}


func socketDidDisconnect(_ sock: GCDAsyncSocket, withError err: Error?) {

let host = connectedHosts[NSString.pointerDescription()(sock)]
if let h = host {
connectedHosts.removeValue(forKey: h)
}
if let idx = clientSockets.index(of: sock) {
clientSockets.remove(at: idx)
}
DispatchQueue.main.async {
if let f = self.block_onDisconnect {
f(self.character, host)
}
}

// 服务器关闭
if sock == asyncSocket {
for sock in clientSockets {
sock.disconnect()
}
}

}

}

Demo源码

https://github.com/xaoxuu/SocketDemo


评论