注:本人适用于有socket基础和接触过XMPP的人一起探讨。不喜勿喷,纯属,闲了总结一下工作经验和小分享而已。
一。socket基础知识
socket
•是一种抽象层,应用程序通过它来发送和接收数据,使用Socket可以将应用程序添加到网络中,与处于同一网络中的其他应用程序进行通信。
•简单来说,Socket提供了程序内部与外界通信的端口并为通信双方的提供了数据传输通道。
Socket的分类
根据不同的的底层协议,Socket的实现是多样化的。目前只介绍TCP/IP协议族的内容,在这个协议族当中主要的Socket类型为流套接字(streamsocket)和数据报套接字(datagramsocket)。
流套接字将TCP作为其端对端协议,提供了一个可信赖的字节流服务。
数据报套接字使用UDP协议,提供数据打包发送服务。
Socket 基本通信模型
TCP通信模型
UDP通信模型
基于TCP协议的Socket
•服务器端首先声明一个ServerSocket对象并且指定端口号,然后调用ServerSocket的accept()方法接收客户端的数据。Accept()方法在没有数据进行接收的处于堵塞状态。(SocketSocket=serverSocket.accept()),一旦接收到数据,通过inputStream读取接收的数据。
客户端创建一个Socket对象,指定服务器端的ip地址和端口号(SocketSocket=newSocket(”ip地址“,端口号);),通过inputStream读取数据,获取服务器发出的数据(OutputStream outputstream=socket.getOutputStream()),最后将要发送的数据写入到outputstream即可进行TCP协议的socket数据传输。
android 实现socket简单通信
客户端:
服务器端:
二。接下来再温习一下流行的xmpp的一般报文格式。
单聊(限时单聊):
<message to="[email protected]" from="[email protected]/moandroid" id="05s3J0tB-1" type="chat" channel="tcp">
<properties xmlns="http://www.jivesoftware.com/xmlns/xmpp/properties">
<property>
<name>topicId</name>
<value/>
</property>
<property>
<name>fromNick</name>
<value>昵称</value>
</property>
<property>
<name>createCST</name>
<value>1541385948024</value>
</property>
<property>
<name>msgType</name>
<value>0</value>
</property>
<property>
<name>contentType</name>
<value>0</value>
</property>
<property>
<name>privateLetterJid</name>
<value>null</value>
</property>
<property>
<name>origin</name>
<value>别名</value>
</property>
<property>
<name>originJid</name>
<value>10100000007820101</value>
</property>
<property>
<name>retransmit</name>
<value/>
</property>
</properties>
<body xmlns="paic:msg:extbody">回家</body>
</message>
群聊(限时群聊/会议):
<message to="[email protected]" from="[email protected]" id="Cp012UcV-2" type="groupchat" channel="tcp">
<properties xmlns="http://www.jivesoftware.com/xmlns/xmpp/properties">
<property>
<name>topicId</name>
<value/>
</property>
<property>
<name>fromNick</name>
<value>昵称</value>
</property>
<property>
<name>msgFrom</name>
<value>[email protected]</value>
</property>
<property>
<name>createCST</name>
<value>1541386213612</value>
</property>
<property>
<name>msgType</name>
<value>0</value>
</property>
<property>
<name>contentType</name>
<value>0</value>
</property>
<property>
<name>privateLetterJid</name>
<value>null</value>
</property>
<property>
<name>origin</name>
<value>别名</value>
</property>
<property>
<name>originJid</name>
<value>10100000007820101</value>
</property>
<property>
<name>retransmit</name>
<value/>
</property>
</properties>
<body xmlns="paic:msg:extbody">来到</body>
</message>
更新群聊名称:
<message to="[email protected]" from="[email protected]" id="d52f9ace-dbc7-4b5b-9adb-ad74edcef67b" type="normal" channel="tcp">
<notify xmlns="paic:msg:note">
<content>***设置了群名称</content>
<command>UPDATE_ROOMNAME</command>
<createcst>1541386325000</createcst>
<msgtime>1541386325422</msgtime>
<updatedby>[email protected]</updatedby>
<msgType>0</msgType>
<newroomname>测试92,测试24,测试3344</newroomname>
<msgType>0</msgType>
</notify>
</message>
这里收发消息借鉴XMPP的XML格式。如果想减少报文体积大小,也可使用JSON格式,前后台约定好就可以了。
XMPP有三种报文格式:<presence>、<message>和<iq>,本文章只存在<message>,不存在<presence>和<iq>报文,下面会讲述为何如何处理。
三。登录流程
用户通过账密登录拿到token,通过后台提供的IP地址和端口号,调用创建socket连接,连接成功后(后台不能拒绝连接,只能通过判断后面一定时间内收到token是否有效去断掉非法的连接),把token通过“登录报文”发给后台验证,后台验证通过后,保持连接状态,即不主动断掉该连接用户的“长连接”。
四。退登,或者被T
退登用户主动调用socket的close断长连接。
被T,用户在线收到被T报文,退到登录页。
被T,用户不在线,那么用户下次登录的时候,连上socket时,发token后,同样收到被T报文,手机退到登录页。
五。手机端发消息
直接调用socket的发消息即可,发成功之后,如果捕捉不到异常,就认为“本地发送成功”,然后处于“等待状态”,等后台发“回执”告诉手机端,你这条消息真的到服务器数据库了,那么这条消息才算“真正的发送成功”。
六。手机端收消息
调用socket的收消息方法,使用死循环包住该方法,拿到后台推送的消息报文,把该报文存到本地后通过UI更新界面,同时也发“回执”给服务器,告诉服务器,你不用再推那条消息给我了。如果后台在5分钟之内没收到“回执”把该条消息在存进“离线消息库”,用户登录的时候,告诉用户,你有离线消息需要拉取。
七。如何区分单聊/群聊/公众号?
from代表发送者,to代表接收者。
from或者to为"***@company.com.cn/moandroid",即为单聊。moandroid代表是安卓手机发送的。
from或者to为"***@conference.company.com.cn",即为群聊。
from或者to为"***@publicservice.company.com.cn",即为公众号。
然后里面的字段,任君自定义,比如fromNick为昵称。
八。后台如何给单聊/群聊/公众号发消息?
单聊:已经知道to的JID,通知soket推消息给该成员即可。
群聊:群组中的成员A发了条消息,后台通过数据库查到群组里面的所有人的JID,通过socket推消息给成员
公众号:同群聊。
综上所述,先有单聊再有群聊。
九。如何知道socket长连接被后台断了?
每分钟发一次ping的报文给后台,如果后台回复了,说明长连接还存在,没回复,那肯定是断了,重新看着登录流程把长连接建立起来。
后台如果一定时间内,比如10分钟没收到用户任何报文,即把该用户长连接断掉,不然开着浪费资源,也无意义了。
十。其它小问题,自问自答:
1.如何保证消息即时性?
答:Ping3次不成功就断线重连,保证长连接不断。
2.如何保证消息完整性,即不丢包?
答:服务端和客户端互发消息都需要收到对方回执才认可对方收到,没收到回执的放离线消息列表。
3.如何保证消息传输安全性?
答:加密消息报文,TCP在线收发消息加密,HTTP拉离线消息也加密。
4.有什么不足,丢包率和后台发包到达率?
答:肯定存在消息的即时性需要优化,更要降低消息的丢失率。
5.服务端和客户端如何感知对方是否在线?
答:客户端ping三次,如果收不到服务端回复就当做是断开。服务端如果N分钟收不到客户端的消息报文就当是断开。
6.为什么不用UDP,而使用TCP?
答:其实用那个都差不多。
7.报文如何保证有序性?
答:爆露给JAVA开发人员的socket并没有什么有序性可言。
8.离线消息什么时候拉取?
答:登录后有个字段会告诉你要不要拉离线消息。
9.断线重连?
答:ping3次不成功当断开,重走登录,以达到“断线重连”的效果。