iOS Socket连接
1 socket简介和网络协议
Socket是进程通讯的一种方式,即调用这个网络库的一些API函数实现分布在不同主机的相关进程之间的数据交换。
1.1 网络模型
OSI模型:应用层 表示层 会话层 传输层 网络层 数据链路层 物理层
eg:qq 应用层 表示层 会话层 发送文本信息hello
传输层 tcp/udp + hello
网络层 ip + tcp/udp + hello
数据链路层 mac地址 + llc + ip + tcp/udp + hello
物理层 比特流
简化模型:应用层 运输层 网络层 数据链路层 物理层
1.2 网络协议
- HTTP协议 超文本传输协议 一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统 基于TCP/IP 协议
2)HTTPS协议 安全套接字层超文本传输协议
HTTP和HTTPS区分:
1.https协议需要到ca申请证书,一般免费证书很少,需要交费。
2.http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
3.http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4.http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
3)TCP 传输控制协议
特点
1、建立连接,形成传输通道
2、在连接中进行数据传输(数据不收大小控制)
3、通过三次握手完成连接,可靠的协议。保证消息完全到达
4、必须建立连接,所以效率很低
4)UDP 用户数据协议
特点
1、将数据内容和源地址、目的地址全部封装成一个数据包,不需要建立连接。
2、每个数据包有大小限制,限制在64k之内。
3、无需要连接,因此是不可靠的传输。
4、速度快
TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。
Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。
1.3 TCP连接 Socket连接
TCP连接:
TCP的三次握手:
SYN(synchronous),同步标志,ACK (Acknowledgement),即确认标志,seq应该是Sequence Number,序列号的意思,另外还有四次握手的fin,应该是final,表示结束标志。
第一次握手:客户端发送一个TCP的SYN标志位置1的包指明客户打算连接的服务器的端口,以及初始序号X,保存在包头的序列号(Sequence Number)字段里。
第二次握手:服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同时,将确认序号(Acknowledgement Number)设置为客户的序列号加1以,即X+1。
第三次握手:客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1。并且把服务器发来ACK的序号字段+1,放在确定字段中发送给对方.并且在数据段放写序列号的+1。
1)TCP连接和Socket连接的区分
socket层只是在TCP/UDP传输层上做的一个抽象接口层,因此一个socket连接可以基于连接,也有可能基于UDP。基于TCP协议的socket连接同样需要通过三次握手建立连接,是可靠的;基于UDP协议的socket连接不需要建立连接的过程,不过对方能不能收到都会发送过去,是不可靠的,大多数的即时通讯IM都是后者
2 基于TCP协议的 Socket连接
2.1 socket 通信
2.1.1 iOS网络编程层次:
Cocoa层:NSURL,NSStream,GameKit
Core Foundtion:基于C的CFNetWork 和 CFNetServices 其中CFNetWork 基于CFStream和CFSocket
OS层:基于C的BSD socket
2.1.2 socket 简介
Socket是进程通讯的一种方式,即调用这个网络库的一些API函数实现分布在不同主机的相关进程之间的数据交换。
2.2 BSD Socket
API函数:
创建套接字
Socket(af,type,protocol)
复制代码
建立地址和套接字的联系
bind(sockid, local addr, addrlen)
复制代码
服务器端侦听客户端的请求
listen( Sockid ,quenlen)
复制代码
建立服务器/客户端的连接 (面向连接TCP)
客户端请求连接
Connect(sockid, destaddr, addrlen)
服务器端等待从编号为Sockid的Socket上接收客户连接请求
newsockid=accept(Sockid,Clientaddr, paddrlen)
复制代码
发送/接收数据
面向连接:
send(sockid, buff, bufflen)
recv( )
面向无连接:
sendto(sockid,buff,…,addrlen)
recvfrom( )
复制代码
释放套接字
close(sockid)
复制代码
TCP协议下的 Socket C/S 连接步骤
2.2.1 服务器工作流程
服务器调用 socket(...) 创建socket;
服务器blind(...) 将 socket 与特定主机地址与端口号绑定,成功绑定返回0,失败返回 -1。
服务器调用 listen(...) 设置缓冲区;
服务器通过 accept(...)接受客户端请求建立连接;
服务器与客户端建立连接之后,就可以通过 send(...)/receive(...)向客户端发送或从客户端接收数据;
服务器调用 close 关闭 socket;
2.2.2 客户端工作流程
客户端调用 socket(...) 创建socket;
客户端调用 connect(...) 向服务器发起连接请求以建立连接;
客户端与服务器建立连接之后,就可以通过 send(...)/receive(...)向客户端发送或从客户端接收数据;
客户端调用 close 关闭 socket;
1 )demo C 客户端 使用BSD socket API 环境 IPV4 服务端 nc -lk 端口号 始终监听本地计算机此端口的数据
2.3 CFNetWork
对BSD socket 轻量级封装 结合run-loop
demo 处理 API
CFNetwork API
CFNetwork 接口是基于 C 的,下面的接口用于创建一对 socket stream,一个用于读取,一个用于写入:
void CFStreamCreatePairWithSocketToHost(CFAllocatorRef alloc, CFStringRef host, UInt32 port, CFReadStreamRef *readStream, CFWriteStreamRef *writeStream);
复制代码
该函数使用 host 以及 port,CFNetwork 会将该 host 转换为 IP 地址,并转换为网络字节顺序。如果我们只需要一个 socket stream,我们可以将另外一个设置为 NULL。还有另外两个“重载”的创建 socket sream的接口:CFStreamCreatePairWithSocket 和 CFStreamCreatePairWithPeerSocketSignature。
在使用这些 socket stream 之前,必须显式地调用其 open 函数:
Boolean CFReadStreamOpen(CFReadStreamRef stream);
Boolean CFWriteStreamOpen(CFWriteStreamRef stream);
复制代码
但与 socket 不同的是,这两个接口是异步的,当成功 open 之后,如果调用方设置了获取 kCFStreamEventOpenCompleted 事件的标志的话就会其调用回调函数。
而该回调函数及其参数设置是通过如下接口进行的:
Boolean CFReadStreamSetClient(CFReadStreamRef stream, CFOptionFlags streamEvents, CFReadStreamClientCallBack clientCB, CFStreamClientContext *clientContext);
Boolean CFWriteStreamSetClient(CFWriteStreamRef stream, CFOptionFlags streamEvents, CFWriteStreamClientCallBack clientCB, CFStreamClientContext *clientContext);
复制代码
该函数用于设置回调函数及相关参数。通过 streamEvents 标志来设置我们对哪些事件感兴趣;clientCB 是一个回调函数,当事件标志对应的事件发生时,该回调函数就会被调用;clientContext 是用于传递参数到回调函数中去。
当设置好回调函数之后,我们可以将 socket stream 当做事件源调度到 run-loop 中去,这样 run-loop 就能分发该 socket stream 的网络事件了。
void CFReadStreamScheduleWithRunLoop(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode);
void CFWriteStreamScheduleWithRunLoop(CFWriteStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode);
复制代码
将 socket stream 从 run-loop 的事件源中移除。
void CFReadStreamUnscheduleFromRunLoop(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode);
void CFWriteStreamUnscheduleFromRunLoop(CFWriteStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode);
复制代码
当我们将 socket stream 的网络事件调度到 run-loop 之后,我们就能在回调函数中相应各种事件,比如 kCFStreamEventHasBytesAvailable 读取数据:
Boolean CFReadStreamHasBytesAvailable(CFReadStreamRef stream);
CFIndex CFReadStreamRead(CFReadStreamRef stream, UInt8 *buffer, CFIndex bufferLength);
复制代码
或 kCFStreamEventCanAcceptBytes 写入数据:
Boolean CFWriteStreamCanAcceptBytes(CFWriteStreamRef stream);
CFIndex CFWriteStreamWrite(CFWriteStreamRef stream, const UInt8 *buffer, CFIndex bufferLength);
复制代码
最后,我们调用 close 方法关闭 socket stream:
void CFReadStreamClose(CFReadStreamRef stream);
void CFWriteStreamClose(CFWriteStreamRef stream);
复制代码
2.4 NSStream
1 使用ayscsocket搭建本地服务器
借助第三方Aysncsocket搭建本地服务器 demo。
终端 telnet IP地址 端口号
涉及的代理方法:
1)-(void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket
2)-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
3)-(void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag
2 基于本地的socket连接 使用NSStream
NSStream 实体类 NSInputStream NSOutputStream
NSStream API 接口
- (void)open;
- (void)close;
- (id )delegate;
- (void)setDelegate:(id )delegate;
- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;
- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;
- (NSStreamStatus)streamStatus;
- (NSError *)streamError;
NSInputStream 类有如下接口:
-
(NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len; 从流中读取数据到 buffer 中,buffer 的长度不应少于 len,该接口返回实际读取的数据长度(该长度最大为 len)。
-
(BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len; 获取当前流中的数据以及大小,注意 buffer 只在下一个流操作之前有效。
-
(BOOL)hasBytesAvailable; 检查流中是否还有数据。
NSOutputStream 类有如下接口:
-
(NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len; 将 buffer 中的数据写入流中,返回实际写入的字节数。
-
(BOOL)hasSpaceAvailable; 检查流中是否还有可供写入的空间。
demo:
1)iOS NSStream不支持 NShost
https://developer.apple.com/library/ios/qa/qa1652/_index.html
NSStream类不支持连接到远程主机,使用CFStream支持。两者可以通过toll-free桥接来相互转换。使用CFStream时,我们可以调用CFStreamCreatePairWithSocketToHost函数并传递主机名和端口号,来获取一个CFReadStreamRef和一个CFWriteStreamRef来进行通信,然后我们可以将它们转换为NSInputStream和NSOutputStream对象来处理。
2)使用NSStreamDelegate 处理流事件
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode