网络编程
1. 概论
两个通信设备(具有网卡)通过网络进行数据的传播与交换
- 建立连接:通过IP或者域名来连接两台设备,通过端口号找到对应的通信程序
- 通信协议:要传输的数据需要根据约定的格式来创建,或者接受的数据需要按照约定的格式来还原
- 数据传输方式:双工、长链接、可靠的,如TCP传输协议,单工、广播、不可靠的,如UDP传输协议
- TCP/IP分层模式:将复杂的网络通信分拆,层的功能独立,下层为上层提供服务
- TCP连接:三次握手
2. SOCKET原理
socket的概念
socket是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。
处于应用层和传输层之间;socket包含进行网络通信必须的五种信息:
①连接使用的协议,
②本地主机的IP地址,
③本地进程的协议端口,
④远地主机的IP地址,
⑤远地进程的协议端口
socket出现解决了“TCP同时为多个应用程序进程提供并发服务的问题”
- socket的本质和实现:socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用==“打开open –> 读写write/read –> 关闭close”模式==来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
建立socket连接
建立Socket连接至少需要一对套接字,其中一个运行于客户端,
称为ClientSocket,另一个运行于服务器端,称为ServerSocket。
套接字之间的连接过程分为三个步骤:
1.服务器监听
2.客户端请求
3.连接确认
* 服务器监听
服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,
实时监控网络状态,等待客户端的连接请求。
* 使用socket()函数创建一个socket
int socket(int domain, int type, int protocol);
socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:
* **domain**:即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
* **type**:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等
* **protocol**:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议
* 调用bind()函数把一个地址族中的特定地址赋给socket
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合
函数的三个参数分别为:
* **sockfd**:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
* **addr**:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是:
* **addrlen**:对应的是地址的长度。
* 调用listen()函数将socket变为被动类型的,等待客户的连接请求
int listen(int sockfd, int backlog);
如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。
- 客户请求
-
客户端这时调用connect()发出连接请求
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
- 连接确认
-
服务端调用accept()函数取接收请求,这样连接就建立好了
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭
-
进行网络通信
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
服务器与客户已经建立好连接就可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信!网络I/O操作有下面几组:
- read()/write()
- recv()/send()
- readv()/writev()
- recvmsg()/sendmsg()
- recvfrom()/sendto()
断开连接
int close(int fd);
在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。
close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。
注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。
socket中TCP的三次握手建立连接
我们知道tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:
-
客户端向服务器发送一个SYN J
-
服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
-
客户端再向服务器发一个确认ACK K+1
-
只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:
image
图1、socket中发送的TCP三次握手
从图中可以看出,当客户端调用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返回,至此三次握手完毕,连接建立。
总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
3. HTTP协议
-
超文本传输协议,基于TCP的面向连接的协议
-
分为四个步骤:1)建立连接,2)发送请求,3)服务的相应请求,4)客户端接收服务器发回的数据并断开连接
-
响应状态码:
-
特点:
- 简单快速:因为HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
- 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
- HTTP 0.9和1.0使用非持续连接:限制每次连接只处理一个请求,服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
- HTTP 1.1使用持续连接:不必为每个web对象创建一个新的连接,一个连接可以传送多个对象。
-
请求方法:
- GET: 请求获取Request-URI所标识的资源
- POST: 在Request-URI所标识的资源后附加新的数据
- HEAD: 请求获取由Request-URI所标识的资源的响应消息报头
- PUT: 请求服务器存储一个资源,并用Request-URI作为其标识
- DELETE: 请求服务器删除Request-URI所标识的资源
- TRACE: 请求服务器回送收到的请求信息,主要用于测试或诊断
- CONNECT: 保留将来使用
- OPTIONS: 请求查询服务器的性能,或者查询与资源相关的选项和需求
4. HTTPS协议
-
HTTP的安全版,加入了SSL层
-
HTTPS通信过程:
-
HTTPS与HTTP的区别
- https协议需要到ca申请证书,一般免费证书很少,需要交费
- http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议
- http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443
- http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全
5.上面知识的总结
-
首先明确Socket在网络模型中哪里:是应用层与传输层之间的桥梁
-
OSI七层网络模型:
- 应用层.
- 表示层.
- 会话层.
- 传输层.
- 网络层.
- 数据链路层.
- 物理层
-
TCP/IP四层网络模型:应用层.传输层.网络层.网络接入层
-
HTTP协议:属于应用层面向对象的协议(超文本传输协议),常基于TCP连接方式, 特点是:
- 支持客户/服务端模式
- 简单快捷灵活
- 客户端发送的每次请求都需要服务器回送响应,请求结束后主动释放连接.俗称”短连接"
-
TCP协议:传输控制协议,提供面向连接.可靠的字节流服务,提供超时重发,丢弃重复数据,检验数据,流量控制等功能。在正式收发数据前,必须建立可靠的连接,即:三次握手.
-
UDP协议:用户数据报协议,面向非连接,不保证可靠性的数据传输服务,没有超时重发等机制,故而传输速度很快.
特点:它不与对方建立连接,而是直接就把数据包发送过去, UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境
-
Socket:又称”套接字”,应用程序通过”套接字”向网络发送请求或应答,它是一个针对TCP和UDP编程的接口,借助它建立TCP/UDP连接。socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉
-
HTTP协议—Socket连接--TCP连接关系:
- HTTP协议提供了封装或者显示数据的具体形式;
- Socket连接提供了网络通信的能力;
- TCP连接提供如何在网络中传输;
- socket是纯C语言的,跨平台;
- HTTP协议是基于socket的,底层使用的就是socket;
- 创建Socket连接时,可以指定使用的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。
-
TCP和UDP区别
- 基于连接和无连接
- 对系统资源要求(TCP较多,UDP较少)
- UDP程序结构较简单
- TCP是流模式,UDP是数据报模式
- 可靠性:TCP保证数据正确性,UDP可能丢包,不保证数据准确性
6. WebSocket协议
什么是WebSocket
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
为什么需要 WebSocket
-
HTTP 协议无法实现服务器主动向客户端发起消息
因为HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求 只能由客户端发起,服务端对请求做出应答处理。
这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)
image
-
WebSocket的出现是对上面问题的一个解决方案
WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。
[站外图片上传中...(image-5d4ed6-1533130904939)]
WebSocket的特点
-
最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
-
建立在 TCP 协议之上,服务器端的实现比较容易。
-
与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
-
数据格式比较轻量,性能开销小,通信高效。
-
可以发送文本,也可以发送二进制数据。
-
没有同源限制,客户端可以与任意服务器通信。
-
协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
ws://example.com:80/some/path
[站外图片上传中...(image-7622f3-1533130904939)]
WebSocket如何工作
在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
注意:Web浏览器和服务器都必须实现 WebSockets 协议来建立和维护连接。由于 WebSockets 连接长期存在,与典型的HTTP连接不同,对服务器有重要的影响。
基于多线程或多进程的服务器无法适用于 WebSockets,因为它旨在打开连接,尽可能快地处理请求,然后关闭连接。任何实际的 WebSockets 服务器端实现都需要一个异步服务器。
-
创建WebSocket对象
以下 API 用于创建 WebSocket 对象:var Socket = new WebSocket(url, [protocol] );
以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。
当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。 -
WebSocket 属性
以下是 WebSocket 对象的属性。假定我们使用了以上代码创建了 Socket 对象:属性 描述 Socket.readyState 只读属性 readyState 表示连接状态,可以是以下值: 0 - 表示连接尚未建立 1 - 表示连接已建立,可以进行通信 2 - 表示连接正在进行关闭 3 - 表示连接已经关闭或者连接不能打开 Socket.bufferedAmount 只读属性 bufferedAmount表示:已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数 -
WebSocket 事件
以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:事件 事件处理程序 描述 open Socket.onopen 连接建立时触发 message Socket.onmessage 客户端接收服务端数据时触发 error Socket.onerror 通信发生错误时触发 close Socket.onclose 连接关闭时触发 -
WebSocket 方法
以下是 WebSocket 对象的相关方法。假定我们使用了以上代码创建了 Socket 对象:方法 描述 Socket.send() 使用连接发送数据 Socket.close() 关闭连接 -
WebSocket 实例
WebSocket客户端iOS实现:facebook的框架SRWebSocket-
接口
@interface SRWebSocket : NSObject // 构造方法 - (instancetype)initWithURLRequest:(NSURLRequest *)request; // 打开之前设置代理 @property (nonatomic, weak) id <SRWebSocketDelegate> delegate; // Open - (void)open; // Close - (void)close; // Send a Data - (void)sendData:(nullable NSData *)data error:(NSError **)error; // Send a UTF8 String - (void)sendString:(NSString *)string error:(NSError **)error; @end
-
实现代理SRWebSocketDelegate
@protocol SRWebSocketDelegate <NSObject> @optional - (void)webSocketDidOpen:(SRWebSocket *)webSocket; - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithString:(NSString *)string; - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithData:(NSData *)data; - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error; - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(nullable NSString *)reason wasClean:(BOOL)wasClean; @end
-
7. MQTT协议
1. 什么是MQTT协议
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的“轻量级”通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用
2. MQTT协议的主要特征
MQTT协议工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:
-
使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合
这一点很类似于XMPP,但是MQTT的信息冗余远小于XMPP,,因为XMPP使用XML格式文本来传递数据
-
对负载内容屏蔽的消息传输
-
使用TCP/IP提供网络连接
主流的MQTT是基于TCP连接进行数据推送的,但是同样有基于UDP的版本,叫做MQTT-SN。 这两种版本由于基于不同的连接方式,优缺点自然也就各有不同了。
-
有三种消息发布服务质量QoS:
- 0 代表“至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
- 1 代表“至少一次”,确保消息到达,但消息重复可能会发生。
- 2 代表“只有一次”,确保消息到达一次。在一些要求比较严格的计费系统中,可以使用此级别。在计费系统中,消息重复或丢失会导致不正确的结果。这种最高质量的消息发布服务还可以用于即时通讯类的APP的推送,确保用户收到且只会收到一次。
-
小型传输,开销很小(固定长度的头部是2字节),协议交换最小化,以降低网络流量
这就是为什么在介绍里说它非常适合“在物联网领域,传感器与服务器的通信,信息的收集”,要知道嵌入式设备的运算能力和带宽都相对薄弱,使用这种协议来传递消息再适合不过了
-
使用Last Will和Testament特性通知有关各方客户端异常中断的机制
Last Will:即遗言机制,用于通知同一主题下的其他设备发送遗言的设备已经断开了连接。 Testament:遗嘱机制,功能类似于Last Will
3. MQTT协议原理
MQTT协议实现方式
实现MQTT协议需要客户端和服务器端协同完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者
MQTT传输的消息分为:主题(Topic)和负载(payload)两部分:
(1)Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload)
(2)payload,可以理解为消息的内容,是指订阅者具体要使用的内容。
网络传输与应用消息
MQTT会构建底层网络传输:它将建立客户端到服务器的连接,提供两者之间的一个有序的、无损的、基于字节流的双向传输。
当应用数据通过MQTT网络发送时,MQTT会把与之相关的服务质量(QoS)和主题名(Topic)相关连。
MQTT客户端
一个使用MQTT协议的应用程序或者设备,它总是建立到服务器的网络连接。客户端的行为有:
- 发布其他客户端可能会订阅的信息
- 订阅其它客户端发布的消息
- 退订或删除应用程序的消息
- 断开与服务器连接。
MQTT服务器
MQTT服务器以称为“消息代理”(Broker),可以是一个应用程序或一台设备。它是位于消息发布者和订阅者之间,它的行为有:
- 接受来自客户的网络连接
- 接受客户发布的应用信息
- 处理来自客户端的订阅和退订请求
- 向订阅的客户转发应用程序消息
MQTT协议中的订阅、主题、会话
-
订阅(Subscription)
订阅包含主题筛选器(Topic Filter)和最大服务质量(QoS)。订阅会与一个会话(Session)关联。一个会话可以包含多个订阅。每一个会话中的每个订阅都有一个不同的主题筛选器。 -
会话(Session)
每个客户端与服务器建立连接后就是一个会话,客户端和服务器之间有状态交互。会话存在于一个网络之间,也可能在客户端和服务器之间跨越多个连续的网络连接。 -
主题名(Topic Name)
连接到一个应用程序消息的标签,该标签与服务器的订阅相匹配。服务器会将消息发送给订阅所匹配标签的每个客户端。 -
主题筛选器(Topic Filter)
一个对主题名通配符筛选器,在订阅表达式中使用,表示订阅所匹配到的多个主题。 -
负载(Payload)
消息订阅者所具体接收的内容。
MQTT协议中的方法
MQTT协议中定义了一些方法(也被称为动作),来于表示对确定资源所进行操作。这个资源可以代表预先存在的数据或动态生成数据,这取决于服务器的实现。通常来说,资源指服务器上的文件或输出。主要方法有:
- Connect。等待与服务器建立连接。
- Disconnect。等待MQTT客户端完成所做的工作,并与服务器断开TCP/IP会话。
- Subscribe。等待完成订阅。
- UnSubscribe。等待服务器取消客户端的一个或多个topics订阅。
- Publish。MQTT客户端发送消息请求,发送完成后返回应用程序线程。
4. MQTT协议数据包结构
在MQTT协议中,一个MQTT数据包由:固定头(Fixed header)、可变头(Variable header)、消息体(payload)三部分构成。MQTT数据包结构如下:
- 固定头(Fixed header)。存在于所有MQTT数据包中,表示数据包类型及数据包的分组类标识。
- 可变头(Variable header)。存在于部分MQTT数据包中,数据包类型决定了可变头是否存在及其具体内容。
- 消息体(Payload)。存在于部分MQTT数据包中,表示客户端收到的具体内容。
MQTT固定头
固定头存在于所有MQTT数据包中,它包括:
-
固定报头的格式
[图片上传失败...(image-3a5de9-1533130904939)] -
MQTT数据包类型
位置:第1个字节,二进制位7-4。
表示为4位无符号值,这些值的定义见 表格 2.1 -控制报文的类型 -
标识位
位置:Byte 1中bits 3-0。这些值的定义见表格2.2
在不使用标识位的消息类型中,标识位被作为保留位。如果收到无效的标志时,接收端必须关闭网络连接:-
DUP:发布消息的副本。用来在保证消息的可靠传输,如果设置为1,则在下面的变长中增加MessageId,并且需要回复确认,以保证消息传输完成,但不能用于检测消息重复发送
-
QoS:发布消息的服务质量,即:保证消息传递的次数
Ø00:最多一次,即:<=1 Ø01:至少一次,即:>=1 Ø10:一次,即:=1 Ø11:预留
-
RETAIN: 发布保留标识,表示服务器要保留这次推送的信息,如果有新的订阅者出现,就把这消息推送给它,如果设有那么推送至当前订阅者后释放
-
MQTT可变头
MQTT数据包中包含一个可变头,它驻位于固定的头和负载之间。可变头的内容因数据包类型而不同,较常的应用是作为包的标识:
很多类型数据包中都包括一个2字节的数据包标识字段,这些类型的包有:PUBLISH (QoS > 0)、PUBACK、PUBREC、PUBREL、PUBCOMP、SUBSCRIBE、SUBACK、UNSUBSCRIBE、UNSUBACK。
Payload消息体
Payload消息体位MQTT数据包的第三部分,包含CONNECT、SUBSCRIBE、SUBACK、UNSUBSCRIBE四种类型的消息:
- (1)CONNECT,消息体内容主要是:客户端的ClientID、订阅的Topic、Message以及用户名和密码。
- (2)SUBSCRIBE,消息体内容是一系列的要订阅的主题以及QoS。
- (3)SUBACK,消息体内容是服务器对于SUBSCRIBE所申请的主题及QoS进行确认和回复。
- (4)UNSUBSCRIBE,消息体内容是要订阅的主题。
5. MQTT客户端的iOS实现
-
说明
MQTT基于订阅者模型架构,客户端如果互相通信,==必须在同一订阅主题下==,即都订阅了同一个topic,客户端之间是没办法直接通讯的。订阅模型显而易见的好处是群发消息的话只需要发布到topic,所有订阅了这个topic的客户端就可以接收到消息了
发送消息必须发送到某个topic,重点说明的是不管客户端是否订阅了该topic都可以向topic发送了消息,还有如果客户端订阅了该主题,那么自己发送的消息也会接收到 -
MQTTClient的使用
- 建立连接
和服务器端确定好MQTT服务器地址,端口号, 用户名, 密码, 订阅主题topic
/** host: 服务器地址 port: 服务器端口 tls: 是否使用tls协议,mosca是支持tls的,如果使用了要设置成true keepalive: 心跳时间,单位秒,每隔固定时间发送心跳包, 心跳间隔不得大于120s clean: session是否清除,这个需要注意,如果是false,代表保持登录, 如果客户端离线了再次登录就可以接收到离线消息 auth: 是否使用登录验证 user: 用户名 pass: 密码 willTopic: 订阅主题 willMsg: 自定义的离线消息 willQos: 接收离线消息的级别 clientId: 客户端id,需要特别指出的是这个id需要全局唯一,因为服务端是根据这个来区分不 同的客户端的,默认情况下一个id登录后,假如有另外的连接以这个id登录, 上一个连接会被踢下线, 我使用的设备UUID */ NSString *clientId = [UIDevice currentDevice].identifierForVendor.UUIDString; MQTTSessionManager *sessionManager = [[MQTTSessionManager alloc] init]; [sessionManager connectTo:@"121.199.19.126" port:1883 tls:false keepalive:60 //心跳间隔不得大于120s clean:true auth:true user:@"guest" pass:@"guest" will:false willTopic:nil willMsg:nil willQos:0 willRetainFlag:false withClientId:clientId]; sessionManager.delegate = self; self.sessionManager = sessionManager;
- 监控连接状态
连接当前状态,添加对应的回调接口,可以进行相关的业务逻辑处理。
// 添加监听状态观察者 [self.sessionManager addObserver:self forKeyPath:@"state" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil];
- 监听连接状态,进行相应处理
// 监听当前连接状态
- 建立连接
-
(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
switch (self.sessionManager.state) {
case MQTTSessionManagerStateClosed:
NSLog(@"连接已经关闭");
break;
case MQTTSessionManagerStateClosing:
NSLog(@"连接正在关闭");break; case MQTTSessionManagerStateConnected: NSLog(@"已经连接"); break; case MQTTSessionManagerStateConnecting: NSLog(@"正在连接中"); break; case MQTTSessionManagerStateError: { NSString *errorCode = self.sessionManager.lastErrorCode.localizedDescription; NSLog(@"连接异常 ----- %@",errorCode); } break; case MQTTSessionManagerStateStarting: NSLog(@"开始连接"); break; default: break;
}
}* 接收消息 实现MQTTSessionManagerDelegate代理方法,处理数据 ``` // 获取服务器返回数据 - (void)handleMessage:(NSData *)data onTopic:(NSString *)topic retained:(BOOL)retained { NSLog(@"------------->>%@",topic); NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"%@",dataString); // 进行消息处理 } ``` * 订阅和发送消息
// 订阅主题 NSDictionary类型,Object 为 QoS,key 为 Topic
self.sessionManager.subscriptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:MQTTQosLevelExactlyOnce] forKey:@"hello"];// 发送消息 返回值msgid大于0代表发送成功
NSString *msg = @"hahaha";
UInt16 msgid = [self.sessionManager sendData:[msg dataUsingEncoding:NSUTF8StringEncoding] //要发送的消息体
topic:@"hello" //要往哪个topic发送消息
qos:0 //消息级别
参考文章:MQTT - iOS实现
网友评论