NTP服务器同步android系统时间

做android开发有一段时间了,之前一直做服务类的产品,最近公司接手sip话机的项目,里面涉及到修改系统设置的问题,今天就把我在项目中有关NTP服务器同步android系统时间遇到的种种问题,在这做一个总结,希望给后面的人一些帮助,本人也是首次接触这样的项目,总结的如有不完善的地方,希望大神指点出来。。。。


ntp服务器地址

有很多,网可以搜一下
cn.pool.ntp.org
s1a.time.edu.cn 北京邮电大学
s1b.time.edu.cn 清华大学
s1c.time.edu.cn 北京大学
s1d.time.edu.cn 东南大学
s1e.time.edu.cn 清华大学
s2a.time.edu.cn 清华大学
s2b.time.edu.cn 清华大学
中国[China] cn.ntp.org.cn
美国[America] us.ntp.org.cn
韩国[korea] kr.ntp.org.cn
新加坡[Singapore] sgp.ntp.org.cn


由于项目要求,在项目中同步系统时间是每隔多久同步一次系统时间,通过TimerTask来执行任务。通过handler来发送处理消息。相关代码:
/**
*校准时间
* 从ntp服务器中获取时间
* @param ntpHost
* ntp服务器域名地址
* @return 如果失败返回-1,否则返回当前的毫秒数
*/

public static void startCalibrateTime(final String mhostAddress,
			final int cycleTime) {
		MyLog.d(tag, "startCalibrateTime()");
		if (mCycleTimer != null) {
			mCycleTask.cancel();
			mCycleTimer.cancel();
			mCycleTimer.purge();
			mCycleTask = null;
			mCycleTimer = null;
		}
		mCycleTask = new TimerTask() {

			@Override
			public void run() {
				MyLog.d(tag, "run()");
				long time = getTimeFromNtpServer(mhostAddress);//从获取ntp服务器上获取时间
				if (time == -1) {
					MyLog.e(tag, "async time failed.");
				} else {
					SystemClock.setCurrentTimeMillis(time);//设置系统时间
				}
				if (isTurnToSuccess) {
					isTurnToSuccess = false;
					mHandler.sendEmptyMessage(MSG_NTP_SEARCH_OK);
				}
			}
			@Override
			public boolean cancel() {
				MyLog.d(tag, "cancel()");
				return super.cancel();
			}
		};
		mCycleTimer = new NgnTimer();
		mCycleTimer.schedule(mCycleTask, 0, cycleTime);
		MyLog.d(tag, "start ntp timer  time:" + cycleTime / 1000);
	}

handler处理消息,在不同的消息出处理不同的操作,针对项目要求,我在项目中保存了ntp服务器地址和每次隔多久同步一次的时间,可以自行定义。
涉及到的变量:
private static String mNtpServer = “pool.ntp.org”;
/**
* NTP获取时间失败时,每隔30s周期性重新获取,直至成功,成功后恢复正常计时
*/
private final static int CYCLE_TIME_ERROR = 30000;

private static Handler mHandler = new Handler() {
		@Override
		public void handleMessage(android.os.Message msg) {
			if (msg.what == MSG_NTP_SEARCH_FAILED) {
				String mhostAddress = GlobalConfigUtils
						.get(ConfTag.DATETIME_SNTP_SERVER);
				if (TextUtils.isEmpty(mhostAddress)) {
					mhostAddress = mNtpServer;
				}
				startCalibrateTime(mhostAddress, CYCLE_TIME_ERROR);
			} else if (msg.what == MSG_NTP_SEARCH_OK) {
				String mhostAddress = GlobalConfigUtils
						.get(ConfTag.DATETIME_SNTP_SERVER);//项目中保存的ntp服务器地址
				if (TextUtils.isEmpty(mhostAddress)) {
					mhostAddress = mNtpServer;
				}
				String timeStr = GlobalConfigUtils			.get(ConfTag.DATETIME_NTP_RESYNC_TIME);//项目中设置的系统默认隔多久同步时间
				int time = 168 * 60 * 60 * 1000;
				if (!TextUtils.isEmpty(timeStr)
						&& TextUtils.isDigitsOnly(timeStr)) {
					time = Integer.parseInt(timeStr)*3600*1000;
				}
				startCalibrateTime(mhostAddress, time);
			}
		};
	};

停止校准时间

public static void stopCalibrateTimer() {
		if (mCycleTimer != null) {
			mCycleTask.cancel();
			mCycleTimer.cancel();
			mCycleTimer.purge();
			mCycleTask = null;
			mCycleTimer = null;
		}
	}

最重要的部分就是从ntp服务器上获取时间,这块很重要。。重要原因就是NTP工作原理简单分析一下:
下面是NTP工作原理图,图片是从我看的资料上截取过来勿喷。
Device A 与 Device B 通过网络相连,都有自己独立的系统时钟,需要通过ntp实现两个系统时钟的时间同步
1.Device B作为服务器,Device A作为客户端,需要网络使本地时钟与服务器时钟同步,假设同步之前,Device A的时间是10:00:00am,Device B的时间是11:00:00am
2.NTP报文的Device A 和Device B 之间单向传输时间是1秒,
3.Device处理报文时间是1秒

NTP服务器同步android系统时间_第1张图片

Device A 与Devide B 工作流程如下

  • Device A发送一个NTP请求报文给Devide B ,该报文带有Device A 离开时的时间戳,该时间戳为10:00:00am(T1)
  • 当NTP报文到达Devide B 时,Devide B 加上自己的时间戳11:00:01am(T2)
  • 当NTP报文离开Devide B 时,Devide B 再加上自己的时间戳11:00:02am(T3)
  • 当Device A接受到NTP响应报文 时,Devide A本地时间11:00:02am(T4)
    ntp工作原理介绍完了,现在应该很清楚原理了吧,现在回过头来继续。。

获取ntp时间:

public static long getTimeFromNtpServer(String hostAddress) {
		MyLog.d(tag, "getTimeFromNtpServer()");
		if (TextUtils.isEmpty(hostAddress)) {
			MyLog.e(tag, "Ntp host is null.");
			return -1;
		}
		if (mNtpClient == null) {
			mNtpClient = new SntpClient();
		}
		boolean isSuccessful = mNtpClient.requestTime(hostAddress, 20000);
		MyLog.d(tag, "requestTime:" + isSuccessful);
		if (isSuccessful) {
			long now = mNtpClient.getNtpTime();//now就是获取的时间
			if (isInErrorCycle) {
				if(!isTurnToSuccess){
					isTurnToSuccess = true;
				}
				isInErrorCycle = false;
			}
			return now;
		} else {
			if (!isInErrorCycle) {
				isInErrorCycle = true;
				isTurnToSuccess = false;
				mHandler.sendEmptyMessage(MSG_NTP_SEARCH_FAILED);
			}

		}
		return -1;
	}

接下来就是封装好的获取时间方法,获取原理如上,接下来就是原理的代码,可以配合原理来理解

public static class SntpClient {
		private static final String TAG = "SntpClient";

		private static final int REFERENCE_TIME_OFFSET = 16;
		private static final int ORIGINATE_TIME_OFFSET = 24;
		private static final int RECEIVE_TIME_OFFSET = 32;
		private static final int TRANSMIT_TIME_OFFSET = 40;
		private static final int NTP_PACKET_SIZE = 48;

		private static final int NTP_PORT = 123;
		private static final int NTP_MODE_CLIENT = 3;
		private static final int NTP_VERSION = 3;

		// Number of seconds between Jan 1, 1900 and Jan 1, 1970
		// 70 years plus 17 leap days
		private static final long OFFSET_1900_TO_1970 = ((365L * 70L) + 17L) * 24L * 60L * 60L;

		// system time computed from NTP server response
		private long mNtpTime;

		// value of SystemClock.elapsedRealtime() corresponding to mNtpTime
		private long mNtpTimeReference;

		// round trip time in milliseconds
		private long mRoundTripTime;

		/**
		 * Sends an SNTP request to the given host and processes the response.
		 * 
		 * @param host
		 *            host name of the server.
		 * @param timeout
		 *            network timeout in milliseconds.
		 * @return true if the transaction was successful.
		 */
		public boolean requestTime(String host, int timeout) {
			DatagramSocket socket = null;
			try {
				socket = new DatagramSocket();
				socket.setSoTimeout(timeout);
				InetAddress address = InetAddress.getByName(host);
				byte[] buffer = new byte[NTP_PACKET_SIZE];
				DatagramPacket request = new DatagramPacket(buffer,
						buffer.length, address, NTP_PORT);

				// set mode = 3 (client) and version = 3
				// mode is in low 3 bits of first byte
				// version is in bits 3-5 of first byte
				buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);

				// get current time and write it to the request packet
				long requestTime = System.currentTimeMillis();
				MyLog.d(TAG, "RequestTime:"+new Date(requestTime));
				long requestTicks = SystemClock.elapsedRealtime();
				writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);

				socket.send(request);

				// read the response
				DatagramPacket response = new DatagramPacket(buffer,
						buffer.length);
				socket.receive(response);
				long responseTicks = SystemClock.elapsedRealtime();
				long responseTime = requestTime
						+ (responseTicks - requestTicks);

				// extract the results
				long originateTime = readTimeStamp(buffer,
						ORIGINATE_TIME_OFFSET);
				long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
				long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
				long roundTripTime = responseTicks - requestTicks
						- (transmitTime - receiveTime);
				// receiveTime = originateTime + transit + skew
				// responseTime = transmitTime + transit - skew
				// clockOffset = ((receiveTime - originateTime) + (transmitTime
				// - responseTime))/2
				// = ((originateTime + transit + skew - originateTime) +
				// (transmitTime - (transmitTime + transit - skew)))/2
				// = ((transit + skew) + (transmitTime - transmitTime - transit
				// + skew))/2
				// = (transit + skew - transit + skew)/2
				// = (2 * skew)/2 = skew
				long clockOffset = ((receiveTime -  requestTime) + (transmitTime - System.currentTimeMillis())) / 2;
				// if (false) Log.d(TAG, "round trip: " + roundTripTime +
				// " ms");
				// if (false) Log.d(TAG, "clock offset: " + clockOffset +
				// " ms");

				// save our results - use the times on this side of the network
				// latency
				// (response rather than request time)
				mNtpTime = System.currentTimeMillis() + clockOffset;
//				mNtpTime = transmitTime;
				mNtpTimeReference = responseTicks;
				mRoundTripTime = roundTripTime;
			} catch (Exception e) {
				if (false)
					Log.d(TAG, "request time failed:" + e);
				e.printStackTrace();
				return false;
			} finally {
				if (socket != null) {
					socket.close();
				}
			}

			return true;
		}

		/**
		 * Returns the time computed from the NTP transaction.
		 * 
		 * @return time value computed from NTP server response.
		 */
		public long getNtpTime() {
			return mNtpTime;
		}

		/**
		 * Returns the reference clock value (value of
		 * SystemClock.elapsedRealtime()) corresponding to the NTP time.
		 * 
		 * @return reference clock corresponding to the NTP time.
		 */
		public long getNtpTimeReference() {
			return mNtpTimeReference;
		}

		/**
		 * Returns the round trip time of the NTP transaction
		 * 
		 * @return round trip time in milliseconds.
		 */
		public long getRoundTripTime() {
			return mRoundTripTime;
		}

		/**
		 * Reads an unsigned 32 bit big endian number from the given offset in
		 * the buffer.
		 */
		private long read32(byte[] buffer, int offset) {
			byte b0 = buffer[offset];
			byte b1 = buffer[offset + 1];
			byte b2 = buffer[offset + 2];
			byte b3 = buffer[offset + 3];

			// convert signed bytes to unsigned values
			int i0 = ((b0 & 0x80) == 0x80 ? (b0 & 0x7F) + 0x80 : b0);
			int i1 = ((b1 & 0x80) == 0x80 ? (b1 & 0x7F) + 0x80 : b1);
			int i2 = ((b2 & 0x80) == 0x80 ? (b2 & 0x7F) + 0x80 : b2);
			int i3 = ((b3 & 0x80) == 0x80 ? (b3 & 0x7F) + 0x80 : b3);

			return ((long) i0 << 24) + ((long) i1 << 16) + ((long) i2 << 8)
					+ (long) i3;
		}

		/**
		 * Reads the NTP time stamp at the given offset in the buffer and
		 * returns it as a system time (milliseconds since January 1, 1970).
		 */
		private long readTimeStamp(byte[] buffer, int offset) {
			long seconds = read32(buffer, offset);
			long fraction = read32(buffer, offset + 4);
			return ((seconds - OFFSET_1900_TO_1970) * 1000)
					+ ((fraction * 1000L) / 0x100000000L);
		}

		/**
		 * Writes system time (milliseconds since January 1, 1970) as an NTP
		 * time stamp at the given offset in the buffer.
		 */
		private void writeTimeStamp(byte[] buffer, int offset, long time) {
			long seconds = time / 1000L;
			long milliseconds = time - seconds * 1000L;
			seconds += OFFSET_1900_TO_1970;

			// write seconds in big endian format
			buffer[offset++] = (byte) (seconds >> 24);
			buffer[offset++] = (byte) (seconds >> 16);
			buffer[offset++] = (byte) (seconds >> 8);
			buffer[offset++] = (byte) (seconds >> 0);

			long fraction = milliseconds * 0x100000000L / 1000L;
			// write fraction in big endian format
			buffer[offset++] = (byte) (fraction >> 24);
			buffer[offset++] = (byte) (fraction >> 16);
			buffer[offset++] = (byte) (fraction >> 8);
			// low order bits should be random data
			buffer[offset++] = (byte) (Math.random() * 255.0);
		}
	}


以上就是从ntp服务器上同步系统时间,项目比较完整,有疑问大家提出来,互相学习,到此结束。

你可能感兴趣的:(android,ntp同步系统时间,android开发,服务器,ntp同步时间)