注:本人适用于有socket基础和接触过XMPP的人一起探讨。不喜勿喷,纯属,闲了总结一下工作经验和小分享而已。
一。socket基础知识
TCP与UDP区别总结:
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。(其实用那个协议开发IM,对android开发人员来说,业务上的写法区别不大)
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;(anroid开发人员使用的是TCP/UDP封装后的Socket,个人看法,你就吹吧,还得从业务上降低消息丢失率和提高消息到达率以及消息即时性)
UDP尽最大努力交付,即不保证可靠交付。
Tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。(这些与我们使用socket一毛钱关系都没有,但是听起来很屌,目前IM是不能保证消息到达的先后顺序,因为后台传发消息是异步的。)
3、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。(知道就行了)
5、TCP对系统资源要求较多,UDP对系统资源要求较少。(这个真有影响,比如TCP假死状态,能发消息给后台,但是后台又认为你掉线了,把该发给你的消息存离线了,导致消息丢失,可以使用ping包较验长连接是否真正通了)
TCP的坑:
1.TCP半关闭:当TCP链接中A向B发送 FIN 请求关闭,另一端B回应ACK之后,并没有立即发送 FIN 给A,A方处于半连接状态(半开关),此时A可以接收B发送的数据,但是A已经不能再向B发送数据。
2.TCP半连接:发生在TCP三次握手中
如果A向B发起链接,B也按照正常情况响应了,但是A不进行三次握手,这就是半连接。
半连接攻击:半连接,会造成B分配的内存资源就一直这么耗着,直到资源耗尽。(SYN攻击)
3.TCP半打开:如果一方关闭或者异常关闭(断电,断网),而另一方并不知情,这样的链接称之为半打开。处于半打开的连接,如果双方不进行数据通信,是发现不了问题的,只有在通信是才真正的察觉到这个连接已经处于半打开状态,如果双方不传输数据的话,仍处于连接状态的一方就不会检测另外一方已经出现异常
解决方法:
如何解决半打开问题,引入心跳机制就可以察觉半打开。
如果需要发数据的话,这边收到之后 其实发现这个连接并不存在了,就会回复RST包告知,这个时候就需要重新建立连接了!
注意事项:
1.每次创建socket必须先关闭socket即socket.close()和关闭输入输出流mMessageWriterProtocol.shutdown();mMessageReaderProtocol.shutdown();
然后必须new Socket()和重新初始化输入输出流。
2.socket.connect(host,port)无返回值,就算服务器已经关了,也不报任何异常。
3.发送报文成功也不代表socket就真的通了,因为就算服务器已经关了,一样发送成功并且有成功回调,得看是否有服务器回应才能真正算是“长连接建立成功”。
4.只要服务器正常,安卓端的输入流肯定能接收到相应服务器推送过来的报文,而无需通过后台登录认证才能接收后台报文。可以这么理解输入流能接收后台报文socket就连接成功了;
能收到后台登录认证通过的报文,这才算是“长连接建立成功”。
MessageReaderProtocol
报文读取协议,即输入流,开个有条件的死循环不停的从后台“读”数据。
字节空间为100M,inputStream.read()是一个堵塞流
MessageWriterProtocol
报文写入协议,即输出流,开个有条件的死循环不停的给后台“写”数据。outputStream.write(imProtocol.getIMProtocolBytes());
outputStream.flush();
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="10100000007820083@company.com.cn" from="10100000007820101@company.com.cn/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="10100000007820083@company.com.cn" from="10400000011075731@conference.company.com.cn" 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>10100000007820101@company.com.cn</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="10100000007820083@pingan.com.cn" from="10400000011075731@conference.company.com.cn" 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>10100000007820101@company.com.cn</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次不成功当断开,重走登录,以达到“断线重连”的效果。