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

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 连接。

1
2
3
4
5
6
7
8
9
class UDPSocket: NSObject, GCDAsyncUdpSocketDelegate {

public static let shared = UDPSocket()

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

}

开始或者关闭广播

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)
}
}
}
}

监听收到的数据

1
2
3
4
5
6
7
8
9
10
11
12
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
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


 评论