iOS 使用 CocoaAsyncSocket 进行通信 网络由下往上分为:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
IP协议对应于网络层,TCP协议对应于传输层,HTTP协议对应于应用层,三者从本质上来说没有可比性,Socket则是对TCP/IP协议的封装和应用。也可以说,TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。
CocoaAsyncSocket是一个十分好用的异步Socket库,本文将以CocoaAsyncSocket作为工具进行网络通信的操作,Demo源码链接在文章末尾。
TCP的三次握手与四次挥手 连接过程:三次握手 客户端发送SYN包(SYN=j)到服务器,并进入SYN_SEND状态,等待服务器确认; 服务器收到SYN包,必须确认客户的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; 客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ACK=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。 由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
关闭过程:四次挥手 客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送(报文段4)。 服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。 服务器B关闭与客户端A的连接,发送一个FIN给客户端A(报文段6)。 客户端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 ) } 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 } DispatchQueue .main.async { if let f = self .block_onConnect { if let ip = newSocket.connectedHost { f(self .character, ip, nil ) } else { f(self .character, "" , nil ) } } } 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() } } } }