如何干净的在服务中实现socket长链接与服务器通信并处理相应的线程问题(有更新)
来源:互联网 发布:java if 大小写 编辑:程序博客网 时间:2024/05/18 03:32
注:本文部分代码改编自csdn某作者,若您觉得侵权,请与我联系。
在我的上一篇文章中,简单了讲解了socket通信在客户端与服务器的大概思路。但是,在实际应用中,问题会变得复杂的多。如安卓端socket应该如何进行长链接,如何处理线程问题,如何保证连接一直都在,长链接在后台是如何运行的。这一系列问题必须通过一系列的实践才能得到解决。下面的就讲讲我的一些经验。
先附客户端的源码和服务器源码(用myeclipse搭建了一个简单的服务器),在代码后面会详细讲解各种注意点!
PS。希望各位不惧麻烦能将代码实际的跑一遍,加深理解。也防止因为我自己的疏忽而误导大家。
SocketService:(由于是在本人的项目上进行的实验,请忽略广播部分)
import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.lang.ref.WeakReference;import java.net.Socket;import java.net.UnknownHostException;import java.util.Arrays;import android.annotation.SuppressLint;import android.app.Service;import android.content.Intent;import android.os.Binder;import android.os.Handler;import android.os.IBinder;import android.os.RemoteException;import android.util.Log;public class SocketService extends Service { private static final String TAG = "BackService"; /** 心跳检测时间 */ private static final long HEART_BEAT_RATE = 3 * 1000; /** 主机IP地址 */ private static final String HOST = "10.0.2.2"; /** 端口号 */ public static final int PORT = 9898; /** 消息广播 */ public static final String MESSAGE_ACTION = "com.message_ACTION"; private boolean isSuccess=false;//针对客户端主动断开连接 private boolean isconnected=false; //针对服务器,如果服务器主动断开链接,为false private long current=0L;//表示服务器主动断开时间 private long sendTime = 0L; /** 弱引用 在引用对象的同时允许对垃圾对象进行回收 */ private WeakReference<Socket> mSocket; private ReadThread mReadThread; private MyBackService iBackService = new MyBackService(); public class MyBackService extends Binder{ public boolean sendMessage(String message) { return sendMsg(message); } }; @Override public IBinder onBind(Intent arg0) { return (IBinder) iBackService; } @Override public void onCreate() { super.onCreate(); new InitSocketThread().start(); } public void onDestroy(){ super.onDestroy(); mHandler.removeCallbacks(heartBeatRunnable); Log.d("SocketService","end Service"); } // 发送心跳包 private Handler mHandler = new Handler(); private Runnable heartBeatRunnable = new Runnable() { //心跳一直在后台跑,防止主动断线和被动断线!!! @Override public void run() { if (System.currentTimeMillis() - sendTime >= HEART_BEAT_RATE) { Log.d("SocketService","heartbear is running"); isSuccess = sendMsg("heartbeat");// 就发送一个\r\n过去, 如果发送失败,就重新初始化一个socket if (System.currentTimeMillis()-current>=10*HEART_BEAT_RATE) isconnected=false;//如果当前时间超过服务器断开时间时长为心跳频率的十倍,则重新连接 if (!isSuccess||!isconnected) { mReadThread.release(); releaseLastSocket(mSocket); mHandler.removeCallbacks(heartBeatRunnable); Log.d("SocketService","重连1"); new InitSocketThread().start(); Log.d("SocketService","重连2"); } } mHandler.postDelayed(this,HEART_BEAT_RATE); // stopSelf();//是否需要在杀进程后保持心跳重连机制,需要的话去除此行代码 } }; public boolean sendMsg(String msg) { if (null == mSocket || null == mSocket.get()) { Log.d("SocketService","掉线"); return false; } Socket soc = mSocket.get(); if(soc.isClosed()||!soc.isConnected()||soc.isInputShutdown()||soc.isClosed()||soc.isOutputShutdown()){ Log.d("SocketService","socket连接客户端主动断开连接"); return false; } try { if (!soc.isClosed() &&!soc.isOutputShutdown()) { final OutputStream os = soc.getOutputStream(); final String message = msg + "\n"; new Thread(new Runnable() { @Override public void run() { try{ os.write(message.getBytes()); os.flush(); Log.d("SocketService","send successfully"); }catch (IOException e){ isconnected=false; } } }).start(); sendTime = System.currentTimeMillis();// 每次发送成功数据,就改一下最后成功发送的时间,节省心跳间隔时间 Log.i(TAG, "发送成功的时间:" + sendTime); return true; } } catch (IOException e) { e.printStackTrace(); return false; } return false; } // 初始化socket private void initSocket() throws UnknownHostException, IOException { Socket socket = new Socket(HOST, PORT); if (socket.isConnected()&&!socket.isClosed()){ //防止初始化时断线 current=System.currentTimeMillis(); isconnected=true; } mSocket = new WeakReference<Socket>(socket); mReadThread = new ReadThread(socket); mReadThread.start(); mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);// 初始化成功后,就准备发送心跳包 //mHandler.removeCallbacks(heartBeatRunnable); } // 释放socket private void releaseLastSocket(WeakReference<Socket> mSocket) { try { if (null != mSocket) { Socket sk = mSocket.get(); if (!sk.isClosed()) { sk.close(); } sk = null; mSocket = null;
isconnected=false;
} } catch (IOException e) { e.printStackTrace(); } } class InitSocketThread extends Thread { @Override public void run() { super.run(); try { initSocket(); Log.d("SocketService","init success"); //mHandler.removeCallbacks(heartBeatRunnable); // //mHandler.postDelayed(heartBeatRunnable,HEART_BEAT_RATE); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } public class ReadThread extends Thread { private WeakReference<Socket> mWeakSocket; private boolean isStart = true; public ReadThread(Socket socket) { mWeakSocket = new WeakReference<Socket>(socket); } public void release() { isStart = false; releaseLastSocket(mWeakSocket); } @SuppressLint("NewApi") @Override public void run() { super.run(); Socket socket = mWeakSocket.get(); if (null != socket) { try { InputStream is = socket.getInputStream(); byte[] buffer = new byte[1024 * 4]; int length = 0; if(is.read()==-1) isStart=false; while (!socket.isClosed() && !socket.isInputShutdown() && isStart && ((length = is.read(buffer)) != -1)) { if (length > 0) { String message = new String(Arrays.copyOf(buffer, length)).trim(); Log.d(TAG, "收到服务器发送来的消息:"+message+"hahaha"); Log.d("123456",message); // 收到服务器过来的消息,就通过Broadcast发送出去 if (message!=""){ if (message.equals("ok")) {// 处理心跳回复 Log.d("SocketService","心跳正常"+message); current=System.currentTimeMillis(); } else { // 其他消息回复 Intent intent = new Intent(MESSAGE_ACTION); intent.putExtra("message", message); sendBroadcast(intent); //接下来的工作,定义出一个json格式,对message进行解析,判断类型,发送特定广播 Log.d("SocketService","hellohello"); //没有断线后心跳一直运行,直到再次连接,掉线期间不应该进行任何网络请求 } } } } } catch (IOException e) { e.printStackTrace(); } } } }}
MainActivity:
import android.app.Notification;import android.app.NotificationManager;import android.content.BroadcastReceiver;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.content.IntentFilter;import android.content.ServiceConnection;import android.graphics.BitmapFactory;import android.os.Handler;import android.os.IBinder;import android.os.Message;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.support.v7.app.NotificationCompat;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.CheckBox;import android.widget.EditText;import android.widget.ImageView;import com.bumptech.glide.Glide;import java.io.BufferedOutputStream;import java.io.BufferedWriter;import java.io.IOException;import java.io.OutputStreamWriter;import java.net.Socket;import okhttp3.OkHttpClient;import okhttp3.Response;public class MainActivity extends AppCompatActivity { private Button userlogin; private myreceiver mybroadcastreceiver; private SocketService.MyBackService myBackService; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { } @Override public void onServiceConnected(ComponentName name, IBinder service) { myBackService=(SocketService.MyBackService)service; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(new Runnable() { @Override public void run() { startService(intent); } }).start(); new Thread(new Runnable() { @Override public void run() { bindService(intent,connection,BIND_AUTO_CREATE); } }).start(); userlogin=(Button)findViewById(R.id.user_login); userlogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread(new Runnable() { @Override public void run() { myBackService.sendMessage("this is mainactivirty\n"); } }).start(); Intent intent1=new Intent(MainActivity.this,test.class); startActivity(intent1); } }); } protected void onDestroy(){ super.onDestroy(); unbindService(connection); unregisterReceiver(mybroadcastreceiver); Log.d("MainActivity","unbindservice"); }}下面是服务器的代码:
public class Server {BufferedWriter writer=null;BufferedReader reader=null;public static void main(String[]args){Server serversocket=new Server();serversocket.start();}public void start(){ServerSocket server=null;Socket socket=null;try {server=new ServerSocket(9898);while(true){socket=server.accept();/* * 当没有客户端连接服务器时,accept方法会阻塞住 */System.out.println("client "+socket.hashCode()+"connect...");manageConnection(socket);}} catch (Exception e) {e.printStackTrace();}finally {try {socket.close();server.close();} catch (Exception e2) {e2.printStackTrace();}}}/* * 连接管理 * 每次客户端连接服务器是时都会生成一个socket,将socket传入manage进行处理和发送 */public void manageConnection(final Socket socket){new Thread(new Runnable(){public void run(){String string=null;try {reader=new BufferedReader(new InputStreamReader(socket.getInputStream()));writer=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); // 下面为测试代码,为了测试客户端的监听功能(客户端接受服务器主动发送数据)是否成功,定时发送心跳包 // 由于在匿名类中使用,writer需要设置为static或者全局变量/* new Timer().schedule(new TimerTask(){public void run(){try {writer.write("heart once...\n");writer.flush();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}},3000, 3000);*//* * 注意:主线程中需要加入while形成循环,否子运行一次就会推出接受客户端信息 * 同理,客户端在写消息的时候也需要注意这一点 */while(!(string=reader.readLine()).equals("bye")){if(!string.equals(""))System.out.println("client "+socket.hashCode()+":"+string);writer.write(string+"\n");writer.flush();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}finally{try {writer.close();reader.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}).start();}}下面讲一下长连接的思路:长链接放在android的服务里进行长时间运行,保证能随时接收消息。同时加入心跳机制和断线重连,保持连接稳定。
在干净实现socket长链接有以下注意点:
1:由于网络通信是耗时操作,而且服务与开启他的活动共用一个主线程,所以从服务器读取需要开启一个新的线程ReadThread。
2:由于需要保持长链接干净,所以一个客户端只允许存在一个与服务器通信的socket。此处普及一个android服务的知识:服务的onCreate方法只在创建时候被调用了一次,这说明:Service被启动时只调用一次onCreate()方法,如果服务已经被启动,在次启动的Service组件将直接调用onStartCommand()方法,通过这样的生命周期,可以根据自身需求将指定操作分配进onCreate()方法或onStartCommand()方法中。所以服务器所有关于socket的操作有应该放在一个在onCreate()方法中开启的线程里。并且向服务器发送信息也应该放在服务里,使用已经开启的socket,避免创建多余的socket。在activity里需要使用时使用bindservice()方法绑定一下。(不理解bindservice()的可以在csdn上搜一下,有很多详细的讲解)
3:注意第二点中的一句话,服务的onCreate方法只在创建时候被调用了一次。在启动服务后,后台心跳包和短线重连会一直运行。如果启动是用bindservice()启动,即将代码MainActivity中的startService()删除,那么启动后退出客户端再进入客户端,程序会另外创建一个socket长链接。如下所示:
client 1814681656connect...client 1814681656:heartbeatclient 1814681656:heartbeatclient 1814681656:heartbeatclient 103530884connect...client 1814681656:heartbeatclient 103530884:heartbeatclient 1814681656:heartbeatclient 103530884:heartbeat这么一来,不断的推出进入会浪费很多资源。也会建立很多socket连接,这不符合我们建立干净长链接的目的。因此,第一次启动服务应使用startService()方法。使用这个方法启动服务后onCreate()执行,此后无论使用bindservice()或者startservice()启动服务,都不会建立新的socket服务。
4:启动服务等耗时的操作不应在主线程运行,都应该重新开一个线程运行。无论在服务或者活动中都如此。
5:我们的长链接理论上讲应该一直在后台运行。所以不需要人工使用stopservice()停止。但考虑到手机性能的问题,在关闭程序后后台服务依旧会跑,心跳极值和短线重连支持着这一点。那么如何做到在被杀进程后完全停止呢?你可以选择在heartbeat线程的最后面加stopself(),使得在被杀进程断线后心跳停止,不会执行短线重连。
6:关于习惯问题,有bindservice(),就得有unbindservice()。
7:借助heartbeat线程说一下,服务中开启的线程最好是在操作结束使用stopself()结束!
PS:关于代码有几点忘记说了!!!
处理心跳根管线重连之前没有考虑服务器主动断线
自己实现一个心跳检测,一定时间内未收到自定义的心跳包则标记为已断开。这是我认为最简单的想法!!!
1:用模拟器测试的话地址应该写10.0.2.2而不是127.0.0.1
2:模拟器测试我只会测试服务器主动断开socket后重连,而上述代码只针对客户端主动断开后重连。如果您实在5.17号之前看的话请重新看一下SocketService中的代码,我已经更新。
3:消息流的处理依旧有问题,以后会更单独更新一个博客讲一讲消息流的处理。
- 如何干净的在服务中实现socket长链接与服务器通信并处理相应的线程问题(有更新)
- Socket通信中碰到的链接问题
- C语言实现服务器与客户端的socket通信运行在linux系统中
- C语言实现服务器与客户端的socket通信运行在linux系统中 .
- xsd中包含有List、数组类型定义,在相应的xml中如何实现相应的值?
- SOCKET的短链接与长连接
- 什么是socket?什么是socket的长、短连接?java如何简单实现socket客户端和服务器?
- 基于Java Socket的自定义协议,实现Android与服务器的长连接(一)
- 基于Java Socket的自定义协议,实现Android与服务器的长连接(二)
- 基于Java Socket的自定义协议,实现Android与服务器的长连接(二)
- 基于Java Socket的自定义协议,实现Android与服务器的长连接(一)
- Android的socket通信的长连接,有心跳检测
- Socket+NIO实现客户端与服务器的通信的Demo
- 如果BarTender出现卸载不干净的问题如何处理
- SQL Server:在触发器中对远程链接服务器进行更新操作的问题
- C# 使用Socket实现服务器与客户端的通信
- Socket实现Android客户端与服务器的通信
- Android socket与服务器通信及心跳连接的实现
- 程序员的进化史
- PCL获取Kinect v2点云
- Android16
- 强制显示错误信息
- Android17
- 如何干净的在服务中实现socket长链接与服务器通信并处理相应的线程问题(有更新)
- 如何使用PageHelper分页组件
- 安装Hadoop及Spark for Ubuntu 16.04
- this的指向
- 2017CCPC中南地区赛暨湘潭大学邀请赛总结
- Android18
- android 笔记一
- [转] 最长回文子串——Manacher算法
- 引用