如何干净的在服务中实现socket长链接与服务器通信并处理相应的线程问题(有更新)

注:本文部分代码改编自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 block
								e.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 block
					e.printStackTrace();
				}finally{
					try {
						writer.close();
						reader.close();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.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:heartbeat
client 1814681656:heartbeat
client 1814681656:heartbeat
client 103530884connect...
client 1814681656:heartbeat
client 103530884:heartbeat
client 1814681656:heartbeat
client 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:消息流的处理依旧有问题,以后会更单独更新一个博客讲一讲消息流的处理。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章