iOS开发网络知识Socket&&Stream

前言

开发的很多项目都用到了Socket长链接,使用场景是即时性和主动性的请求需求。

本文的学习过程

1.Socket的位置和理解

2.Socket通讯过程 && Stream流的学习

3.iOS的Socket开发

1.Socket的位置和理解

Socket的理解

socket的位置

网络七层架构

Socket的位置

socket(套接字)是抽象出来的接口,是一个通信链的句柄,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。

2.Socket的两种协议

TCP:面向连接、传输可靠(保证数据正确性,保证数据顺序)、用于传输大量数据(流模式)、速度慢,建立连接需要开销较多(时间,系统资源)

UDP:面向非连接、传输不可靠、用于传输少量数据(数据包模式)、速度快。(类似广播)

Socket通讯过程图解

Socket运行中的C函数

1.创建套接字

Socket(af,type,protocol)//建立地址和套接字的联系

bind(sockid, local addr, addrlen)//服务器端侦听客户端的请求

listen( Sockid ,quenlen)//建立服务器/客户端的连接 (面向连接TCP)

2.客户端请求连接

Connect(sockid, destaddr, addrlen)//服务器端等待从编号为Sockid的Socket上接收客户连接请求

newsockid=accept(Sockid,Clientaddr, paddrlen)//发送/接收数据

3.面向连接:

send(sockid, buff, bufflen)

recv()

4.面向无连接:             5.释放套接字    close(socked)

endto(sockid,buff,…,addrlen)

recvfrom()

3.iOS的Socket开发

在iOS中以NSStream(流)来发送和接收数据,可以设置流的代理,对流状态的变化做出相应的动作(连接建立,接收到数据,连接关闭)。

NSStream:数据流的父类,用于定义抽象特性,例如:打开、关闭代理,NSStream继承自CFStream(CoreFoundation)
NSInputStream:NSStream的子类,用于读取输入
NSOutputStream:NSSTream的子类,用于写输出。

1.CFNetwork理解(OC底层的流)

CFNetWork是苹果提供的位于 Core Foundation 中的一个基于C的底层框架,是对更底层OS层BSD socket的封装

CFNetWork主要依赖俩个API,CFSocket 和 CFStream,CFSocket主要用于网络底层的通信,而CFStream包括CFReadStream 和 CFWriteStream,分别用于对Socket的读取和写入。

CFStream,流,是一个在搭建的通讯通道里连续传送的字节序列。steam是单向的,所以有必要建立input(read) stream和output(write) stream。

PS:

AFNetworking 使用NSStream

WebSocket 使用NSStream(H5一种协议,SocketRocket)

 

NSStream是一个基类,在Cocoa中它有两个子类NSInputStream和INOutputStream。分别对应输入和输出流。流提供了一种简单的方式在不同和介质中交换数据,这种交换方式是与设备无关的。你可以创建一个流通过NSData,File,SocketData,在处理大数据的时候,使用流就可以边读边处理。Cocoa中的流对象与Core Foundation中的流对象是对应的。我们可以通过toll-free桥接方法来进行相互转换。NSStream、NSInputStream和NSOutputStream分别对应CFStream、CFReadStream和CFWriteStream。

NSStream比CFStream有更高的扩展性,例如创建一个获得它读到的数据的长度,或者你能够做一个子类,它的实例化对象能够从它们的流里寻找,发回已读字节。NSStream有它自己一套的必需重写方法,如NSInputStream和NSOutputStream。

 

NSStream的使用过程

1.Input流(读,从流中读出数据)

1.从数据源创建和初始化一个NSInputStream实例

2.将输入流对象配置到一个run loop,open the stream

3. 通过流对象的delegate函数处理事件

4. 当所有数据读完,关闭流

一,使用流对象的准备工作

在使用NSInputStream对象之前你必须有流的数据源,数据源的类型可以是文件,NSData对象,或者一个网络套接字。(前3步)如下

 data = [NSMutableData data];

    NSString *filePath = [[NSBundle mainBundle]pathForResource:@"test1" ofType:@"txt"];

    NSInputStream *inputStream = [[NSInputStream alloc] initWithFileAtPath:filePath];

    inputStream.delegate = self;

    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

   [inputStream open];

在你open stream之前,将该对象配置到一个run loop接收stream events。这样,当流中没有数据可读时可以避免delegate阻塞。如果流是发生在另一个线程,你需要确认该流对象是配置在哪个线程的run loop中,不应该在别的线程来处理流!最后Open,开始流!!!

二,处理Stream Events(协议方法)

open 流对象后,流对象会一直向其delegate发送stream:handleEvent: 消息直到到达了流对象的末尾。这些消息的参数中包含一个指示流事件类型的NSStreamEvent常量。对NSInputStream对象而言,最常用的事件类型是NSStreamEventOpenCompleted,NSStreamEventHasBytesAvailable,NSStreamEventEndEncountered。我们尤其感兴趣的应该是NSStreamEventHasBytesAvailable事件。下面的例子就是一个处理NSStreamEventHasBytesAvailable事件的好的方法:

以下例子包括了输入流和输出流的处理方法(便于同学们直接拷贝使用&整体了解)。

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{

    switch (eventCode){

        case NSStreamEventHasBytesAvailable:{

            uint8_t buf[2048];

            NSInteger len = 0;

            len = [(NSInputStream *)aStream read:buf maxLength:2048];  // 读取数据

            if (len) {

                [data appendBytes:buf length:len];

            } else {

                NSLog(@"没有buffer");

            }

        }

        break;

        case NSStreamEventEndEncountered:{

            [aStream close];

        }

        break;

        case NSStreamEventHasSpaceAvailable:{

            NSInteger bufSize = 2048;

            uint8_t buf[bufSize];

            [data getBytes:buf length:bufSize];

            NSOutputStream *writeStream = (NSOutputStream *)aStream;

            NSInteger len = [writeStream write:buf maxLength:sizeof(buf)];

            [data setLength:0];

            self.location += len;

            if (self.location>self.fileSize) {

                NSLog(@"NSStreamEventEndEncountered");

                [aStream close];

            }

        }

        break;

        default:

        break;

    }

}

 

这里声明了一个大小为2048的buf,(PS:然后将它缓存在一个可变的data里(viewdidload的时候实例化了),这个data为演示使用!!!)调用read:maxLength:函数从stream中读取指定大小的数据到buf中,如果读取成功,delegate将会将读取到的数据添加到NSMutableData对象_data中。

三,处理stream object

当NSInputStream对象到达steam的末尾的时候,它会向stream:handleEvent:函数发送一个NSStreamEventEndEncountered事件类型常量,这里应该需要关闭流!!

  case NSStreamEventEndEncountered:{

            [aStream close];

        }

2.output流(写,将数据写入流)

1,创建和初始化一个NSOutputSteam实例,设置写的路径,并且设置它的delegate。

2,将这个流对象布置在一个runloop上并且open the stream。

3,处理流对象向其delegate发送的事件消息。

4,如果流对象向内存中写入了数据,那么可以通过使用NSStreamDataWrittenToMemoryStreamKey属性获取数据。

5,当没有数据可供写入时,清理流对象。

使用NSOutputStream对象之前你必须指定数据写入的流的目标位置,输出流对象的目标位置可以是file,C buffer, application memory,network socket

 NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) lastObject];

    NSString *outputPath = [doc stringByAppendingPathComponent:@"local.txt"];

    NSLog(@"----path--->%@",doc);

    NSOutputStream *writeStream = [[NSOutputStream alloc] initToFileAtPath:outputPath append:YES];

    [writeStream setDelegate:self];

    [writeStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

    [writeStream open];

本例可以获取文件数据的长度

NSDictionary *fileAttributes = [[NSFileManager defaultManager]attributesOfItemAtPath:filePath error:nil];

    unsigned long long length = [fileAttributes fileSize];

    self.fileSize = length;

当流对象有 有空间可供数据写入 之类的与流有关的事件消息发送时,delegate会收到从NSOutputStream对象发送来的消息。

将流放在一个runloop上,当流对象不能接收更多数据的时候,可以使delegate避免阻塞。如果流是发生在另一个线程,你需要确认该流对象是配置在哪个线程的run loop中,不应该在别的线程来处理流!最后Open,开始流!!!开始数据项NSOutputStream对象传送。

二,处理 Stream Events

开启NSOutputStream后,只要delegate持续向流对象写入数据,流对象就是一直向其delegate发送stream:handleEvent:消息,直到到达了流的末尾。

全部代码已在上面演示

case NSStreamEventHasSpaceAvailable:{
…}

//判断长度,已经没有
if (self.location>self.fileSize) {

}

当在向stream中写数据时NSOutputStream对象发生错误,它会停止streaming并且使用NSStreamEventErrorOccurred消息通知其delegate。

通过向NSOutputStream对象发送propertyForKey:消息获取从流向内存中写入的数据,设定key的值为NSStreamDataWrittenToMemoryStreamKey,该stream object将数据返回到一个NSData对象中。

仿Socket通讯,NSStreamSocket(Stream实现Socket的传输功能)

CFReadStreamRef readStream;

    CFWriteStreamRef writeStream;

    CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)@"172.16.4.26", PORT, &readStream, &writeStream);    _inputStream = (__bridge_transfer NSInputStream *)readStream;

    _outputSream = (__bridge_transfer NSOutputStream *)writeStream;

    [_inputStream setDelegate:self];

    [_outputSream setDelegate:self];

    [_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

    [_outputSream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

    [_inputStream open];

    [_outputSream open];

在iOS开发中,一般使用第三方的Socket框架实现Socket通讯功能。

CocoaAsyncSocket

CocoaAsyncSocket支持tcp和udp。其中:

AsyncSocket类是支持TCP的

AsyncUdpSocket是支持UDP的

AsyncSocket是封装了CFSocket和CFSteam的TCP/IP socket网络库

支持基于IPV4和IPV6的TCP流

AsyncUdpSocket是UDP/IP socket网络库,包装自CFSocket

使用过程

链接成功回调协议方法

- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port{

    NSLog(@"did connect to host");

}

协议中的回调方法获取数据。。。。

- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{

    NSLog(@"did read data");

}

发送数据

AsyncSocket *socket=[[AsyncSocket alloc] initWithDelegate:self];

    [socket connectToHost:@"www.baidu.com" onPort:80 error:nil];

    [socket readDataWithTimeout:3 tag:1];

    [socket writeData:[@"GET / HTTP/1.1\n\n" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:3 tag:1];

网络数据传输

Wireshark捉包

仔细观察,我们能找到源IP,源端口,目标IP,目标端口 和数据 等信息

那么在实际的应用过程中,前端需要做什么?

1.编译数据/序列化,反编译数据/逆序列化

2.数据缓冲,防止丢包,粘包导致数据错误

3.异常断开重连机制(包括网络错误,网络环境变化)

4.发送心跳包,保证长链接

5.过滤心跳包响应数据

6.申请链接&&主动断开链接

普通的数据包格式(参考)