iOS开发之Socket通信基本原理

1.socket基本概念

在移动开发中,我们在很频繁地和后台接口进行数据通讯,通常是http请求,http是一种无状态的协议,无状态是指Web浏览器和Web服务器之间不需要建立持久的连接,这意味着当一个客户端向服务器端发出请求,然后Web服务器返回响应(response),连接就被关闭了,在服务器端不保留连接的有关信息,http遵循请求(Request)/应答(Response)模型。Web浏览器向Web服务器发送请求,Web服务器处理请求并返回适当的应答。所有http连接都被构造成一套请求和应答, 服务器和客户端是如何建立http请求连接过程的呢?答案是通过建立TCP连接,即http是基于TCP三次握手进行连接的。
一次完整的HTTP请求过程从TCP三次握手建立连接成功后开始,客户端按照指定的格式开始向服务端发送HTTP请求,服务端接收请求后,解析HTTP请求,处理完业务逻辑,最后返回一个HTTP的响应给客户端,HTTP的响应内容同样有标准的格式。无论是什么客户端或者是什么服务端,大家只要按照HTTP的协议标准来实现的话,那么它一定是通用的。在此文就不对http请求做过多的介绍了,那什么是socket呢?socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭) ,这里着重介绍介绍TCP/UDP的socket通信机制和基本原理。

2. 基本原理

客户端和服务端建立长连接经过了以下的步骤:


socket连接过程.jpg
(1)连接建立步骤

服务器端的步骤是:
  1、创建一个socket,用函数socket();
  2、设置socket属性,用函数setsockopt(); * 可选
  3、绑定IP地址、端口等信息到socket上,用函数bind();
  4、开启监听,用函数listen();
  5、接收客户端上来的连接,用函数accept();
  6、收发数据,用函数send()和recv(),或者read()和write();
  7、关闭网络连接;
  8、关闭监听;

客户端的步骤为:
  1、创建一个socket,用函数socket();
  2、设置socket属性,用函数setsockopt();* 可选
  3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
  4、设置要连接的对方的IP地址和端口等属性;
  5、连接服务器,用函数connect();
  6、收发数据,用函数send()和recv(),或者read()和write();
  7、关闭网络连接;

作为TCP/IP传输协议的一种,客户端和服务端通过TCP建立socket连接的内部原理远比上图的步骤复杂,客户端和服务端是通过何种方式来建立连接的呢 ?我们知道客户端要和服务端进行连接需要通过3次握手:
客户端向服务器发送一个SYN J
服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
客户端再像服务器发一个确认ACK K+1

只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:
三次握手.png

其大致过程类似于拨号打电话: A打电话给B,B接收,B说喂,A听说到了喂,然后A也说喂,其中A和B都知道了对方是在给自己打电话,相互发送了一个信号表示我可以听到你的来电,即表示收到了信号,连接是可靠的,即可以开始建立连接,来进行详谈了。
从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求( 服务端启动之后就在不断地监听连接请求),即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。
(2)断开连接步骤

断开连接,即tcp的四次挥手步骤:


四次挥手.png

从上图可以看到,客户端或者服务端首先调用close主动关闭连接,这时TCP发送一个FIN M;
另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
接收到这个FIN的源发送端TCP对它进行确认。
这样每个方向上都有一个FIN和ACK.
与之对应的UDP的连接步骤要简单许多,分别如下:
  UDP编程的服务器端一般步骤是:
  1、创建一个socket,用函数socket();
  2、设置socket属性,用函数setsockopt();* 可选
  3、绑定IP地址、端口等信息到socket上,用函数bind();
  4、循环接收数据,用函数recvfrom();
  5、关闭网络连接;

UDP编程的客户端一般步骤是:
  1、创建一个socket,用函数socket();
  2、设置socket属性,用函数setsockopt();* 可选
  3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
  4、设置对方的IP地址和端口等属性;
  5、发送数据,用函数sendto();
  6、关闭网络连接;

在iOS开发中的socket连接是基于C函数,本文不讲底层的代码实现过程(本人技术有限),本文 使用了大神写的CocoaAsyncSocket和YYNetwork来模拟了一下客服端和服务端建立的socket长连接:


YYNetWork服务端.png

客户端的代码如下所示:

// MARK: - viewControlelr'view's lifeCircle
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [_client disconnect];
    [_timer  invalidate];
    _timer = nil;
    NSLog(@"socket断开了连接!");
}

- (void)readData {
    [_client readDataToLength:1000 withTimeout:-1 tag:123];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    _client = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    NSError *error = nil;
    //连接服务端 IP和Port
    [_client  connectToHost:@"192.168.0.192"
                     onPort:8080
                      error:&error];
    if (error) {
        NSLog(@"链接服务端失败!");
    } else{
        
    }
    if (@available(iOS 10.0, *)) {
        __weak typeof(self)weakSelf = self;
        _timer = [NSTimer scheduledTimerWithTimeInterval:5.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
            ClientVC *strongSelf = weakSelf;
            //向服务器发送数据
            
            [strongSelf sendDataToServer];
        }];
    } else {
        _timer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(sendDataToServer) userInfo:nil repeats:YES];
    }
    [_timer fire];
    [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
    
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"readData" style:UIBarButtonItemStylePlain target:self action:@selector(readData)];
    //监听接收服务端传过来的数据
    [_client readDataWithTimeout:-1 tag:123];
}

-(void)sendDataToServer {
    [_client writeData:[@"HelloWord!" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:1.0 tag:123];
}
// MARK: -GCDAsyncSocketDelegate

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
    NSLog(@"连接上了服务端!");
}

// MARK: - 数据接收
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    NSString *recieveStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    self.navigationItem.title = recieveStr;
    NSLog(@"接收到服务端的数据:%@",recieveStr);
    //监听服务端发送过来的数据
    [_client readDataWithTimeout:-1 tag:123];
    //本地推送一波
    [LocalPushCenter localPushForDate:[NSDate date]
                               forKey:recieveStr
                            alertBody:recieveStr
                          alertAction:recieveStr
                            soundName:nil
                          launchImage:nil
                             userInfo:@{@"info":recieveStr}
                           badgeCount:1
                       repeatInterval:NSCalendarUnitDay];
    
}

- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag {
}


// MARK: - 数据发送
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
    NSLog(@"向服务端发送数据!");
}

// MARK: - 与服务端断开连接
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
    NSLog(@"与服务端断开了!");
}

// MARK: - memory management
-(void)dealloc {
    [_timer invalidate];
    _timer = nil;
    _client.delegate = nil;
    [_client disconnect];
    _client = nil;
}

你可能感兴趣的:(iOS开发之Socket通信基本原理)