最近做项目需要实现时钟同步的功能, 对于NTP协议有现成的开源库很容易实现, 但是设置系统时间JAVA本身并不支持, 看来只能访问系统库了. 把我的研究心得给大家分享.
java是跨平台的, 所以访问系统库也要尽可能的跨平台了. 最初我希望通过调用标准C函数来实现跨平台, 实验了之后发现标准C函数只支持到秒级, 而时钟同步精度至少要到毫秒级, 很失望. 只好再找去它方法, 后来想到kernel32.dll应该是所有windows系统都有的库, 而libc.so是所有linux系统都有的库, 恩, 看来只能用这种方法来分别处理了, 也算是跨平台了.
1. 环境
jdk1.6
windows xp
openSUSE11.2
2. 依赖的jar
本程序需要依赖JNA框架.
jna.jar
3. 接口
import java.util.Date;
public interface JNative {
/**
* 设置系统时间
* @param date Date
*/
public void setLocalTime(Date date);
}
4. windows实现
import java.util.Calendar;
import java.util.Date;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Structure;
public class WindowsImpl implements JNative{
public static class SYSTEMTIME extends Structure {
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
}
public interface Kernel32 extends Library{
public boolean SetLocalTime(SYSTEMTIME st); //带时区
public int GetCurrentProcessId();
}
public static Kernel32 kernel32Instance = null;
public WindowsImpl(){
kernel32Instance = (Kernel32)Native.loadLibrary("kernel32", Kernel32.class);
}
@Override
public void setLocalTime(Date date) {
Calendar c = Calendar.getInstance();
c.setTime(date);
SYSTEMTIME st = new SYSTEMTIME();
st.wYear = (short)c.get(Calendar.YEAR);
st.wMonth = (short)(c.get(Calendar.MONTH) + 1);
st.wDay = (short)c.get(Calendar.DAY_OF_MONTH );
st.wDayOfWeek = (short)c.get(Calendar.DAY_OF_WEEK);
st.wHour = (short)c.get(Calendar.HOUR_OF_DAY);
st.wMinute = (short)c.get(Calendar.MINUTE);
st.wSecond = (short)c.get(Calendar.SECOND);
st.wMilliseconds = (short)c.get(Calendar.MILLISECOND);
kernel32Instance.SetLocalTime(st);
}
}
5. linux实现
import java.util.Date;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.Structure;
public class LinuxImpl implements JNative {
public static class tm extends Structure{
public static class ByReference extends tm implements Structure.ByReference{}
public static class ByValue extends tm implements Structure.ByValue{}
public int tm_sec;//seconds 0-61
public int tm_min;//minutes 1-59
public int tm_hour;//hours 0-23
public int tm_mday;//day of the month 1-31
public int tm_mon;//months since jan 0-11
public int tm_year;//years from 1900
public int tm_wday;//days since Sunday, 0-6
public int tm_yday;//days since Jan 1, 0-365
public int tm_isdst;//Daylight Saving time indicator
}
public static class timeval extends Structure{
public static class ByReference extends timeval implements Structure.ByReference{}
public static class ByValue extends timeval implements Structure.ByValue{}
public NativeLong tv_sec; /* 秒数 */
public NativeLong tv_usec; /* 微秒数 */
}
public static class timezone extends Structure{
public static class ByReference extends timezone implements Structure.ByReference{}
public static class ByValue extends timezone implements Structure.ByValue{}
public int tz_minuteswest;
public int tz_dsttime;
}
public interface CLibrary extends Library{
int gettimeofday(timeval.ByReference tv, timezone.ByReference tz);
int settimeofday(timeval.ByReference tv, timezone.ByReference tz);
}
public static CLibrary cLibraryInstance = null;
public LinuxImpl(){
cLibraryInstance = (CLibrary)Native.loadLibrary("c", CLibrary.class);
}
@Override
public void setLocalTime(Date date) {
long ms = date.getTime();
long s = ms / 1000; //秒
long us = (ms % 1000) * 1000; //微秒
timeval.ByReference tv = new timeval.ByReference();
timezone.ByReference tz = new timezone.ByReference();
cLibraryInstance.gettimeofday(tv, tz);
tv.tv_sec = new NativeLong(s);
tv.tv_usec = new NativeLong(us);
cLibraryInstance.settimeofday(tv, tz);
}
}
6. 工厂类
import com.sun.jna.Platform;
public class NativeFactory {
public static JNative newNative(){
if(Platform.isWindows()){
return new WindowsImpl();
}
return new LinuxImpl();
}
}
7. 测试
public static void main(String args[]){
System.out.println("1. " + new Date());
JNative jNative = NativeFactory.newNative();
jNative.setLocalTime(new Date(System.currentTimeMillis() + (10*60*1000))); //10分钟
System.out.println("2. " + new Date());
}
执行结果是系统时间增加了10分钟, 本程序在windows xp和openSUSE11.2下测试通过.
注意new Date()获取的时间为系统时间, 不是硬件时间.