本文主要介绍Android完整的Socket解决方案,并通过代码给大家一个实例分析。有兴趣的可以借鉴一下。
整体步骤流程
先说整体步骤:
发送UDP广播,众所周知,UDP广播的特点是整个网段内的所有设备都可以接收到这个消息。
接收方接收UDP广播,并将其ip地址和双方同意的端口号回复给UDP发送方。
当发送方获得对方的ip地址和端口号后,就可以发起TCP请求,建立TCP连接。
保持TCP心跳。如果发现对方不在了,超时后重复步骤1重新建立联系。
整体步骤和上面说的一样,下面是代码扩展:
搭建 UDP 模块
公共UDPSocket(上下文context) {
this.mContext=context
int cpuNumbers=runtime . get runtime()。available processors();
//根据CPU数量初始化线程池
mThreadPool=executors . newfixedthreadpool(cpuNumbers * Config。POOL _ SIZE);
//记录对象的创建时间
lastreeceivetime=system . current time millis();
messageReceiveList=new ArrayList();
Log.d(标签,'创建UDP对象');
//create user();
}
首先做一些初始化,准备线程池,记录对象的初始时间等等。
public void startUDPSocket() {
如果(客户端!=null)返回;
尝试{
//指示此套接字在设置的端口上侦听数据。
CLIENT=new datagram socket(CLIENT _ PORT);
client . setreuseaddress(true);
if (receivePacket==null) {
//创建接受数据的数据包
receive packet=new datagram packet(receive byte,BUFFER _ LENGTH);
}
startSocketThread();
} catch (SocketException e) {
e . printstacktrace();
}
}
然后创建一个真正的UDP套接字DatagramSocket。注意,这里传递的端口号CLIENT_PORT意味着这个DatagramSocket在这个端口号上接收消息。
/**
*打开线程发送数据。
*/
私有void startSocketThread() {
clientThread=新线程(new Runnable() {
@覆盖
公共无效运行(){
receive message();
}
});
isThreadRunning=true
client thread . start();
Log.d(标签,'打开UDP数据接收线程');
startheartbeatimer();
}
我们都知道Socket处理数据的发送和接收,发送和接收都是阻塞的,所以要放在一个子线程里。这里打开一个线程处理接收到的UDP消息(关于UDP模块的上一篇文章已经详细描述了,这里就不详细展开了)
/**
*处理收到的消息。
*/
私有void receiveMessage() {
while (isThreadRunning) {
尝试{
如果(客户端!=null) {
client.receive(接收数据包);
}
lastreeceivetime=system . current time millis();
Log.d(标签,'接收数据包成功.');
} catch (IOException e) {
Log.e(TAG,' UDP数据包接收失败!停止线程’);
stopUDPSocket();
e . printstacktrace();
返回;
}
if(receive packet==null | | receive packet . getlength()==0){
Log.e(标签,'无法接收UDP数据或者接收到的UDP数据为空');
继续;
}
String strReceive=new String(receive packet . get data()、receivePacket.getOffset()、receive packet . getlength());
Log.d(TAG,str receive ' from ' receive packet . get address()。getHostAddress()':' receive packet . get port());
//解析收到的json信息
notifyMessageReceive(strReceive);
//每次收到UDP数据后重置长度。否则,下一个接收的分组可能被截断。
if(接收数据包!=null) {
receive packet . setlength(BUFFER _ LENGTH);
}
}
}
UDP数据在子线程中接收,notifyMessageReceive方法通过接口通知消息。
/**
*发送到心跳包
*
* @param消息
*/
公共void sendMessage(最终字符串消息){
mThreadPool.execute(新的Runnable() {
@覆盖
公共无效运行(){
尝试{
BROADCAST _ IP=wifi util . getbroadcastaddress();
Log.d(标签,' BROADCAST _ IP:' BROADCAST _ IP ');
inet地址targetAddress=inet地址。按名称获取(BROADCAST _ IP);
数据报数据包=新的数据报数据包(消息。getbytes()、message.length()、targetAddress、CLIENT _ PORT);
客户端发送(数据包);
//数据发送事件
Log.d(标签,'数据发送成功');
} catch (UnknownHostException e) {
e。printstacktrace();
} catch (IOException e) {
e。printstacktrace();
}
}
});
}
接着开始心跳计时器开启一个心跳线程,每间隔五秒,就去广播一个用户数据报协议(用户数据报协议)消息。注意这里getBroadcastAddress是获取的网段ip,发送这个用户数据报协议(用户数据报协议)消息的时候,整个网段的所有设备都可以接收到。
到此为止,我们发送端的用户数据报协议(用户数据报协议)算是搭建完成了。
搭建 TCP 模块
接下来传输控制协议(传输控制协议)模块该出场了,UDP发送心跳广播的目的就是找到对应设备的互联网协议(互联网协议的缩写)地址和约定好的端口,所以在用户数据报协议(用户数据报协议)数据的接收方法里:
/**
* 处理用户数据报协议收到的消息
*
* @param消息
*/
私有void handleUdpMessage(字符串消息){
尝试{
JSON对象JSON对象=新JSON对象(消息);
字符串IP=JSON对象。optstring(配置.TCP _ IP);
字符串port=JSON对象。optstring(配置.TCP _ PORT);
如果(!TextUtils.isEmpty(ip)!TextUtils.isEmpty(port)) {
startTcpConnection(ip,port);
}
} catch (JSONException e) {
e。printstacktrace();
}
}
这个方法的目的就是取到对方UDPServer端,发给我的用户数据报协议(用户数据报协议)消息,将它的互联网协议(互联网协议的缩写)地址告诉了我,以及我们提前约定好的端口号。
怎么获得一个设备的互联网协议(互联网协议的缩写)呢?
公共字符串getLocalIPAddress() {
wifi信息wifi信息=mwifimanager。获取连接信息();
返回intto IP(wifi信息。getip地址());
}
私有静态字符串intToIp(int i) {
return (i0xFF)' . '((i 8)0xFF)' . '((i 16)0xFF)' . '
((I 24)0x ff);
}
现在拿到了对方的ip,以及约定好的端口号,终于可以开启一个传输控制协议(传输控制协议)客户端了。
私有布尔startTcpConnection(最终字符串ip,最终(同Internationalorganizations)国际组织端口){
尝试{
if (mSocket==null) {
mSocket=新套接字(ip,端口);
m插座。setkeepalive(true);
msocket。setcpnodelay(true);
msocket。setreuseaddress(true);
}
InputStream是=m套接字。getinputstream();
br=新缓冲读取器(新InputStreamReader(is));
输出流OS=m套接字。获取输出流();
pw=new PrintWriter(新缓冲写入器(新输出streamwriter(OS)),true);
Log.d(标记,' tcp创建成功.');
返回真实的
} catch(异常e) {
e。printstacktrace();
}
返回错误的
}
当传输控制协议(传输控制协议)客户端成功建立的时候,我们就可以通过传输控制协议(Transmission Control Protocol)套接字来发送和接收消息了。
细节处理
接下来就是一些细节处理了,比如我们的用户数据报协议(用户数据报协议)心跳,当传输控制协议(传输控制协议)建立成功之时,我们要停止用户数据报协议(用户数据报协议)的心跳:
if (startTcpConnection(ip,Integer.valueOf(port))) {//尝试建立传输控制协议(传输控制协议)连接
if (mListener!=null) {
m听众。成功时();
}
startReceiveTcpThread();
startheartbeatimer();
}否则{
if (mListener!=null) {
m听众。在失败(配置错误代码。创建_ TCP _错误);
}
}
//TCP已经成功建立连接,停止用户数据报协议(用户数据报协议)的心跳包。
public void stopfheartbeatimer(){
如果(定时器!=null) {
计时器。exit();
计时器=空
}
}
对传输控制协议(传输控制协议)连接进行心跳保护:
/**
* 启动心跳
*/
private void startHeartbeatTimer(){
if (timer==null) {
timer=新心跳计时器();
}
timer.setOnScheduleListener(新心跳计时器OnScheduleListener() {
@覆盖
公共计划无效(){
Log.d(标签,'计时器按计划运行.');
持续时间长=系统。当前时间毫秒()-上次接收时间;
Log.d(标签,' duration:' duration ');
如果(持续时间超时){//若超过十五秒都没收到我的心跳包,则认为对方不在线。
Log.d(标记,' tcp ping超时,对方已经下线');
stopTcpConnection();
if (mListener!=null) {
m听众。在失败(配置错误代码。PING _ TCP _超时);
}
} else If(duration heart beat _ message _ duration){//如果他超过两秒没有收到我的心跳包,再发一个。
JSON object JSON object=new JSON object();
尝试{
jsonObject.put(Config。消息,配置。平);
} catch (JSONException e) {
e . printstacktrace();
}
sendTcpMessage(JSON object . tostring());
}
}
});
timer.startTimer(0,1000 * 2);
}
首先,每隔两秒钟,我会给对方发一个ping包,看看对方是否在。如果超过15秒还没人接,说明对方断线了,关闭我的TCP端。输入onFailed方法。
@覆盖
公共void on失败(int错误代码){//TCP异常处理
开关(错误代码){
案例配置。错误代码。CREATE_TCP_ERROR:
打破;
案例配置。错误代码。PING_TCP_TIMEOUT:
UDP socket . startheartbeattimer();
tcpSocket=null
打破;
}
}
当TCP连接超时时,我将重新启动UDP广播心跳,并寻找等待连接的设备。进入下一步循环。
对于数据传输的格式等细节,这是和业务有关的。你自己决定吧。
你也可以根据你的业务模式开启不同的线程通道,无论是CPU密集型还是IO密集型。这就涉及到螺纹的知识了。
源代码共享:https://github.com/itsMelo/AndroidSocket
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。