iOS 网络编程socket

一、概念

首先,理清一些概念

TCP/IP和UDP,HTTP协议,Socket


1.TCP/IP和UDP,是网络中比较底层的协议,平时的网络连接基本都离不开这两个协议,其中

1) TCP:面向连接的传输控制协议:

建立连接,形成传输数据的通道(建立连接的三次握手,断开连接的四次握手)。

在连接中进行大数据传输,数据大小不受限制。

通过三次握手完成连接,是可靠协议,数据安全送达。

必须建立连接,效率会稍低。

2) UDP:面向无连接的用户数据报协议:

只管发送,不确认对方是否接收到。

不需要建立连接,将数据及源和目的封装成数据包中,每个数据报的大小限制在 64K 之内。

因为无需连接,因此是不可靠协议。

不需要建立连接,速度快。

应用场景:多媒体教室/网络流媒体。

2.HTTP 协议即超文本传送协议,是建立在 TCP /IP 协议之上的一种应用,主要解决如何包装数据。

HTTP协议详细规定了浏览器与服务器之间相互通信的规则,是万维网交换信息的基础。

HTTP是基于请求-响应形式并且是短连接,并且是无状态的协议。

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

3.Socket通常被称为“套接字”,通常用于描述IP地址和端口,是一个通信的句柄,可以用来实现不同虚拟机或者不同计算机之间的通信。

Socket连接是长连接,理论上客户端和服务器端一旦建立连接将不会主动断开此连接,表现为持续连接,服务端可主动将消息推送给客户端。

Socket虽然能够实现网络通信,但实际上只是对 TCP/IP 协议的封装,本身并不是协议,而是一个调用接口(API),是应⽤层与运输层 TCP/IP 协议族通信的中间软件抽象层,通过调用socket的接口,我们就能使用TCP/IP协议。

Socket区别于HTTP协议的网络连接,在 Internet 上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个 Socket,并绑定到一个端口上,不同的端口对应于不同的服务。以网络中的两个主机为例,通过服务端主机的IP地址和服务软件的端口建立Socket,在客户端主机上建立socket并连接到对应服务端主机的Socket,在这两个socket中间便是网络连接,数据在两个Socket之间进行传输。

PS:关于Socket的类型描述放在最后


二、iOS平台的Socket编程

(一)BSDSocket

Socket一开始是使用纯C编写的,是可以跨平台的,UNIX最底层的Socket是基于BSDSocket的,而苹果的系统底层便是基于UNIX,因此也支持BSDSocket。BSDSocket主要通过一些函数实现Socket连接和数据的读写,swift和OC同样可以使用这些方法,只要import了相应的头文件,但是要实现跨平台的Socket,底层最好还是直接使用C/C++来实现Socket连接

BSDSocket最基本的几个接口函数如下:

1)创建Socket并指定使用的协议(TCP或UDP)

int server = socket(int32 addressFamily, int32 type,int32 protocol)

在创建成功之后,可通过这个函数设置Socket 的选项,即socket的属性,包括端口重用,收发数据时延等。

int err= setsockopt(SOCKET s,int level,int optname,const char* optval,int optlen);

2)将主机的IP地址和某个端口绑定到Socket

int err=bind(int socketFileDescriptor,sockaddr *addressToBind,int addressStructLength)

PS:以上两个函数是建立socket连接必不可少 的,无论是TCP还是UDP的C/S(即服务端和客户端)程序架构的socket连接都要先通过以上两步建立Socket。

3)在绑定好之后,根据选定的协议,若是UDP,则可以直接进行数据传输,以下是发送和接收接口

int err=sendto(int socketFileDescriptor,char *buffer, int bufferLength, intflags, sockaddr *destinationAddress, int destinationAddressLength)

int err=recvfrom(int socketFileDescriptor,char *buffer, int bufferLength, intflags, sockaddr *fromAddress, int*fromAddressLength)

4)若是TCP协议,则服务端还要进行监听客户端连接请求,连接客户端之后,才能进行数据传输

服务端

监听连接请求

int err=listen(int socketFileDescriptor, int backlogSize)

socket() 函数创建的 socket 默认是一个主动类型的,listen() 函数将 socket 变为被动类型的,等待客户的连接请求。

接收连接请求

int peerSocket = accept(int socketFielDescriptor, sockaddr *clientAddress, int clientAddressStructLength)

数据传输

发送,接收接口,第一个参数都是客户端的socket文件描述符peerSocket

int len=send(int socketFileDescriptor, char*buffer, int bufferLength, int flags)

int len=recv(int socketFileDescriptor, char*buffer, int bufferLength, int flags)

最后关闭连接

int close(int socketFileDescriptor)

客户端

不需要监听,直接连接到指定的服务器IP和端口 

客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。

int err = connect(int socketFileDescriptor,sockaddr *serverAddress, int serverAddressLength)

数据传输

发送,接收接口,第一个参数都是客户端的socket文件描述符

int len=send(int socketFileDescriptor, char*buffer, int bufferLength, int flags)

int len=recv(int socketFileDescriptor, char*buffer, int bufferLength, int flags)

最后关闭连接

int close(int socketFileDescriptor)


注意

这里的accept(),recv(),send(),recvfrom(),sendto()几个接口函数都是阻塞式的,即运行时会阻塞当前线程,因此实际开发中需要另外开辟线程执行这些函数。根据执行结果再回到主线程进行数据更新。

完整的UDP连接示意图


完整的TCP连接示意图


(二)CFSocket


iOS官方给出的CFSocket,存在于CoreFoundation框架中,使用纯C语言实现,同样可以实现跨平台应用,它是基于BSDSocket进行抽象和封装的,CFSocket 中包含了少数开销,它几乎可以提供 BSD sockets 所具有的一切功能,并且把 socket 集成进一个“运行循环”RunLoop中。

Core Foundation框架是一组C语言接口,它们为iOS应用程序提供基本数据管理和服务功能

群体数据类型 (数组、集合等)、程序包、字符串管理、日期和时间管理、原始数据块管理、偏好管理、URL及数据流操作、线程和RunLoop、端口和soket通讯

RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面事件循环的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 "接受消息->等待->处理" 的循环中,直到这个循环结束,函数返回。




CFSocket实现流式Socket(基于TCP)的过程如下:(CFSocekt 用于建立连接,CFStream 用于读写数据)

这里我使用OC实现,用swift还需要转化指针,代码过于复杂。

服务端

1)创建socket,这里

CFSocketRef socket_server = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack//触发类型,TCPServerAcceptCallBack,NULL);

TCPServerAcceptCallBack是一个回调函数,在参数中触发类型代表的事件活跃时触发

具体的回调事件触发类型enumCFSocketCallBackType{ 

kCFSocketNoCallBack =0,//表示不需要回调函数,回调函数的参数直接设置为NULL

kCFSocketReadCallBack =1,  kCFSocketAcceptCallBack =2,(常用)  kCFSocketDataCallBack =3,  kCFSocketConnectCallBack =4,  kCFSocketWriteCallBack =8};

根据需要设置端口重用

_Bool reused = YES;

setsockopt(CFSocketGetNative(_socket_server), SOL_SOCKET, SO_REUSEADDR, (const void *)&reused, sizeof(reused));

2)绑定本地IP和端口到Socket

struct sockaddr_in Socaddr;

memset(&Socaddr, 0, sizeof(Socaddr));

Socaddr.sin_len=sizeof(Socaddr);

Socaddr.sin_family=AF_INET;

Socaddr.sin_addr.s_addr=INADDR_ANY;

Socaddr.sin_port=CFSwapInt16(1235);//设置端口,这里任意取的

CFDataRef dataaddr=CFDataCreate(kCFAllocatorDefault, (UInt8*)&Socaddr, sizeof(Socaddr));

CFSocketError err=CFSocketSetAddress(_socket_server, dataaddr);

CFRelease(dataaddr);

由于Core Foundation的对象不属于ARC的管理范畴,所以需要自己release。

3)获取当前线程的runloop并把服务端Socket加入到RunLoop中,启动RunLoop。

这里实际上封装了listen和accept,因此同样会产生阻塞,需要另外开辟线程。

CFRunLoopRef runLoopRef = CFRunLoopGetCurrent();

CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket_server, 0);

CFRunLoopAddSource(runLoopRef, sourceRef, kCFRunLoopCommonModes);

CFRelease(sourceRef);

CFRunLoopRun();

如上面1)中所述,回调函数TCPServerAcceptCallBack会在某些事件触发时自动执行,这里我们要自己设计函数体,在客户端接入之后进行相应操作,回调函数的参数是有固定要求的,但函数名可自定义,具体如下:

void TCPServerAcceptCallBack(CFSocketRef socket,CFSocketCallBackType type,CFDataRef address,const void *data,void *info)

根据参数type是否等于kCFSocketAcceptCallBack,即可确定是客户端连接成功

根据参数data此时是CFSocketNativeHandle类型,获取客户端Socket的Handle,再用getpeername获取客户端Socket地址

CFSocketNativeHandle nativeHanldle = *(CFSocketNativeHandle*)data;//*去引用

getpeername(nativeHanldle, (struct sockaddr * )name , &namelen)

创建对应客户端socket的输入输出流

void CFStreamCreatePairWithSocket(CFAllocatorRef alloc, CFSocketNativeHandle sock, CFReadStreamRef *readStream, CFWriteStreamRef *writeStream);

CFStreamCreatePairWithSocket()操作成功后,readStream和writeStream都指向有效的地址,因此判断是不是还是之前设置的NULL,就知道是否创建成功

客户端

客户端相对简单很多,直接创建对应服务端的IP和端口的Socket,并创建对应的输入输出流

NSString * server = @"192.168.8.39";

CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)server, 12435, &readStream, &writeStream);

判断此时的readStream和writeStream是否都不等于NULL,若是则创建成功


后续的流的操作,客户端和服务端是相同的


流管理


首先我们要对NSStream熟悉一下,它是属于Cocoa框架中的流对象,Cocoa是苹果的面向对象开发框架,Cocoa中的流对象与Core Foundation中的流对象是对应的。因此我们可以通过toll-free桥接方法来进行相互转换。NSStream、NSInputStream和NSOutputStream分别对应CFStream、CFReadStream和CFWriteStream。

我们要做的就是继承协议NSStreamDelegate,将CFStream转化为对应的NSStream并加入到RunLoop,打开流

NSInoutstream* instream;

NSOutputStream * outstream;

self.instream = (__bridge NSInputStream*)readStreamRef;

self.outstream = (__bridge NSOutputStream*) writeStreamRef;

[self.instream setDelegate:selfClass];

[self.outstream setDelegate: selfClass];

[self.instream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

[self.outstream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

[self.instream open];

[self.outstream open];

实现协议方法

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

在这个函数中可以根据下面👇数据流的状态枚举进行不同的流操作

NSStreamEventNone                  // 无事件

NSStreamEventOpenCompleted        // 建立连接完成

NSStreamEventHasBytesAvailable    // 有可读的字节,接收到了数据,可以读了

NSStreamEventHasSpaceAvailable    // 可以使用输出流的空间,此时可以发送数据给服务器

NSStreamEventErrorOccurred        // 发生错误

NSStreamEventEndEncountered        // 流结束事件,在此事件中负责做销毁工作

输入流的读

[_instream read:(nonnull uint8_t *) maxLength:(NSUInteger)]

输出流的写

[_outstream write:(nonnull const uint8_t *) maxLength:(NSUInteger)]

通过服务端和客户端各自的读写流,就能够相互传输数据了,文件图片等都可以转化成字节流进行传输,这里就不多说了。


除了以上两种方法,有很多第三方的库都能实现Socket,操作会更加简单 ,如GCDAsyncSocket,webSocket,Socket.IO


附录:

Socket的类型

流套接字(SOCK_STREAM):流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议。

数据报套接字(SOCK_DGRAM):数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据包套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。

原始套接字(SOCK_RAW):原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。


名字/常量 描述

SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。

SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。

SOCK_SEQPACKET 这个协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。

SOCK_RAW 这个socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)

SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 一、网络各个协议:TCP/IP、SOCKET、HTTP等 网络七层由下往上分别为物理层、数据链路层、网络层、传输层...
    杯水救车薪阅读 2,208评论 0 17
  • 2017.910,今天不是911.早上7点送宝坐车,结果等车的时候来个女司机,问我们走不,宝犹豫了下就上车了,我则...
    艳儿吖阅读 154评论 0 0
  • 我们在这里盘点的学校与普通学校不同之处在于他们都是虚拟的,但虚拟学校会让你的学习比现实学习更加有趣,并教导出众多英...
    f2bc274c0ca8阅读 464评论 0 2