接下来,针对 DingtalkChatbot 提供的信息发送方法进行讲解,这里根据信息类型进行划分。
from dingtalkchatbot.chatbot import DingtalkChatbotwebhook = "https://oapi.dingtalk.com/robot/send?access_token=c01697dd3c97efecd727491693a2ead2d668e8c5dabeb0c3604f545821fc72b7"xiaoq = DingtalkChatbot(webhook)xiaoq.send_link(title='监测预警,这里有一条重要信息', text='API接口字段异常', message_url="https://www.imooc.com", pic_url="https://www.imooc.com/static/img/column/icon.png")代码解释:通过 send_link () 方法发送带 Link 信息内容,其中 title 为标题内容,text 为简介内容,message_url 为 Link 跳转的链接地址,pic_url 为显示的图片地址。执行完成后,效果如下图所示。
from dingtalkchatbot.chatbot import DingtalkChatbotwebhook = "https://oapi.dingtalk.com/robot/send?access_token=c01697dd3c97efecd727491693a2ead2d668e8c5dabeb0c3604f545821fc72b7"xiaoq = DingtalkChatbot(webhook)xiaoq.send_markdown(title='监测预警', text='#### 北京天气\n' '> 9度,西北风1级\n' '> 空气良89\n' '> 相对温度73%\n', is_at_all=True)代码解释:通过 send_markdown () 方法发送 markdown 语法的信息,其中 title 为标题内容,text 为正文内容。执行完成后,效果如下图所示。
上一小节中结合 requests 完成了自定义机器人信息的发送,但在传递参数时,配置内容在使用较为繁琐,本小节将介绍一种使用更加便捷的方法来实现信息的发送。
客户端通过使用 send 函数向服务端发送数据,例如:ws.send("一段测试消息");可以发送文本格式,也可以发送二进制格式,例如:var input = document.getElementById("file"); input.onchange = function(){ var file = this.files[0]; if(!!file){ //读取本地文件,以gbk编码方式输出 var reader = new FileReader(); reader.readAsBinaryString(file); reader.onload = function(){ //读取完毕后发送消息 ws.send(this.result); } }}
得到 Webhook 地址后,就可以通过自定义机器人向钉钉群中发送消息了,目前发送消息的类型支持文本 (text)、链接 (link)、markdown (markdown)、ActionCard、FeedCard,大家可以根据自己的使用场景选择合适的消息类型。下面结合 requests 发送一条普通文本信息,代码如下所示。import jsonimport requestsHEADERS = {"Content-Type": "application/json;charset=utf-8"}url = "https://oapi.dingtalk.com/robot/send?access_token=c01697dd3c97efecd727491693a2ead2d668e8c5dabeb0c3604f545821fc72b7"data = { "msgtype": "text", "text": { "content": "监测预警"+"hello world" }, "isAtAll": True}response = requests.post(url, json.dumps(data), headers=HEADERS)代码解释:将 Webhook 地址保存在变量 url 中,作为 request 发起 post 请求时的请求地址,传递参数 data 中,其中 msgtype 为消息类型,“text” 为普通文本,配置项第二个 text 为发送的内容,isAtAll 为布尔值,是否 @ 所有人,如果设置为 False,可以通过 atMobiles,@指定人(atMobiles 处配置手机号码)。代码执行完成后,即完成了第一条钉钉群机器人的信息发送,效果如下图所示:
我们创建一个简单的 UDP 客户端程序,代码如下:import java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.InetSocketAddress;import java.net.SocketAddress;public class UDPClient { private static final int PORT = 9002; private static final String DST_HOST = "127.0.0.1"; private static final int RECV_BUFF_LEN = 1500; private static byte[] inBuff = new byte[RECV_BUFF_LEN]; public static void main(String[] args) { // 创建 UDP 客户端 Socket,选择无参构造方法,由系统分配本地端口号和网络接口 try (DatagramSocket udpClient = new DatagramSocket()){ // 构造发送的目标地址,指定目标 IP 和目标端口号 SocketAddress to = new InetSocketAddress(DST_HOST, PORT); while (true){ String req = "Hello Server!"; // 构造发送数据包,需要传入消息内容和目标地址结构 SocketAddress DatagramPacket message = new DatagramPacket(req.getBytes(), req.length(), to); // 发送消息 udpClient.send(message); System.out.println("Send UDP message:" + req + " to server:" + message.getSocketAddress().toString()); // 构造接收消息的数据包,需要传入 byte 数组 DatagramPacket inMessage = new DatagramPacket(inBuff, inBuff.length); // 接收消息 udpClient.receive(inMessage); System.out.println("Recv UDP message:" + new String(inMessage.getData(), 0, inMessage.getLength()) + " from server:" + inMessage.getSocketAddress().toString()); // 每隔 2 秒发送一次消息 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); } }}
from dingtalkchatbot.chatbot import DingtalkChatbot,CardItemwebhook = "https://oapi.dingtalk.com/robot/send?access_token=c01697dd3c97efecd727491693a2ead2d668e8c5dabeb0c3604f545821fc72b7"xiaoq = DingtalkChatbot(webhook)card1 = CardItem(title="监测预警-1", url="https://www.imooc.com", pic_url="https://www.imooc.com/static/img/column/icon.png")card2 = CardItem(title="监测预警-2", url="https://www.imooc.com", pic_url="https://www.imooc.com/static/img/column/icon.png")card3 = CardItem(title="监测预警-3", url="https://www.imooc.com", pic_url="https://www.imooc.com/static/img/column/icon.png")cards = [card1, card2, card3]xiaoq.send_feed_card(cards)代码解释:在 import 处导入 CardItem 后,通过 CardItem 初始化三条信息内容,包括标题、链接地址、图片地址,初始化完成后,调用 send_feed_card 方法进行信息发送。执行完成后,效果如下图所示。
在对 RabbitMQ 的整体架构有一个宏观了解之后,我们还需要对 RabbitMQ 的消息发送原理也有所了解,知道消息在 RabbitMQ Server 是怎样流转的。同样地,RabbitMQ 消息的发送原理也是基于 AMQP 协议中消息的发送原理,结合 AMQP 消息的发送原理(同学们不需要知道),我们可以得出 RabbitMQ 消息的发送原理。我们先来看一下,结合 RabbitMQ 整体架构而得出的 RabbitMQ 消息发送原理是怎样的,如下图所示:由此图,我们可以得出 RabbitMQ 消息发送的步骤:第一步,生产者将消息生产出来,并将消息发送到 RabbitMQ Server 上,即我们发到 RabbitMQ 中的消息,会首先置于 RabbitMQ Server 中;第二步,RabbitMQ Server 根据客户端所发来的连接请求,判断将消息传递到哪个 Virtual Host 中,如果我们在连接 RabbitMQ Server 时,没有设置要连接的 Virtual Host 地址,则 RabbitMQ Server 会将我们的消息传递到地址为 “/” 的 Virtual Host 中去;第三步,在将消息传递到对应的 Virtual Host 中后,Virtual Host 会继续解析我们的连接请求,并在这一步解析出我们需要的 Exchange 的类型,以及 Channel 的名称,Queue 的名称,以及消息和 Exchange 之间是否有 routing_key ,Channel 和 Queue 之间是否有 bidding_key 这些信息;第四步,Virtual Host 会根据解析出来的这些信息,将消息和 Exchange 进行匹配,相应的,Exchange 也会和对应的 Channel 进行匹配,并最终将 Queue 和 Channel 进行绑定,使消息进入到对应的消息队列中去;第五步,待消息进入到对应的消息队列中之后,RabbitMQ Server 会返回给我们一个确认应答(确认应答后续会进行介绍),来通知我们,消息已经成功被 RabbitMQ Server 所发送,于是,消费者变回根据一定的策略来从消息队列中获取消费,并最终将该消息消费掉,消息消费之后,也会给我们返回一个确认应答(确认应答后续会进行介绍),告诉我们消息已经成功消费掉了。以上就是 RabbitMQ 进行消息发送的先后步骤,为了更直观地为各位同学呈现 RabbitMQ 的消息发送原理,我做了一个流程图给大家,如下图所示:同学们可以根据上述步骤,结合流程图进行学习和验证。
我们创建一个简单的 UDP 服务端程序,代码如下:import java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;public class UDPServer { private static final int BIND_PORT = 9002; private static final String BIND_HOST = "127.0.0.1"; private static final int RECV_BUFF_LEN = 1500; private static byte[] inBuff = new byte[RECV_BUFF_LEN]; public static void main(String[] args) { // 构造服务器 Socket,绑定到一个固定的端口,监听的 IP 是 0.0.0.0 try (DatagramSocket udpServer = new DatagramSocket(BIND_PORT)) { // 构造接收消息的数据包,需要传入 byte 数组。 // 我们将这条语句放在循环外,不需要每次消息收发都构造此结构 DatagramPacket inMessage = new DatagramPacket(inBuff, inBuff.length); while (true){ // 接收客户端消息 udpServer.receive(inMessage); System.out.println("Recv UDP message:" + new String(inMessage.getData(), 0, inMessage.getLength()) + " from Client:" + inMessage.getSocketAddress().toString()); String rsp = "Hello Client!"; // 构造发送的消息结构 // 注意!!!对于服务器来说,发送的目标地址一定是接收消息时的源地址,所以从 inMessage 结构获取 DatagramPacket message = new DatagramPacket(rsp.getBytes(), rsp.length(), inMessage.getSocketAddress()); // 发送消息 udpServer.send(message); System.out.println("Send UDP message:" + rsp + " to Client:" + message.getSocketAddress().toString()); // 重置接收数据包消息长度,准备接收下一个消息 inMessage.setLength(inBuff.length); } } catch (IOException e) { e.printStackTrace(); } }}
对于消息发送模式这一名词,我们先抛开 RabbitMQ 不说,单从字面意义上去理解,很容易知道,消息发送模式指的就是:消息发送时,所使用的方法或者中间介质,换成大白话就是说,消息是通过什么媒介去进行发送的。在 RabbitMQ 中,消息发送模式我们完全可以按照上述所说的来理解,只不过,在 RabbitMQ 中,对消息传输所通过的媒介有专业的术语罢了。接下来就让我们来看一下,在 RabbitMQ 中都有哪些消息发送模式。
可能发生消息丢失的场景:网络故障。网络环境的不可靠导致消息发送失败,例如网络丢包、网络故障。数据在网络中传输会经过诸多网络设备,只要其中一个网络链接在数据抵达前已经流量满载,新到的数据将会阻塞一段时间段。另外比较少见的例子是施工挖断光纤或者其他原因导致硬件层面的长时间不可用。参考解决方案是使用AMQP协议的事务机制。生产者在发出消息之后,消息是否到达RabbitMQ服务器是默认不可知的,所以在生产者发送消息之前,调用channel.txSelect 语句开启事务,如果消息发送失败,那么调用channel.txRollback回滚事务,重新发送一条消息;如果消息发送成功,那么调用channel.txCommit提交事务。采用事务的缺点是增加耗时,会降低RabbitMQ的吞吐性能。所以RabbitMQ还有一种性能改进方案,即Confirm机制,步骤如下:(1)生产者调用channel.confirmSelect将通信方式设置为confirm模式;(2)生产者发送的所有消息都会被分配一个唯一 ID;(3)当生产者发送的消息成功投递到队列之后,RabbitMQ会发送一个确认给生产者,生产者即得知这条消息已经成功发送。
客户端请求private void sendMsgToGroup(Scanner scanner,Channel channel){ System.out.println("请输入群组ID:"); int groupId=scanner.nextInt(); System.out.println("请输入发送消息内容:"); String msg=scanner.next(); Integer userId=(Integer) channel.attr(AttributeKey.valueOf("userid")).get(); GroupSendMsgReqBean bean=new GroupSendMsgReqBean(); bean.setFromuserid(userId); bean.setTogroupid(groupId); bean.setMsg(msg); channel.writeAndFlush(bean);}服务端处理public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter { private static Map<Integer, Channel> map=new HashMap<Integer, Channel>(); private static Map<Integer, Group> groups=new HashMap<Integer, Group>(); private void sendMsg(GroupSendMsgReqBean bean,Channel channel){ GroupSendMsgResBean res=new GroupSendMsgResBean(); //1.根据“群组ID”获取对应的“组信息” Group group=groups.get(bean.getTogroupid()); //2.给“发送人”响应,通知其发送的消息是否成功 if(group==null){ res.setCode(1); res.setMsg("groupId="+bean.getTogroupid()+",不存在!"); channel.writeAndFlush(res); return; }else{ res.setCode(0); res.setMsg("群发消息成功"); channel.writeAndFlush(res); } //3.根据“组”下面的“成员”,变量并且逐个推送消息 List<GroupMember> members=group.getMembers(); for(GroupMember gm:members){ GroupRecMsgBean rec=new GroupRecMsgBean(); rec.setFromuserid(bean.getFromuserid()); rec.setMsg(bean.getMsg()); gm.getChannel().writeAndFlush(rec); } }}
只需要配置常规的邮件收发信息即可。实例:# 网易邮箱发件服务器spring.mail.host=smtp.163.com# 网易邮箱发件端口spring.mail.prot=25# 发件人账号spring.mail.username=taqsxxkj@163.com# 发件授权密码,注意授权码是用于登录第三方邮件客户端的专用密码spring.mail.password=123456spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory# Spring Boot Admin 发件收件信息spring.boot.admin.notify.mail.from=taqsxxkj@163.comspring.boot.admin.notify.mail.to=taqsxxkj@163.comspring.boot.admin.notify.mail.cc=taqsxxkj@163.com此处特别注意发件授权密码不是普通邮箱的登录密码,而是授权密码,以网易邮箱为例在下图位置设置。网易邮箱授权密码设置
from dingtalkchatbot.chatbot import DingtalkChatbotwebhook = "https://oapi.dingtalk.com/robot/send?access_token=c01697dd3c97efecd727491693a2ead2d668e8c5dabeb0c3604f545821fc72b7"xiaoq = DingtalkChatbot(webhook)xiaoq.send_text(msg='监测预警,大家好,我是小Q', is_at_all=True)代码解释:首先通过 DingtalkChatbot () 方法初始化机器人,通过 send_text () 方法发送普通文本信息,msg 为发送的内容,is_at_all 为是否 @全员。执行完成后,效果如下图所示。
UDP 又叫用户数据包协议,相对于 TCP,它是一种面向无连接的协议,也就是通信双方在交换数据之前无需建立一条专用通道,当然在通信结束前也无需释放通道。这样一来,通信的效率非常高,但缺点是我们无法确定发出去的消息对方是否能够准确收到,所以它是一个轻量不可靠的通信方式。以上的定义描述了二者主要的差异,更多细致的内容可以参考其他资料。
面试官提问: 上述你提到了 UDP 和 TCP 报文,它们的具体结构是怎样的?题目解析:在上个题目中我们总结了 TCP 协议和 UDP 协议的不同点,其中谈到了 TCP 和 UDP 协议首部格式不同,接下来分别画图分析。 (UDP 报文首部) 如上图可见,UDP 首部只有 8 个字节的数据,包括源端口号、目标端口号、长度以及校验和。源端口号:发送计算机的应用端口;目标端口号:接收端计算机的接收端口,也是占用 16 位 Bit;长度:表示 UDP 报文首部以及携带数据的长度;校验和:校验数据在传输过程中是否损坏。 (TCP 报文首部)在画完了示意图之后,关于 TCP 报文首部,我们需要解释的字段:序号:对字节流编号,例如本次传输的序号是 100,携带的数据长度是 100 字节,那么下次传输的序号就是 200;确认号:客户端 A 往服务器端 B 发送了一个报文,序号是 100,携带的数据长度是 100 字节,那么 B 往 A 发送的报文中确认号就是 200,表示期望收到的下一个报文的序号。标志位 CWR(Congestion Window Reduce):拥塞窗口减少标志;标志位 ECE(ECN Echo):ECE 标志等于 1 时,通知接收方,表示接收方到这边的网络存在拥塞;标志位 URG(Urgent):本报文是否包含紧急数据,只有当 URG=1 时,"校验和" 后面的 "紧急指针" 字段才有效;标志位 ACK(Acknowledgement):ACK=1 则表示前面发送的确认号是否有效,TCP 连接建立之后,ACK 必须设置为 1;标志位 PSH(Push):PSH 设置为 1 则表示需要将收到的数据立即传输给上层应用,否则先放缓存;标志位 RST(Reset):RST 设置为 1 则表示 TCP 连接出现异常,需要强制断开;标志位 SYN(Synchronize):SYN 设置为 1 则表示希望建立连接;标志位 FIN(Finsish):FIN 设置为 1 则表示数据已经发送完成,可以断开 TCP 连接。上述定义中,序号、确认号以及 ACK、SYN 和 FIN 标志位是我们需要重点关注的部分,因为在 TCP 建立连接和断开连接时会涉及到。
每个客户端和服务端建立连接的时候,必须把个人用户信息上传到服务端,由服务端统一保存映射关系,如果某个客户端下线了,则服务端监听到连接断开,删除对应的映射关系。其次,发起群聊的时候,需要传递 touser 字段,服务端根据该字段在映射表里面查找到对应的连接通道并发起消息推送。
通过设置 HTTP 的 Set-Cookie 消息头,Web 服务器将 Cookie 发送给浏览器。Set-Cookie 消息的格式如下面所示,括号中的部分都是可选的:Set-Cookie:value [ ;expires=date][ ;domain=domain][ ;path=path][ ;secure]消息头的第一部分,value 部分,通常是一个 name=value 格式的字符串。服务端向客户端发送的 HTTP 响应中设置 HTTP 的 Set-Cookie 消息头,一个具体的例子如下:Connection:keep-aliveContent-Type:text/plainDate:Fri, 14 Jul 2017 10:49:23 GMTSet-Cookie:user=ZhangSanTransfer-Encoding:chunked在这个例子中,服务端向客户端发送的 HTTP 消息头中,设置了 ‘Set-Cookie:user=ZhangSan’,客户端浏览器将接受到字符串 ‘user=ZhangSan’ 作为 Cookie,下次访问网站时,浏览器会将该 Cookie 发送给服务端。
发送邮件如下代码所示:import zmailmail = { 'subject': '你有一封新的邮件!', 'content_text': '测试内容!'}server = zmail.server('xxxxxx@qq.com', 'xlogucqphohxcabi')server.send_mail("xxxxxx@163.com", mail)代码解释:通过 zmail.server 构建与邮件服务器通信的 MailServer 对象,构建完成后,通过 send_mail () 方法发送邮件,第一个参数为接收邮件的邮箱,第二个参数为构建好的邮件内容。代码执行完成后,如下图所示。
UDP 是面向数据报的传输协议。UDP 的包头非常简单,总共占用 8 字节长度,格式如下:+--------------------+--------------------+| 源端口(16 bits) | 目的端口(16 bits) |+--------------------+--------------------+| 包的长度(16 bits) | 检验和(16 bits) |+--------------------+--------------------+源端口号:占用 2 字节长度,用于标识发送端应用程序。目的端口:占用 2 字节长度,用于标识接收端应用程序。包的长度:表示 UDP 数据包的总长度,占用 2 字节长度。包的长度的值是 UDP 包头的长度加上 UDP 包体的长度。 包体最大长度是 65536-8 = 65528 字节。提示:网络层的 IPv4 Header 也包含了 Length 字段,IPv4 Payload 的最大长度是 65536-60 = 65476 字节。如果我们控制 UDP 数据包总长度不超过 65476 字节,UDP Header 其实是不需要 UDP Length 字段的。因为在实际开发中,程序员会保证传给 UDP 的数据长度不超过 MTU 最大限度,所以 UDP Length 字段显得有点儿多余。检验和:占用 2 字节长度。UDP 检验和是用于差错检测的,检验计算包含 UDP 包头和 UDP 包体两部分。从 UDP 的协议格式可以看出,UDP 保证了应用层消息的完整性,比如,UDP 客户端向服务器发送 “Hello Server,I’m client。How are you?”,UDP 客户端发送的是具有一定含义的数据,UDP 服务端收到也是这个完整的消息。不像面向字节流的 TCP 协议,需要应用程序解析消息。为此,UDP 编程会简单很多。
在 RabbitMQ 中,所有经过 RabbitMQ 来传输的消息,都需要经过 RabbitMQ 的队列来进行传输,至于什么是队列,我在前面的文章中已经讲过,这里不再赘述。在介绍 RabbitMQ 中都有哪些消息发送模式之前,我们需要首先了解,在 RabbitMQ 中的消息发送模式是如何体现的。消息在 RabbitMQ 队列传输的过程中,根据不同的传输方式,以及所使用的队列种类的不同,一共划分了 5 个消息传输模式,而这 5 个消息传输模式,就是我们所说的消息发送模式。根据 RabbitMQ 所实现的消息投递方式来划分,可以将消息发送模式分为两大类,分别是点对点模式、发布订阅模式;根据 RabbitMQ 所采用的队列方式以及匹配规则的不同,可以将消息发送模式分为五大类,分别是普通队列模式、工作队列模式、发布订阅模式、直接模式、主题模式。由于按照消息投递方式所划分的范围较广,我们不能充分了解每个消息发送模式的内容,所以,在介绍消息发送模式时,我会按照 RabbitMQ 所采用的队列方式和匹配规则的不同来进行讲解,请同学们做好准备。
面试官提问: TCP 协议和 UDP 协议有什么区别?有什么共同点?题目解析:相同点:两个协议最大的共同点是都位于 TCP/IP 网络模型的传输层。不同点:我们通过表格的形式对比不同。TCP(Transmission Control Protocol,传输控制协议)UDP(User Datagram Protocol,用户数据报协议)是否连接面向连接无连接传输方式面向字节流:直接以字节流形式传输面向报文:对于应用程序交付的数据,添加首部之后就交付给 IP 层首部格式 20 个字节的固定首部只有 8 个字节是否可靠可靠传输,依靠流量控制和拥塞控制不可靠传输连接对象个数一对一连接支持一对一(点到点),一对多以及多对多传输适用场景要求可靠传输的场景,例如发送邮件和传输文件对可靠性要求低,效率要求高的场景,例如 QQ 的视频通话根据表格中的特点对比我们可以总结得到:TCP 协议面向连接并且可靠,UDP 协议无连接并且不可靠;虽然 UDP 协议不可靠,但是在对数据完整性要求低,对传输速度要求高的场景,可以适用 UDP 协议。
信号(Signal)是 Unix 系统中就已有的 IPC 方式,继承于 Unix 的 Linux 系统和 MacOS 系统也具有相同的通信方式。信号的工作原理是向某个进程发送特定的消息,目标进程在收到消息之后,就知道特定事件已经发生,此时进程可以忽略消息即不做处理,或者是处理消息调用固定的函数。以 MacOS 为例,在 shell 终端输入 kill -l 可以列出支出的全部信号名称:HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM URG STOP。MacOS 支持的信号列表
客户端收到服务端的 Cookie 后,该 Cookie 会在接下来的每个请求中被发送至服务器。Cookie 的值被存储在名为 Cookie 的 HTTP 消息头中,并且只包含了 Cookie 的值,其它的选项全部被去除。客户端向服务端发送的 HTTP 请求中设置 Cookie 消息头,一个具体的例子如下:Connection:keep-aliveCookie:user=ZhangSanHost:localhost:8080User-Agent:Mozilla/5.0 AppleWebKit/537.36 Chrome Safari在这个例子中,客户端向服务端发送的 HTTP 消息头中,设置了 ‘Cookie:user=ZhangSan’,服务端接受到字符串 ‘user=ZhangSan’ 作为 Cookie,从而确认此次请求对应的用户。
交互过程中,发送请求是第一步。那么,我们将如何构造一个请求呢?这一章节,我们将一步一步来构建一个 Ajax 请求。学习本节,你将学会:如何通过 XMLHttpRequest 和 ActiveXObject 来构造一个通用的 xhr 对象。如何通过 xhr 对象来发送 GET、 POST 等请求。Content-type 在 Ajax 数据发送中的作用。那么,接下来让我们进入本节的学习吧。
客户端每隔 5 秒钟发送一次消息给服务端,查看效果如何?实例:final ChannelFuture channelFuture=bootstrap.connect("127.0.0.1",80).sync();channelFuture.addListener(new ChannelFutureListener() { public void operationComplete(ChannelFuture future) throws Exception { if(future.isDone()){ if(future.isSuccess()){ //1秒钟之后,每隔5描述发送一次消息 channelFuture.channel().eventLoop().scheduleWithFixedDelay(new Runnable() { public void run() { channelFuture.channel().writeAndFlush("hello world"); } },1,5, TimeUnit.SECONDS); }else if(future.isCancelled()){ System.out.println("连接被取消"); }else if(future.cause()!=null){ System.out.println("连接出错:"); } } }});执行结果:handlerAddedchannelRegisteredchannelActivechannelReadchannelReadComplete channelReadchannelReadComplete channelReadchannelReadComplete通过执行结果我们发现,第一次的时候执行 handlerAdded ()、channelRegistered ()、channelActive (),后面就不会被执行了。客户端的每次请求时,都会触发 channelRead () 和 channelReadComplete () 两个核心方法。
如果记录数量很多,服务器不可能都将它们返回给用户。API 应该提供参数,过滤返回结果。比如,我们想获取全校师生的个人信息,如果将这些信息一股脑地全部展示在网页上,是不明智也是不现实的。如果数据量太大,在实际开发中我们会采用分页展示的形式。另外,如果想在一次考试后,按照成绩高低展示学生信息,那么可以通过过滤信息来实现。所谓过滤,就是在 URL 中添加一下限制参数。下面是一些常见的参数。?limit=10:指定返回记录的数量?offset=10:指定返回记录的开始位置。?page=2&per_page=100:指定第几页,以及每页的记录数。?sortby=score&order=asc:指定返回结果按照学生的成绩(score)正序(asc)排列顺序。参数的设计允许存在冗余,即允许 API 路径和 URL 参数允许有重复。比如,想要查询某个班级所有学生信息,我们可以设计 GET /classes/ID/students 与 GET /students?class_id=ID 两种地址,任何一种都可得到相同的结果。
Phone 设置项中可以模拟来电和短信:From:选择或输入电话号码;CALL DEVICE:点击来电;HOLD CALL:点击保持通话;END CALL:点击结束通话;SMS message:输入模拟消息的正文内容;SEND MESSAGE:点击发送短信。
如果状态码是 4xx,服务器就应该向用户返回出错信息。一般来说,返回的信息是键值对形式的数据,将 error 作为键名,出错信息作为键值即可。比如,在一个提供查询学生信息的 API 中,要求客户端提供正确的 API key(可以理解为输入了正确的用户名和密码)才能访问,如果提供的 API key 不正确,此时服务器应拒绝访问,并返回错误信息。这样,客户端就知道了为何没能查到信息,修改成正确的 API key 即可。{ error: "Invalid API key"}