最近公司的项目要求加入消息推送功能,由于项目用户量不是很大,推送需求不是很严格,而且是基于内网的推送,所以我舍弃了使用三方的推送服务,自己使用Socket写了推送功能,剪出一个小Demo来跟大家分享一下,有不足之处希望读者能够多多给出建议。
关于Socket的TCP和UDP协议,相信大家都是很清楚的,当然做长连接两者都是可以的,据说QQ和微信360等使用的UDP做的,使用两个Service相互监控保持不被系统杀死,但是我目前做不了他们那样的效果,如果各位有更好的办法,可以共享一下,我们言归正传。
代码还是相当粗糙的,目的是练习使用Socket。服务器端使用一个线程的集合存储不同的客户端,相关代码如下:
public class ServerDemo {
private int count = 0;
private boolean isStartServer = false;
private ArrayList mThreadList = new ArrayList();
public static void main(String[] args) throws IOException {
ServerDemo server = new ServerDemo();
server.start();
}
/**
* 开启服务端的Socket
* @throws IOException
*/
public void start() throws IOException {
// 启动服务ServerSocket,设置端口号
ServerSocket ss = new ServerSocket(9001);
System.out.println("服务端已开启,等待客户端连接:");
isStartServer = true;
int socketID = 0;
Socket socket = null;
startMessageThread();
while (isStartServer) {
// 此处是一个阻塞方法,当有客户端连接时,就会调用此方法
socket = ss.accept();
System.out.println("客户端连接成功" + socket.getInetAddress());
// 4. 为这个客户端的Socket数据连接
SocketThread thread = new SocketThread(socket, socketID++);
thread.start();
mThreadList.add(thread);
}
}
private void startMessageThread() {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
try {
for (SocketThread st : mThreadList) {// 分别向每个客户端发送消息
if (st.socket == null || st.socket.isClosed())
continue;
System.out.println("客户端的userId:" + st.userId + " 消息编号:" + count);
if (st.userId == null || "".equals(st.userId))// 如果暂时没有确定Socket对应的用户Id先不发
continue;
String content = "我是从服务器发来的消息:" + count;
// 根据userId模拟服务端向不同的客户端推送消息
if (count % 2 == 0) {
if (st.userId.equals("002"))
content = "我是从服务器发发送给用户002的消息:" + count;
else
continue;
} else {
if (st.userId.equals("001"))
content = "我是从服务器发发送给用户001的消息:" + count;
else
continue;
}
SocketMessage message = new SocketMessage();
message.setFrom(Custom.NAME_SERVER);
message.setTo(Custom.NAME_CLIENT);
message.setMessage(content);
message.setType(Custom.MESSAGE_EVENT);
message.setUserId(st.userId);
BufferedWriter writer = st.writer;
String jMessage = Util.initJsonObject(message).toString() + "\n";
writer.write(jMessage);
writer.flush();
System.out.println("向客户端" + st.socket.getInetAddress() + "发送了消息:" + content);
}
count++;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, 0, 1000 * 30);//此处设置定时器目的是模仿服务端向客户端推送消息,假定每隔30秒推送一条消息
}
/**
* 关闭与SocketThread所代表的客户端的连接
* @param socketThread要关闭的客户端
* @throws IOException
*/
private void closeSocketClient(SocketThread socketThread) throws IOException {
if (socketThread.socket != null && !socketThread.socket.isClosed()) {
if (socketThread.reader != null)
socketThread.reader.close();
if (socketThread.writer != null)
socketThread.writer.close();
socketThread.socket.close();
}
mThreadList.remove(socketThread);
socketThread = null;
}
/**
* 客户端Socket线程,
* @author 华硕
*
*/
public class SocketThread extends Thread {
public int socketID;
public Socket socket;//客户端的Socket
public BufferedWriter writer;
public BufferedReader reader;
public String userId;//客户端的UserId
private long lastTime;
public SocketThread(Socket socket, int count) {
socketID = count;
this.socket = socket;
lastTime = System.currentTimeMillis();
}
@Override
public void run() {
super.run();
try {
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//循环监控读取客户端发来的消息
while (isStartServer) {
// 超出了发送心跳包规定时间,说明客户端已经断开连接了这时候要断开与该客户端的连接
long interval = System.currentTimeMillis() - lastTime;
if (interval >= (Custom.SOCKET_ACTIVE_TIME * 1000 * 4)) {
System.out.println("客户端发包间隔时间严重延迟,可能已经断开了interval:" + interval);
System.out.println("Custom.SOCKET_ACTIVE_TIME * 1000:" + Custom.SOCKET_ACTIVE_TIME * 1000);
closeSocketClient(this);
break;
}
if (reader.ready()) {
lastTime = System.currentTimeMillis();
System.out.println("收到消息,准备解析:");
String data = reader.readLine();
System.out.println("解析成功:" + data);
SocketMessage from = Util.parseJson(data);
//给UserID赋值,此处是我们项目的需求,根据客户端不同的UserId来分别进行推送
if (userId == null || "".equals(userId))
userId = from.getUserId();
SocketMessage to = new SocketMessage();
if (from.getType() == Custom.MESSAGE_ACTIVE) {//心跳包
System.out.println("收到心跳包:" + socket.getInetAddress());
to.setType(Custom.MESSAGE_ACTIVE);
to.setFrom(Custom.NAME_SERVER);
to.setTo(Custom.NAME_CLIENT);
to.setMessage("");
to.setUserId(userId);
writer.write(Util.initJsonObject(to).toString() + "\n");
writer.flush();
} else if (from.getType() == Custom.MESSAGE_CLOSE) {//关闭包
System.out.println("收到断开连接的包:" + socket.getInetAddress());
to.setType(Custom.MESSAGE_CLOSE);
to.setFrom(Custom.NAME_SERVER);
to.setTo(Custom.NAME_CLIENT);
to.setMessage("");
to.setUserId(userId);
writer.write(Util.initJsonObject(to).toString() + "\n");
writer.flush();
closeSocketClient(this);
break;
} else if (from.getType() == Custom.MESSAGE_EVENT) {//事件包,客户端可以向服务端发送自定义消息
System.out.println("收到普通消息包:" + from.getMessage());
}
}
Thread.sleep(100);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
当服务端收到客户端的连接时候,就开启线程用来接收客户端的消息(心跳包的消息或者是普通的消息),服务器端模拟消息推送,利用定时器每隔一段时间就像客户端推送一条消息。我的消息分为三种:
心跳消息,客户端每隔一段时间就会向服务器发送一条心跳消息,服务端收到之后会马上返回一条心跳消息给客户端,以此来保持连接。
普通消息,客户端与服务端的数据交互。
断开连接消息,当服务端收到客户端的断开连接消息时,会立马给客户端返回断开连接消息,同时服务端会主动与客户端断开连接,并从线程任务列表把该客户端Socket移除,只有再次连接才会被添加进来。客户端收到服务端的断开回执,也会断开连接,防止资源浪费和异常发生。
因为我专业是做Android开发的,重点说一下客户端的代码:
在onStartCommand方法返回START_STICKY,如果Service由于内存不够被意外杀死,那么等内存够了之后,还会重启
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand(Intent intent, int flags, int startId)");
// return super.onStartCommand(intent, flags, startId);
//当Service重启时候,判断一下,如果Socket断开了连接,但是保存的状态还是连接状态,那就是意外的断开,需要重连
if (isServerClose() && SocketServiceSP.getInstance(SocketService.this).isSocketConnect())
connectSocket();
return START_STICKY;//设置START_STICKY为了使服务被意外杀死后可以重启
}
注册广播,当Service走onDestroy()方法时候,发送广播,重启服务
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate()");
SocketServiceSP.getInstance(this).saveSocketServiceStatus(true);//保存了Service的开启状态
//收到Service被杀死的广播,立即重启
restartBR = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "SocketServer重启了......");
String action = intent.getAction();
if (!TextUtils.isEmpty(action) && action.equals("socketService_killed")) ;
Intent sIntent = new Intent(SocketService.this, SocketService.class);
startService(sIntent);
SocketServiceSP.getInstance(SocketService.this).saveSocketServiceStatus(true);//保存了Service的开启状态
//当Service重启时候,判断一下,如果Socket断开了连接,但是保存的状态还是连接状态,那就是意外的断开,需要重连
if (isServerClose() && SocketServiceSP.getInstance(SocketService.this).isSocketConnect())
connectSocket();
}
};
registerReceiver(restartBR, new IntentFilter("socketService_killed"));
}
/**
* 销毁Service同时要销毁Socket
*/
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy()");
if (mReadThread != null)
mReadThread.release();
releaseLastSocket(mSocket);
sendBroadcast(new Intent("socketService_killed"));
SocketServiceSP.getInstance(SocketService.this).saveSocketServiceStatus(false);
unregisterReceiver(restartBR);
}
Service的启动使用的startService方法启动的,为了使用户能够操作界面来连接和断开Socket,就需要Service进程与Activity进程进行交互,而这里需要用到AIDL技术,AIDL接口的方法参数使用了自定义的SocketMessage类,所以还需要实现Parcelable接口。
关于AIDL的使用,就不进行详细的介绍了,有不清楚的朋友可以私下交流
下面看一下整个Service的代码
package com.herenit.socketpushdemo.core;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.support.v7.app.NotificationCompat;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.herenit.socketpushdemo.ISocketMessageListener;
import com.herenit.socketpushdemo.ISocketServiceInterface;
import com.herenit.socketpushdemo.MainActivity;
import com.herenit.socketpushdemo.R;
import com.herenit.socketpushdemo.bean.SocketMessage;
import com.herenit.socketpushdemo.common.Custom;
import com.herenit.socketpushdemo.common.SocketServiceSP;
import com.herenit.socketpushdemo.common.Util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.ref.WeakReference;
import java.net.Socket;
/**
* Created by HouBin on 2017/3/14.
* 与服务器保持长连接的Service
*/
public class SocketService extends Service {
private final String TAG = SocketService.class.getSimpleName();
//Service实例,用于在Activity中进行连接断开发消息等图形界面化的操作
//Socket的弱引用
private WeakReference mSocket;
//消息发出的时间(不管是心跳包还是普通消息,发送完就会跟新时间)
private long sendTime = 0;
private final int MSG_WHAT_CONNECT = 111; //连接Socket
private final int MSG_WHAT_DISCONNECT = 112;//断开Socket
private final int MSG_WHAT_SENDMESSAGE = 113;//发送消息
/**
* 处理Socket的连接断开发消息的Handler机制
*/
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_WHAT_CONNECT:
if (isServerClose()) {
connectSocket();
} else {
Toast.makeText(SocketService.this, "Socket已经连接上了", Toast.LENGTH_SHORT).show();
}
break;
case MSG_WHAT_DISCONNECT:
if (isServerClose())
Toast.makeText(SocketService.this, "Socket已经断开了", Toast.LENGTH_SHORT).show();
else
interruptSocket();
break;
case MSG_WHAT_SENDMESSAGE:
if (isServerClose()) {
Toast.makeText(SocketService.this, "请先连接Socket", Toast.LENGTH_SHORT).show();
} else {
SocketMessage socketMessage = (SocketMessage) msg.obj;
try {
SocketService.this.sendMessage(socketMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
}
break;
}
}
};
//读取服务器端发来的消息的线程
private ReadThread mReadThread;
//监控服务被杀死重启的广播,保持服务不被杀死
private BroadcastReceiver restartBR;
public SocketService() {
super();
}
/**
* 创建Service的同时要创建Socket
*/
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate()");
SocketServiceSP.getInstance(this).saveSocketServiceStatus(true);//保存了Service的开启状态
//收到Service被杀死的广播,立即重启
restartBR = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "SocketServer重启了......");
String action = intent.getAction();
if (!TextUtils.isEmpty(action) && action.equals("socketService_killed")) ;
Intent sIntent = new Intent(SocketService.this, SocketService.class);
startService(sIntent);
SocketServiceSP.getInstance(SocketService.this).saveSocketServiceStatus(true);//保存了Service的开启状态
//当Service重启时候,判断一下,如果Socket断开了连接,但是保存的状态还是连接状态,那就是意外的断开,需要重连
if (isServerClose() && SocketServiceSP.getInstance(SocketService.this).isSocketConnect())
connectSocket();
}
};
registerReceiver(restartBR, new IntentFilter("socketService_killed"));
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand(Intent intent, int flags, int startId)");
// return super.onStartCommand(intent, flags, startId);
//当Service重启时候,判断一下,如果Socket断开了连接,但是保存的状态还是连接状态,那就是意外的断开,需要重连
if (isServerClose() && SocketServiceSP.getInstance(SocketService.this).isSocketConnect())
connectSocket();
return START_STICKY;//设置START_STICKY为了使服务被意外杀死后可以重启
}
/**
* 客户端通过Socket与服务端建立连接
*/
public void connectSocket() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Socket socket = new Socket(Custom.SERVER_HOST, Custom.SERVER_PORT);
//这里保存Socket当前已经连接上了
SocketServiceSP.getInstance(SocketService.this).saveSocketConnectStatus(true);
socket.setSoTimeout(Custom.SOCKET_CONNECT_TIMEOUT * 1000);
Log.e(TAG, "Socket连接成功。。。。。。");
mSocket = new WeakReference(socket);
mReadThread = new ReadThread(socket);
mReadThread.start();
mHandler.postDelayed(activeRunnable, Custom.SOCKET_ACTIVE_TIME * 1000);//开启定时器,定时发送心跳包,保持长连接
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
/**
* 发送心跳包的任务
*/
private Runnable activeRunnable = new Runnable() {
@Override
public void run() {
if (System.currentTimeMillis() - sendTime >= Custom.SOCKET_ACTIVE_TIME * 1000) {
SocketMessage message = new SocketMessage();
message.setType(Custom.MESSAGE_ACTIVE);
message.setMessage("");
message.setFrom(Custom.NAME_CLIENT);
message.setTo(Custom.NAME_SERVER);
try {
if (!sendMessage(message)) {
if (mReadThread != null)
mReadThread.release();
releaseLastSocket(mSocket);
connectSocket();
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
mHandler.postDelayed(this, Custom.SOCKET_ACTIVE_TIME * 1000);
}
};
/**
* 发送消息到服务端
*
* @param message
* @return
*/
public boolean sendMessage(SocketMessage message) throws RemoteException {
message.setUserId("001");
if (mSocket == null || mSocket.get() == null) {
return false;
}
Socket socket = mSocket.get();
try {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
if (!socket.isClosed()) {
String jMessage = Util.initJsonObject(message).toString() + "\n";
writer.write(jMessage);
writer.flush();
Log.e(TAG, "发送消息:" + jMessage);
sendTime = System.currentTimeMillis();//每次发送成数据,就改一下最后成功发送的时间,节省心跳间隔时间
if (message.getType() == Custom.MESSAGE_EVENT) {//通知实现了消息监听器的界面,让其跟新消息列表
messageListener.updateMessageList(message);
}
} else {
return false;
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 读取消息的线程
*/
class ReadThread extends Thread {
private WeakReference mReadSocket;
private boolean isStart = true;
public ReadThread(Socket socket) {
mReadSocket = new WeakReference(socket);
}
public void release() {
isStart = false;
releaseLastSocket(mReadSocket);
}
@Override
public void run() {
super.run();
Socket socket = mReadSocket.get();
if (socket != null && !socket.isClosed()) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (isStart) {
if (reader.ready()) {
String message = reader.readLine();
Log.e(TAG, "收到消息:" + message);
SocketMessage sMessage = Util.parseJson(message);
if (sMessage.getType() == Custom.MESSAGE_ACTIVE) {//处理心跳回执
} else if (sMessage.getType() == Custom.MESSAGE_EVENT) {//事件消息
if (messageListener != null)
messageListener.updateMessageList(sMessage);
sendNotification(sMessage);
} else if (sMessage.getType() == Custom.MESSAGE_CLOSE) {//断开连接消息回执
mHandler.removeCallbacks(activeRunnable);
release();
releaseLastSocket(mSocket);
}
}
Thread.sleep(100);//每隔0.1秒读取一次,节省点资源
}
} catch (IOException e) {
release();
releaseLastSocket(mSocket);
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
//通知的ID,为了分开显示,需要根据Id区分
private int nId = 0;
/**
* 收到时间消息,发送通知提醒
*
* @param sMessage
*/
private void sendNotification(SocketMessage sMessage) {
//为了版本兼容,使用v7包的BUILDER
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
//状态栏显示的提示,有的手机不显示
builder.setTicker("简单的Notification");
//通知栏标题
builder.setContentTitle("from" + sMessage.getFrom() + "的消息");
//通知栏内容
builder.setContentText(sMessage.getMessage());
//通知内容摘要
builder.setSubText(sMessage.getUserId());
//在通知右侧的时间下面用来展示一些其他信息
// builder.setContentInfo("其他");
//用来显示同种通知的数量,如果设置了ContentInfo属性,则NUmber属性会被覆盖,因为二者显示的位置相同
// builder.setNumber(3);
//可以点击通知栏的删除按钮
builder.setAutoCancel(true);
//系统状态栏显示的小图标
builder.setSmallIcon(R.mipmap.jpush_notification_icon);
//通知下拉显示的大图标
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
//点击通知跳转的INTENT
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 1, intent, 0);
builder.setContentIntent(pendingIntent);//点击跳转
//通知默认的声音,震动,呼吸灯
builder.setDefaults(NotificationCompat.DEFAULT_ALL);
Notification notification = builder.build();
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.notify(nId, notification);
nId++;
}
/**
* 释放Socket,并关闭
*
* @param socket
*/
private void releaseLastSocket(WeakReference socket) {
//正常的断开连接,这里保存断开连接的状态
SocketServiceSP.getInstance(this).saveSocketConnectStatus(false);
if (socket != null) {
Socket so = socket.get();
try {
if (so != null && !so.isClosed())
so.close();
socket.clear();
Log.e(TAG, "Socket断开连接。。。。。。");
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 判断是否断开连接,断开返回true,没有返回false
*
* @return
*/
public boolean isServerClose() {
try {
if (mSocket != null && mSocket.get() != null) {
mSocket.get().sendUrgentData(0);//发送1个字节的紧急数据,默认情况下,服务器端没有开启紧急数据处理,不影响正常通信
return false;
}
} catch (Exception se) {
return true;
}
return true;
}
/**
* 销毁Service同时要销毁Socket
*/
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy()");
if (mReadThread != null)
mReadThread.release();
releaseLastSocket(mSocket);
sendBroadcast(new Intent("socketService_killed"));
SocketServiceSP.getInstance(SocketService.this).saveSocketServiceStatus(false);
unregisterReceiver(restartBR);
}
/**
* 对外提供的断开Socket连接的方法(向服务器发送断开的包,服务器收到后会与之断开)
*/
public void interruptSocket() {
new Thread(new Runnable() {
@Override
public void run() {
SocketMessage message = new SocketMessage();
message.setType(Custom.MESSAGE_CLOSE);
message.setMessage("");
message.setFrom(Custom.NAME_CLIENT);
message.setTo(Custom.NAME_SERVER);
try {
sendMessage(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
}
@Override
public boolean onUnbind(Intent intent) {
Log.e(TAG, "onUnbind(Intent intent)");
return super.onUnbind(intent);
}
@Override
public void onRebind(Intent intent) {
Log.e(TAG, "onBind(Intent intent)");
super.onRebind(intent);
}
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onRebind(Intent intent) ");
return mBinder;
}
/**************************************************
* AIDL
********************************************************/
private ISocketMessageListener messageListener;
private Binder mBinder = new ISocketServiceInterface.Stub() {
private static final String ITAG = "ISocketServiceInterface";
/**
* 客户端要求连接Socket
* @throws RemoteException
*/
@Override
public void connectSocket() throws RemoteException {
Log.e(ITAG, "connectSocket");
mHandler.sendEmptyMessage(MSG_WHAT_CONNECT);
}
/**
* 客户端要求断开Socket连接
* @throws RemoteException
*/
@Override
public void disConnectSocket() throws RemoteException {
Log.e(ITAG, "disConnectSocket");
mHandler.sendEmptyMessage(MSG_WHAT_DISCONNECT);
}
/**
* 客户端向服务器端发送消息
* @param message
* @throws RemoteException
*/
@Override
public void sendMessage(SocketMessage message) throws RemoteException {
Log.e(ITAG, "sendMessage");
Message msg = Message.obtain();
msg.what = MSG_WHAT_SENDMESSAGE;
msg.obj = message;
mHandler.sendMessage(msg);
}
/**
* 客户端添加消息监听器,监听服务器端发来的消息
* @param listener
* @throws RemoteException
*/
@Override
public void addMessageListener(ISocketMessageListener listener) throws RemoteException {
Log.e(ITAG, "addMessageListener");
messageListener = listener;
}
@Override
public void removeMessageListener(ISocketMessageListener listener) throws RemoteException {
Log.e(ITAG, "removeMessageListener");
messageListener = null;
}
};
}
代码的注释还是比较详细的,整体的代码大家可以去我的GitHub上下载
服务端代码
客户端代码
时间比较紧,介绍的不是很详细,很抱歉,大家有疑问就直接看整个项目或者直接联系我吧!