场景:时间服务器用于Internet众多的网络设备时间同步。时间服务器的默认端口为37,当时间服务器收到客户端的连接请求,立即向返回一个网络时间。
网络时间为一个4字节的无符号整数,它表示当前时间和1900年元月1日0点整的秒数值差。
JTimeClient.java
import java.net.*;
import java.io.*;
public class JTimeClient {
private String server; //时间服务器
private int port; //端口号
public JTimeClient(String server) {
this(server, 37);//时间服务器默认端口号37
}
public JTimeClient(String server, int port) {
this.server = server;
this.port = port;
}
//返回网络时间, -1表示出错
public long getNetTime() {
Socket socket = null;
InputStream in = null;
try {
//连接
socket = new Socket(server, port);
//时间服务器
in = socket.getInputStream( );
//读取数据,网络时间为4字节无符号整数,
//表示基于1900年元月1日0点的秒数
long netTime = 0;
for(int i=0; i<4; i++) {
netTime = (netTime << 8) | in.read();
}
return netTime;
}
catch (UnknownHostException e) {
//e.printStackTrace();
}
catch (IOException e) {
//e.printStackTrace();
}
finally {//安全释放资源
try {//关闭输入流
if(in != null) in.close();
} catch (Exception e) {}
try {//关闭连接
if(socket != null) socket.close();
} catch (Exception e) {}
}
return -1;
}
public static void main(String[] args) {
JTimeClient timeClient = null;
if(args.length == 1) {//命令行参数指定时间服务器
timeClient = new JTimeClient(args[0]);
}
else if(args.length == 2) {//指定时间服务器及其端口
timeClient = new JTimeClient(args[0], Integer.parseInt(args[1]));
}
else {
System.out.println("Usage: java JTimeClient TimeServer Port");
return;
}
System.out.println("Time:" + timeClient.getNetTime());
}
}
如果未启动时间服务器,直接运行本程序结果:
java JTimeClient localhost
Time:-1
轮流工作方式:JTimeServer.java
import java.net.*;
import java.io.*;
import java.util.Date;
public class JTimeServer implements Runnable {
private int port;
public JTimeServer() {
this(37);//时间服务器默认端口号37
}
public JTimeServer(int port) {
this.port = port;
}
public void run() {
try {
//创建服务器套接字
ServerSocket server = new ServerSocket(port);
//轮流处理多个客户端的请求
while(true) {
Socket connection = null;
try {
//等待客户端连接请求
connection = server.accept();
//利用Date.getTime获取当前系统时间(单位毫秒)
//常数2208988800为网络时间和系统时间差
Date now = new Date();
long netTime = now.getTime()/1000 + 2208988800L;
byte[] time = new byte[4];
for(int i=0; i<4; i++) {
time[3 - i] = (byte)(netTime &0x00000000000000FFL);
netTime >>= 8;
}
//获取套接字输入流,并写入网络时间
OutputStream out = connection.getOutputStream();
out.write(time);
out.flush();
}
catch (IOException e) {
}
finally {//关闭当前连接
if (connection != null) connection.close();
}
}
}
catch(IOException e) {
}
}
public static void main(String[] args) {
JTimeServer timeServer = null;
if(args.length == 0) {
timeServer = new JTimeServer();
}
else if(args.length == 1) {//命令行参数指定时间服务器监听端口
timeServer = new JTimeServer(Integer.parseInt(args[0]));
}
else {
System.out.println("Usage: java JTimeServer Port");
return;
}
(new Thread(timeServer)).start();
}
}
运行结果:
3558353322
并发工作方式:JTimeServer2.java
import java.net.*;
import java.io.*;
import java.util.Date;
class JTimeThread extends Thread {
private Socket connection;
public JTimeThread(Socket connection) {
this.connection = connection;
}
public void run() {
try {
//利用Date.getTime获取当前系统时间(单位毫秒)
//常数2208988800为网络时间和系统时间差
Date now = new Date();
long netTime = now.getTime()/1000 + 2208988800L;
byte[] time = new byte[4];
for(int i=0; i<4; i++) {
time[3 - i] = (byte)(netTime &0x00000000000000FFL);
netTime >>= 8;
}
//获取套接字输入流,并写入网络时间
OutputStream out = connection.getOutputStream();
out.write(time);
out.flush();
}
catch(IOException e) {
}
finally {//关闭和客户端的连接
try {
if (connection != null) connection.close();
} catch (IOException e) {}
}
}
}
public class JTimeServer2 implements Runnable {
private int port;
public JTimeServer2() {
this(37);//时间服务器默认端口号37
}
public JTimeServer2(int port) {
this.port = port;
}
public void run() {
ServerSocket server = null;
try {
//创建服务器套接字
server = new ServerSocket(port);
//轮流处理多个客户端的请求
while(true) {
try {
//等待客户端连接请求
Socket connection = server.accept();
//创建并启动一个JTimeThread线程来服务客户端请求
(new JTimeThread(connection)).start();
}
catch (IOException e) {
}
}
}
catch(IOException e) {
}
finally { //关闭服务器Socket
try {
if(null != server) server.close();
} catch (IOException e) {}
}
}
public static void main(String[] args) {
JTimeServer2 timeServer = null;
if(args.length == 0) {
timeServer = new JTimeServer2();
}
else if(args.length == 1) {//命令行参数指定时间服务器监听端口
timeServer = new JTimeServer2(Integer.parseInt(args[0]));
}
else {
System.out.println("Usage: java JTimeServer2 Port");
return;
}
(new Thread(timeServer)).start();
}
}
Date.getTime()获取当前系统时间,这是一个基于1927元月1日0点的long型毫秒数。
PS注意:
在向Socket写入数据的时候,我们还要注意一个非常重要的细节:网络字节顺序为高字节在前。
Java 新IO再进化Selector创建一个非阻塞的服务器,此服务器向客户端返回当前的系统时间。
import java.net.InetSocketAddress ;
import java.net.ServerSocket ;
import java.util.Set ;
import java.util.Iterator ;
import java.util.Date ;
import java.nio.channels.ServerSocketChannel ;
import java.nio.ByteBuffer ;
import java.nio.channels.SocketChannel ;
import java.nio.channels.Selector ;
import java.nio.channels.SelectionKey ;
public class DateServer{
public static void main(String args[]) throws Exception {
int ports[] = {8000,8001,8002,8003,8005,8006} ; // 表示五个监听端口
Selector selector = Selector.open() ; // 通过open()方法找到Selector
for(int i=0;i<ports.length;i++){
ServerSocketChannel initSer = null ;
initSer = ServerSocketChannel.open() ; // 打开服务器的通道
initSer.configureBlocking(false) ; // 服务器配置为非阻塞
ServerSocket initSock = initSer.socket() ;
InetSocketAddress address = null ;
address = new InetSocketAddress(ports[i]) ; // 实例化绑定地址
initSock.bind(address) ; // 进行服务的绑定
initSer.register(selector,SelectionKey.OP_ACCEPT) ; // 等待连接
System.out.println("服务器运行,在" + ports[i] + "端口监听。") ;
}
// 要接收全部生成的key,并通过连接进行判断是否获取客户端的输出
int keysAdd = 0 ;
while((keysAdd=selector.select())>0){ // 选择一组键,并且相应的通道已经准备就绪
Set<SelectionKey> selectedKeys = selector.selectedKeys() ;// 取出全部生成的key
Iterator<SelectionKey> iter = selectedKeys.iterator() ;
while(iter.hasNext()){
SelectionKey key = iter.next() ; // 取出每一个key
if(key.isAcceptable()){
ServerSocketChannel server = (ServerSocketChannel)key.channel() ;
SocketChannel client = server.accept() ; // 接收新连接
client.configureBlocking(false) ;// 配置为非阻塞
ByteBuffer outBuf = ByteBuffer.allocateDirect(1024) ; //
outBuf.put(("当前的时间为:" + new Date()).getBytes()) ; // 向缓冲区中设置内容
outBuf.flip() ;
client.write(outBuf) ; // 输出内容
client.close() ; // 关闭
}
}
selectedKeys.clear() ; // 清楚全部的key
}
}
}
注意:服务器运行后并不会像传统的Socket那样立刻关闭服务器,而是会继续等待下一次的连接。