iOS DeveloperiOS开发攻城狮的集散地

使用GCDAsyncSocket过程中对Socket的总结

2018-07-11  本文已影响106人  CyanCricket

一,前言

前段时间一直在开发并维护一个关于收银系统的项目,其中使用了CocoaAsyncSocket.framework这一类库,着重使用的是该类库里面的GCDAsyncSocket类。这里做个梳理。

该类是socket套接字的封装,可用与iOS平台的socket开发。第一次接触socket要始于大学时候了,也仅了解这么一个东西(那什么,出来混总是要还的)......

二,认识Socket

要了解Socket,有些东西想必是绕不过去的...... 0_0

2.1,socket所在的位置

在七层网络模型中的位置:socket是连接应用层与传输层之间的桥梁
著名的网络七层从下往上依次是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。


七层网络模型

2.2,概念

TCP/IP是传输层协议,主要解决数据如何在网络中传输;
HTTP是应用层协议,主要解决如何包装数据;

其实我们在传输数据的时候,可以只使用传输层,数据同样能到达接收方,但是那样的话由于没有应用层,便无法识别数据内容,那传输的数据就没有意义了,要使传输有意义,则就必须使用应用层协议。

应用层协议很多,有HTTP、FTP、TELNET、等等,或者自己定义应用层协议。WEB使用HTTP作为传输层协议,用来封装HTTP文本信息,然后使用TCP/IP做传输协议将它发送到网络上。

Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket我们可以使用TCP/IP协议。

2.3,HTTP协议

HTTP协议就是超文本传输协议,属于应用层面向对象的协议,常基于TCP连接方式,建立在TCP协议上的一种应用,是web联网的基础,也是手机应用终端常用的协议。

HTTP连接最显著的特点是客户端发送的每次请求都需要服务器返回响应,在请求结束之后,会主动释放连接,俗称“短连接”,从建立连接到关闭连接的过程称为“一次连接”。

由于HTTP在每次请求结束后都会主动释放连接,因此HTTP连接是一种“短连接”,要保持客户端程序的在线状态,需要不断地向服务器发起连接请求。通常的 做法是即时不需要获得任何数据,客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求,服务器在收到该请求后对客户端进行回复,表明知道客 户端“在线”。若服务器长时间无法收到客户端的请求,则认为客户端“下线”,若客户端长时间无法收到服务器的回复,则认为网络已经断开。

2.4,TCP协议

TCP协议是传输控制协议,提供面向连接,可靠地字节流服务,提供超时重发,丢弃重复数据,检验数据,流量控制等功能。在正式收发数据前,必须建立可靠的连接,即“三次握手”。

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

四次挥手:
第一次挥手:数据传输结束以后,客户端的应用进程发出连接释放报文段,并停止发送数据,其首部:FIN=1,seq=u。
第二次挥手:服务器端收到连接释放报文段之后,发出确认报文,其首部:ack=u+1,seq=v。此时本次连接就进入了半关闭状态,客户端不再向服务器发送数据。而服务器端仍会继续发送。
第三次挥手:若服务器已经没有要向客户端发送的数据,其应用进程就通知服务器释放TCP连接。这个阶段服务器所发出的最后一个报文的首部应为:FIN=1,ACK=1,seq=w,ack=u+1。
第四次挥手:客户端收到连接释放报文段之后,必须发出确认:ACK=1,seq=u+1,ack=w+1。 再经过2MSL(最长报文端寿命)后,本次TCP连接真正结束,通信双方完成了他们的告别。
在这个过程中,通信双方的状态如下图,其中:ESTAB-LISHED:连接建立状态、FIN-WAIT-1:终止等待1状态、FIN-WAIT-2:终止等待2状态、CLOSE-WAIT:关闭等待状态、LAST-ACK:最后确认状态、TIME-WAIT:时间等待状态、CLOSED:关闭状态

2.5,UDP协议

用户数据报协议,面向非连接,不保证可靠性的数据传输服务,没有超时重发等机制,故而传输速度很快。

三,Socket的原理

3.1,Socket概念

Socket(套接字)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。包含进行网络通信的必须的五种信息:双方连接使用的协议、本地主机IP地址,本地进程的协议端口、远地主机的IP地址、远地进程的协议端口。

3.2,建立一个socket连接

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

  • 服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
  • 客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
  • 连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户 端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

3.3,Socket与TCP连接的关系

创建Socket连接时候,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP、UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。

3.4,Socket连接与HTTP连接关系

通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。
但在实际网络应用 中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。

而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。

很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给客户端;
若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以 保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端。

四,Socket使用

首先,根据项目实际需求,这里定义主机(也就是server)是iPad,子机(也就是client)是iPad,iPhone,Android终端等;同时,当设备是iPad时,可以切换主子机角色。

1,基于TCP,GCDAsyncSocket

Server端:


server->serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:server delegateQueue:[server serverQueue]];   //初始化、设置代理
BOOL isAccept = [serverSocket acceptOnPort:kPort error:nil];//监测端口

... 表示其他处理的其他操作,


- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket{

...
    [clientSocketArray addObject:model];//使用一个数组保存连接的socket,保证此链接是长连接,如果不保存,则连接完会自动断开,从而导致无法互相发送消息
    
...
    TRMessageModel *msg = [[TRMessageModel alloc] init];
    msg.protocol    = TRInnerProtocolOfWelcome;
    msg.queueType   = TRQueueTypeOfSenderLinear;
    msg.data        = [@"Hello" dataUsingEncoding:NSUTF8StringEncoding];
    msg.isSuccess   = YES;
    msg.hashCode    = msg.hash;
    ...

    [self sendMessageWithSocket:newSocket messageModel:msg];// 发送一些业务信息,其实就是调用[socket writeData:contentData withTimeout:-1 tag:0];方法,根据不同业务发送不同消息

    
    [sock readDataWithTimeout:-1 tag:0];
}

接下来根据不同状态,会走不同的代理方法:


- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
    [sock readDataWithTimeout:-1 tag:0];
}

- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
    [sock readDataWithTimeout:-1 tag:tag];
}


- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{

}

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    LSLog(@"%s",__func__);
    
    ...
      [self receiveMessageWithData:data socketModel:client];// 业务逻辑处理
    ...    
    [sock readDataWithTimeout:-1 tag:tag];
}

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
    //断开之后需要的一些操作
...
// [self detectionHeartbeat];
}

client端:


client.socketModel.socket = [[GCDAsyncSocket alloc] initWithDelegate:client delegateQueue:[client clientQueue]];

与主机端一样,都分别走不同的代理方法


- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {

    TRMessageModel *msgModel  = [[TRMessageModel alloc] init];
    msgModel.protocol           = TRInnerProtocolOfVerification;
    msgModel.data               = [@"connect" dataUsingEncoding:NSUTF8StringEncoding];
    msgModel.queueType          = TRQueueTypeOfSenderLinear;
    msgModel.deviceCode         = self.deviceCode;
    msgModel.deviceName         = self.deviceName;
    msgModel.isSuccess            = YES;
    msgModel.hashCode           = msgModel.hash;
...    

    [self sendMessageWithSocket:sock messageModel:msgModel];
    self.timestamp = TimeStamp();
    ...    
    [self postSynergismHeartbeat];// 发送心跳包
    [self detectionHeartbeat];// 监测心跳
    
    [sock readDataWithTimeout:-1 tag:0];
}

2,基于UDP,GCDAsyncUDPSocket

GCDAsyncUDPSocket类是iOS下支持UDP协议的socket编程类,使用也很容易,通信的收发过程都有代理可以调用。

五,遇到的一些问题

本项目中使用socket过程中还涉及到一些处理粘包问题、消息等待处理、线程问题、队列、消息广播,设备辨认、心跳、网络环境强弱,数据效率,数据安全,数据格式,跨平台,等等;

有的是业务上的,有的是客观环境下的,有的是技术上的,可见一个稳定的项目要客服很多技术问题,所以程序猿们都很棒有木有。

这些问题这里先不一一写过,在后续总结里面一一探讨

希望各路大佬不吝赐教,指出不足。拍砖!拍砖!拍砖!

上一篇 下一篇

猜你喜欢

热点阅读