2014-12-14 12:15:11 zhangjm_123 阅读数 21821

上篇文章Android socket 编程 实现消息推送(一)讲到socket编程的消息推送原理,现在我们来看看基于Android客户机socket编程实现消息推送的过程。


根据消息推送的原理图,我们的实现过程主要分为Server端和Client端,Server端采用Java的编程,而Client端则用Android编程。

所以在这里也分别创建了两个工程SocketServerSocketClient

1.SocketServer工程


我们先来看一下SocketMessage.java类:

public class SocketMessage {

	public int to;//socketID,指发送给谁
	public int from;//socketID,指谁发送过来的
	public String msg;//消息内容
	public String time;//接收时间
	public SocketThread thread;//socketThread下面有介绍
}

该类是一个消息类,用于表示消息是由谁发给谁的、消息内容是什么、接收时间是多少,只有几个属性,比较简单。

而MyServer.java类就相对比较多一些代码:

package com.jimstin.server;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

import org.json.JSONObject;


import com.jimstin.msg.SocketMessage;

public class MyServer {

	private boolean isStartServer;
	private ServerSocket mServer;
	/**
	 * 消息队列,用于保存SocketServer接收来自于客户机(手机端)的消息
	 */
	private ArrayList<SocketMessage> mMsgList = new ArrayList<SocketMessage>();
	/**
	 * 线程队列,用于接收消息。每个客户机拥有一个线程,每个线程只接收发送给自己的消息
	 */
	private ArrayList<SocketThread> mThreadList = new ArrayList<SocketThread>();
	
	/**
	 * 开启SocketServer
	 */
	private void startSocket() {
		try {
			isStartServer = true;
			int prot = 2000;//端口可以自己设置,但要和Client端的端口保持一致
			mServer = new ServerSocket(prot);//创建一个ServerSocket
			System.out.println("启动server,端口:"+prot);
			Socket socket = null;
			int socketID = 0;//Android(SocketClient)客户机的唯一标志,每个socketID表示一个Android客户机
			//开启发送消息线程
			startSendMessageThread();
			//用一个循环来检测是否有新的客户机加入
			while(isStartServer) {
				//accept()方法是一个阻塞的方法,调用该方法后,
				//该线程会一直阻塞,直到有新的客户机加入,代码才会继续往下走
				socket = mServer.accept();
				//有新的客户机加入后,则创建一个新的SocketThread线程对象
				SocketThread thread = new SocketThread(socket, socketID++);
				thread.start();
				//将该线程添加到线程队列
				mThreadList.add(thread);
			}
    		
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 开启推送消息线程,如果mMsgList中有SocketMessage,则把该消息推送到Android客户机
	 */
	public void startSendMessageThread() {
		new Thread(){
			@Override
			public void run() {
				super.run();
				try {
					/*如果isStartServer=true,则说明SocketServer已启动,
					用一个循环来检测消息队列中是否有消息,如果有,则推送消息到相应的客户机*/
					while(isStartServer) {
						//判断消息队列中的长度是否大于0,大于0则说明消息队列不为空
						if(mMsgList.size() > 0) {
							//读取消息队列中的第一个消息
							SocketMessage from = mMsgList.get(0);
							for(SocketThread to : mThreadList) {
								if(to.socketID == from.to) {
									BufferedWriter writer = to.writer;
									JSONObject json = new JSONObject();
									json.put("from", from.from);
									json.put("msg", from.msg);
									json.put("time", from.time);
									//writer写进json中的字符串数据,末尾记得加换行符:"\n",否则在客户机端无法识别
									//因为BufferedReader.readLine()方法是根据换行符来读取一行的
									writer.write(json.toString()+"\n");
									//调用flush()方法,刷新流缓冲,把消息推送到手机端
									writer.flush();
									System.out.println("推送消息成功:"+from.msg+">> to socketID:"+from.to);
									break;
								}
							}
							//每推送一条消息之后,就要在消息队列中移除该消息
							mMsgList.remove(0);
						}
						Thread.sleep(200);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}.start();
	}
	
	/**
	 * 定义一个SocketThread类,用于接收消息
	 *
	 */
	public class SocketThread extends Thread {
		
		public int socketID;
		public Socket socket;//Socket用于获取输入流、输出流
		public BufferedWriter writer;//BufferedWriter 用于推送消息
		public BufferedReader reader;//BufferedReader 用于接收消息
		
		public SocketThread(Socket socket, int count) {
			socketID = count;
			this.socket = socket;
			System.out.println("新增一台客户机,socketID:"+socketID);
		}
		
		@Override
		public void run() {
			super.run();

			try {
				//初始化BufferedReader
				reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
				//初始化BufferedWriter
				writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "utf-8"));
				//如果isStartServer=true,则说明SocketServer已经启动,
				//现在需要用一个循环来不断接收来自客户机的消息,并作其他处理
				while(isStartServer) {
					//先判断reader是否已经准备好
					if(reader.ready()) {
						/*读取一行字符串,读取的内容来自于客户机
						reader.readLine()方法是一个阻塞方法,
						从调用这个方法开始,该线程会一直处于阻塞状态,
						直到接收到新的消息,代码才会往下走*/
						String data = reader.readLine();
						//讲data作为json对象的内容,创建一个json对象
						JSONObject json = new JSONObject(data);
						//创建一个SocketMessage对象,用于接收json中的数据
						SocketMessage msg = new SocketMessage();
						msg.to = json.getInt("to");
						msg.msg = json.getString("msg");
						msg.from = socketID;
						msg.time = getTime(System.currentTimeMillis());
						//接收到一条消息后,将该消息添加到消息队列mMsgList
						mMsgList.add(msg);
						System.out.println("收到一条消息:"+json.getString("msg")+" >>>> to socketID:"+json.getInt("to"));
					}
					//睡眠100ms,每100ms检测一次是否有接收到消息
					Thread.sleep(100);
				}
				
			} catch (Exception e) {
				e.printStackTrace();
			}  
            
		}
	}
	/**
	 * 获取指定格式的时间字符串,通过毫秒转换日期
	 * @param millTime
	 */
	private String getTime(long millTime) {
		Date d = new Date(millTime);
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		return sdf.format(d);
	}
	public static void main(String[] args) {
		MyServer server = new MyServer();
		server.startSocket();
	}

}

2.SocketClient工程

该工程是一个Android的工程,只有一个MainActivity.java和activity_main.xml文件,

先看一下activity_main.xml布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical" >
    
    <LinearLayout 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <EditText 
	        android:id="@+id/ip_edt"
	        android:layout_width="0dp"
	        android:layout_height="wrap_content"
	        android:layout_weight="1"
	        android:hint="ip"
	        android:text="172.16.1.200"/>
        <EditText 
	        android:id="@+id/port_edt"
	        android:layout_width="0dp"
	        android:layout_height="wrap_content"
	        android:layout_weight="1"
	        android:hint="port"
	        android:text="2000"/>
    </LinearLayout>
    <Button 
        android:id="@+id/start_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="start"/>
    <EditText 
        android:id="@+id/socket_id_edt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="socketID"/>
    

    
    <EditText 
        android:id="@+id/msg_edt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minLines="5"
        android:hint="content"
        android:gravity="top"
        />
    <Button 
        android:id="@+id/send_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="send"/>
	<TextView 
	    android:id="@+id/console_txt"
	    android:layout_width="match_parent"
	    android:layout_height="0dp"
	    android:layout_weight="1"/>    
	<Button 
        android:id="@+id/clear_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="clear"/>
</LinearLayout>

效果图:



MainActivity.java类:

package com.jimstin.socketclient;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.json.JSONObject;

import com.tencent.stat.MtaSDkException;
import com.tencent.stat.StatConfig;
import com.tencent.stat.StatService;

import android.R.integer;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.app.Activity;

public class MainActivity extends Activity implements OnClickListener {

	private EditText mIPEdt, mPortEdt, mSocketIDEdt, mMessageEdt;
	private static TextView mConsoleTxt;
	
	private static StringBuffer mConsoleStr = new StringBuffer();
	private Socket mSocket;
	private boolean isStartRecieveMsg;
	
	private SocketHandler mHandler;
	protected BufferedReader mReader;//BufferedWriter 用于推送消息
	protected BufferedWriter mWriter;//BufferedReader 用于接收消息
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

	private void initView() {
		mIPEdt = (EditText) findViewById(R.id.ip_edt);
		mPortEdt = (EditText) findViewById(R.id.port_edt);
		mSocketIDEdt = (EditText) findViewById(R.id.socket_id_edt);
		mMessageEdt = (EditText) findViewById(R.id.msg_edt);
		mConsoleTxt = (TextView) findViewById(R.id.console_txt);
		findViewById(R.id.start_btn).setOnClickListener(this);
		findViewById(R.id.send_btn).setOnClickListener(this);
		findViewById(R.id.clear_btn).setOnClickListener(this);
		mHandler = new SocketHandler();
	}

	/**
	 * 初始化socket
	 */
	private void initSocket() {
		//新建一个线程,用于初始化socket和检测是否有接收到新的消息
		Thread thread = new Thread(new Runnable() {
			
			@Override
			public void run() {
				String ip = mIPEdt.getText().toString();//IP
				int port = Integer.parseInt(mPortEdt.getText().toString());//Socket
				
				try {
					isStartRecieveMsg = true;
					mSocket = new Socket(ip, port);
					mReader = new BufferedReader(new InputStreamReader(mSocket.getInputStream(), "utf-8"));
					mWriter = new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream(), "utf-8"));
					while(isStartRecieveMsg) {
						if(mReader.ready()) {
							/*读取一行字符串,读取的内容来自于客户机
							reader.readLine()方法是一个阻塞方法,
							从调用这个方法开始,该线程会一直处于阻塞状态,
							直到接收到新的消息,代码才会往下走*/
							String data = mReader.readLine();
							//handler发送消息,在handleMessage()方法中接收
					        mHandler.obtainMessage(0, data).sendToTarget();
						}
						Thread.sleep(200);
					}
					mWriter.close();
					mReader.close();
					mSocket.close();
				} catch (Exception e) {
					e.printStackTrace();
				} 
			}
		});
		thread.start();
	}
	
	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.send_btn:
			send();
			break;
		case R.id.clear_btn:
			mConsoleStr.delete(0, mConsoleStr.length());
			mConsoleTxt.setText(mConsoleStr.toString());
			break;
		case R.id.start_btn:
			if(!isStartRecieveMsg) {
				initSocket();
			}
			break;
		default:
			break;
		}
	}

	/**
	 * 发送
	 */
	private void send() {
		new AsyncTask<String, Integer, String>() {

			@Override
			protected String doInBackground(String... params) {
				sendMsg();
				return null;
			}
		}.execute();
	}
	/**
	 * 发送消息
	 */
	protected void sendMsg() {
		try {
			String socketID = mSocketIDEdt.getText().toString().trim();
			String msg = mMessageEdt.getText().toString().trim();
			JSONObject json = new JSONObject();
			json.put("to", socketID);
			json.put("msg", msg);
			mWriter.write(json.toString()+"\n");
			mWriter.flush();
			mConsoleStr.append("我:" +msg+"   "+getTime(System.currentTimeMillis())+"\n");
			mConsoleTxt.setText(mConsoleStr);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	static class SocketHandler extends Handler {
		
		@Override
		public void handleMessage(Message msg) {
			// TODO Auto-generated method stub
			super.handleMessage(msg);
			switch (msg.what) {
			case 0:
				try {
					//将handler中发送过来的消息创建json对象
					JSONObject json = new JSONObject((String)msg.obj);
					mConsoleStr.append(json.getString("from")+":" +json.getString("msg")+"   "+getTime(System.currentTimeMillis())+"\n");
					//将json数据显示在TextView中
					mConsoleTxt.setText(mConsoleStr);
				} catch (Exception e) {
					e.printStackTrace();
				}
				
				break;

			default:
				break;
			}
		}
	}
	
	@Override
	public void onBackPressed() {
		super.onBackPressed();
		isStartRecieveMsg = false;
	}
	
	private static String getTime(long millTime) {
		Date d = new Date(millTime);
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		return sdf.format(d);
	}
	
}

以上代码的注释都比较详细,就不再多说了。


资源下载地址:http://download.csdn.net/detail/zhangjm_123/8258019


注意:先把Android端的apk分别安装到两台手机上面,再运行SocketServer,点击SocketClient的Start,在SocketServer的控制台上面就可以看到有新的客户机增加了(确保IP地址和端口的正确前提下)。然后输入socketID(socketID表示Android客户机的一个账号,就像QQ号一样,从0开始,一个socketID表示一个客户机)和content,点击send就可以发送消息了。

效果图请见Android socket 编程 实现消息推送(一)

有问题的欢迎留言。

2016-03-02 17:38:21 u013408979 阅读数 2794

1.使用第三方推送平台
如友盟、极光、百度等现成的消息推送。好处:消息及时、稳定,集成简单。不需要自己搭建支持服务器,但是可能涉及到收费、保密、服务质量、扩展等问题。
2、MQTT协议实现android推送
MQTT是一个轻量级的消息发布、订阅协议,用来做推送比较理想,RSMB就是一个简单是MQTT代理。协议简洁、小巧、可扩展性强、省流量、省电,可更具需求进行二次开发;缺点:不够成熟、实现复杂、服务端组件rsmb不开源,部署硬件成本较高,需要公司提供服务器支持。
3、XMPP协议实现消息推送
androidpn是一个局域XMPP协议的开源推送实现。缺点:时间过长,就再也收不到推送消息了;性能上也不够稳定;二次开发需要做的工作比较多,费流量,费电,部署硬件成本高。主要应用于许多聊天系统中。
4、C2DM服务
底层采用的是XMPP协议进行封装的。这个是google提供的官方的消息推送服务,但是其需要依赖google官方提供的C2DM服务器,所有国内不能用。
5、轮询方式
每隔一段时间就去服务器发送询问消息,一旦服务器有变化就立即同步消息。消息不及时,费流量。
6、SMS方式
通过拦截短信的方式来同步消息。每当需要通知用户进行同步消息是假,就向该用户发送短信,客户端对短信进行拦截,然后做相应的处理,费钱
7、长连接方式
客户端和服务器端建立长连接,可保证及时性和实时性。但是不够稳定。

2012-10-17 14:29:58 xiao12593a 阅读数 12

在开发Android和iPhone应用程序时,我们往往需要从服务器不定的向手机客户端即时推送各种通知消息,iPhone上已经有了比较简单的和完美的推送通知解决方案,可在Android平台上实现起来却相对比较麻烦,最近就对Android的推送通知服务进行初步的研究。

 

在Android手机平台上,Google提供了C2DM(Cloudto Device Messaging)服务,起初我就是准备采用这个服务来实现Android手机上的推送功能。

Android Cloud to Device Messaging (C2DM)是一个用来帮助开发者从服务器向Android应用程序发送数据的服务。该服务提供了一个简单的、轻量级的机制,允许服务器可以通知移动应用程序直接与服务器进行通信,以便于从服务器获取应用程序更新和用户数据。C2DM服务负责处理诸如消息排队等事务并向运行于目标设备上的应用程序分发这些消息。

但这个服务存在很大的问题:

1、C2DM内置于Android的2.2系统上,无法兼容老的1.5到2.1系统;

2、C2DM需要依赖于Google官方提供的C2DM服务器,由于国内的网络环境,这个服务经常不可用,如果想要很好的使用,我们的App Server必须也在国外。这个可能不是每个开发者或公司能实现的。

所以最终我放弃了这个方案。采用XMPP协议实现Android推送,事实上Google官方的C2DM服务器底层也是采用XMPP协议进行的封装。

XMPP(可扩展通讯和表示协议)是基于可扩展标记语言(XML)的协议,它用于即时消息(IM)以及在线探测。这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息。

androidpn是一个基于XMPP协议的java开源Android push notification实现。它包含了完整的客户端和服务器端。

 

androidpn的实现示意图如下:

 

androidpn客户端需要用到一个基于java的开源XMPP协议包asmack,这个包同样也是基于openfire下的另外一个开源项目smack,不过我们不需要自己编译,可以直接把androidpn客户端里面的asmack.jar拿过来用。客户端利用asmack中提供的XMPPConnection类与服务器建立持久连接,并通过该连接进行用户注册和登录认证,同样也是通过这条连接,接收服务器发送的通知。

androidpn服务器端也是java语言实现的,基于openfire开源工程,它的Web部分采用的是spring框架。Androidpn服务器包含两个部分,一个是侦听在5222端口上的XMPP服务,负责与客户端的XMPPConnection类进行通信,作用是用户注册和身份认证,并发送推送通知消息。另外一部分是Web服务器,采用一个轻量级的HTTP服务器,负责接收用户的Web请求。服务器架构如下:

最上层包含四个组成部分,分别是SessionManager,Auth Manager,PresenceManager以及Notification Manager。SessionManager负责管理客户端与服务器之间的会话,Auth Manager负责客户端用户认证管理,Presence Manager负责管理客户端用户的登录状态,NotificationManager负责实现服务器向客户端推送消息功能。

服务器端界面如下,分别对应了上述的几个功能模块:

 

 

 

 下载了客户端代码后需修改androidpn.properties文件中的xmppHost的地址,如下:

 

点击Submit发送以后,可以在Android手机端看到接收的消息如下

点击OK按钮可以进行相应的URI跳转。

该解决方案的最大优势就是简单,并且避免了C2DM的两个问题。利用XMPP协议我们还可以进一步的对协议进行扩展,实现更完善的功能。

更多信息:http://androidpn.sourceforge.net

2019-06-04 18:34:16 weixin_42009516 阅读数 137

Android 推送机制实现原理

APP传统获取服务端信息途径:Pull模式–这种模式客户端和服务器端维持的是短连接。当然也存在由服务器端主动向客户端主动发送消息的通信模式,称为Push模式,即为推送–此时需要移动端和服务器端保持一个长连接通道。
也有APP基于Pull模式通过轮询的方式实现类似推送的功能,即客户端启动一个定时器,每隔一段时间向服务器端发起Pull请求,也称伪推送。真是的推送是基于TCP长连接实现的,并通过间隔性发送心跳包来放止NAT超时,同时判断服务器端和客户端是否断开,最终保证通道的畅通。

自己实现推送功能

在Android中想要建立Tcp长连接,就不能使用HttpUrlConnection或者HttpClient等网络请求API,因为他们属于HTTP协议,Java为开发者提供了网络套接字Socket,它封装了很多TCP的操作,对于移动端来说,一个推送的基本框架需要包含:

  • 和服务端建立连接
  • 发送数据给服务器
  • 从服务器接收推送数据的功能
  • 心跳包的实现
    代码中,每个功能会分别封装为独立的线程,然后通过一个管理器统筹连接的建立和管理。

长连接的建立(TCPConnectThread)

长连接的建立主要通过Socket类的connect方法实现的,其中TCP_URL表示地址,TCP_PORT表示端口号,SOCKET_CONNECT_TIMEOUT表示超时时间,它的setKeepAlive表示这次连接是长连接。
实际中,还要考虑连接是否已经建立,是否需要重新建立,手机网络不可用时,需要增加延时重试机制,在建立Socket连接的过程中出现异常,需要重新建立连接等。

数据的发送(TCPSendThread)

长连接建立后,我们需要保存返回的Socket实例–代表长连接的通道,Socket通信发送的是字节数据,通常情况下,一个完整的消息至少包含协议头 + 协议主体内容 + 校验码

数据的接收(TCPReceiveThread)

推送数据的接收和数据的发送流程类似,只不过一个是从DataInputStream中读取数据,一个是写数据

心跳包的实现(TCPHeartBeatThread)

心跳包就是一个遵循某个自定义协议的数据,处理可以使用心跳包实现长连接保活之外,根据具体需求,也可以往心跳包中怎家一个其他信息。在Android中心跳包的间隔性发送可以通过定时器AlarmManager实现,也可通过while循环+Thread.sleep方式实现。
通过四个步骤一个SDK雏形就有了,当然想要形成一个完整的SDK需要考虑推送SDK服务的保活,SDK和服务端通信的安全性保证,SDK耗费的电量和网络流量等性能指标的优化。同一个手机多个APP使用的推送SDK服务和Socket通道的复用等。

没有更多推荐了,返回首页